swoole基礎(chǔ)-常見的websocket問題

上一節(jié)我們講述了websocket在swoole中的使用嚷掠,并且我們也給出了一個(gè)簡(jiǎn)單的聊天模型,不同的客戶端可以相互發(fā)消息服猪。有些同學(xué)不以為然饼拍,server有swoole提供強(qiáng)大的API,客戶端由h5提供websocket API向楼,操作很方便查吊,沒感覺到什么問題呀,這一章節(jié)是否有存在的必要性呢湖蜕?

有逻卖,非常有。今天我們就針對(duì)websocket中常見的幾個(gè)問題做一個(gè)詳細(xì)的總結(jié)說明昭抒,具體要說的重點(diǎn)大概有下面3個(gè)

  • 心跳檢測(cè)的必要性
  • 校驗(yàn)客戶端連接的有效性
  • 客戶端的重連機(jī)制

我們分別來看下

心跳檢測(cè)

還記得我們?cè)谶M(jìn)程模型一文中介紹的Master進(jìn)程嗎评也?當(dāng)時(shí)我們說過,Master進(jìn)程灭返,包括主線程盗迟,多個(gè)Reactor線程等。其實(shí)主進(jìn)程內(nèi)還包括其他線程熙含,比如我們現(xiàn)在講的心跳檢測(cè)罚缕,在Master進(jìn)程內(nèi)就有專門用于心跳檢測(cè)的線程。

那到底什么是心跳檢測(cè)呢怎静?說著websocket怕磨,怎么談到要醫(yī)治病人了喂饥?這個(gè)心跳檢測(cè)呢,是server定時(shí)檢測(cè)客戶端是否還連接的意思肠鲫,即server定時(shí)檢測(cè)client是否還活著员帮,所以我們說的專業(yè)點(diǎn)就是所謂的心跳檢測(cè)。

等等导饲,老師你說“定時(shí)檢測(cè)”捞高?是不是說之前學(xué)的定時(shí)器可以派上用場(chǎng)了?

怎么感覺之前講的不教你在實(shí)際場(chǎng)景中運(yùn)用一次你就不會(huì)似的渣锦。當(dāng)然硝岗,你要是用定時(shí)器也沒問題,不過呢袋毙,我們都說有專門的心跳檢測(cè)線程的存在了型檀,所以,我們只需要簡(jiǎn)單的配置听盖,開啟這個(gè)心跳檢測(cè)線程就可以了胀溺。

有同學(xué)還有疑問,server我們有onClose回調(diào)皆看,客戶端斷開連接我們可以主動(dòng)關(guān)閉連接或者刪除客戶端的映射關(guān)系仓坞,再者說,即使連接無效腰吟,斷了就斷了唄无埃,反正我的server面向的client也沒有多少,心跳檢測(cè)就真的有存在的必要性么毛雇?

正常情況下嫉称,不需要×榇客戶端斷開連接能夠通知到server织阅,server自然也就可以主動(dòng)關(guān)閉連接。但是始藕,有很多非正常情況的存在蒲稳,比如斷電斷網(wǎng)尤其是移動(dòng)網(wǎng)絡(luò)盛行的當(dāng)下氮趋,二者之間建立的友好關(guān)系(連接)非常不穩(wěn)定伍派,這就必然會(huì)導(dǎo)致大量的fd(fd的數(shù)量是有限的,還記得最大是多少嗎剩胁?)被浪費(fèi)诉植!所以為了解決這些問題,swoole內(nèi)置了心跳檢測(cè)機(jī)制昵观。

我們只需要做如下簡(jiǎn)單的配置即可

$serv->set([
    'heartbeat_check_interval' => N,
    'heartbeat_idle_time' => M,
]);

如上晾腔,分別配置heartbeat_check_interval和heartbeat_idle_time參數(shù)舌稀,二者配合使用,其含義就是N秒檢查一次灼擂,看看哪些連接M內(nèi)沒有活動(dòng)的壁查,就認(rèn)為這個(gè)連接是無效的,server就會(huì)主動(dòng)關(guān)閉這個(gè)無效的連接剔应。

是不是說N秒server會(huì)主動(dòng)向客戶端發(fā)一個(gè)心跳包睡腿,沒有收到客戶端響應(yīng)的才認(rèn)為這個(gè)連接是死連接呢?那還要heartbeat_idle_time做什么峻贮,對(duì)吧席怪?

swoole的實(shí)現(xiàn)原理是這樣的:server每次收到客戶端的數(shù)據(jù)包都會(huì)記錄一個(gè)時(shí)間戳,N秒內(nèi)循環(huán)檢測(cè)下所有的連接纤控,如果M秒內(nèi)該連接還沒有活動(dòng)挂捻,才斷開這個(gè)連接。

心跳檢測(cè)的問題船万,記得自己動(dòng)手實(shí)踐實(shí)踐哦刻撒,有不懂的可以下面給我留言。

校驗(yàn)客戶端連接的有效性

按照我們上文創(chuàng)建的websocket server唬涧,當(dāng)然只有本地的ip才能連接上疫赎,因?yàn)閟erver監(jiān)聽的ip是127.0.0.1。實(shí)際項(xiàng)目上線后碎节,如果你的websocket server是對(duì)外開放的捧搞,就需要把ip修改為服務(wù)器外網(wǎng)的ip地址或者修改為0.0.0.0。

如此狮荔,也便帶來了新的問題:

任意客戶端都可以連接到我們的server了胎撇,這個(gè)“任意”可不止我們自己認(rèn)為有效的客戶端,還包括你的我的所有的非有效或者惡意的連接殖氏,這可不是我們想要的晚树。

如何避免這一問題呢?方法有很多種雅采,比如我們可以在連接的時(shí)候認(rèn)為只有g(shù)et傳遞的參數(shù)valid=1才允許連接爵憎;或者我們只允許登錄用戶才可以連接server;再或者我們可以校驗(yàn)客戶端每次send所攜帶的token婚瓜,server對(duì)該值校驗(yàn)通過后才認(rèn)為當(dāng)前是有效連接等等宝鼓。與此同時(shí),server開啟心跳檢測(cè)巴刻,對(duì)于惡意無效的連接愚铡,直接干掉!

上面簡(jiǎn)單的介紹了一些解決方案,下面我們以client 連接server時(shí)攜帶token為例做一個(gè)實(shí)際說明沥寥。

首先我們只允許登錄用戶才可以連接server碍舍,假設(shè)某用戶的唯一標(biāo)識(shí)uid=100,token的生成規(guī)則我們約定如下:token=md5(md5(uid)+key)邑雅,其中key=客戶端和服務(wù)端雙方約定的某個(gè)字符串片橡,我們這里假設(shè)key="^manks.top&swoole$",不包括雙引號(hào)淮野。

server的代碼實(shí)現(xiàn)如下(詳細(xì)的代碼參考WebSocketServerValid.php )

<?php

class WebSocketServerValid
{
    private $_serv;
    public $key = '^manks.top&swoole$';

    public function __construct()
    {
        $this->_serv = new swoole_websocket_server("127.0.0.1", 9501);
        $this->_serv->set([
            'worker_num' => 1,
            'heartbeat_check_interval' => 30,
            'heartbeat_idle_time' => 62,
        ]);
        $this->_serv->on('open', [$this, 'onOpen']);
        $this->_serv->on('message', [$this, 'onMessage']);
        $this->_serv->on('close', [$this, 'onClose']);
    }

    /**
     * @param $serv
     * @param $request
     */
    public function onOpen($serv, $request)
    {
        $this->checkAccess($serv, $request);
    }

    /**
     * @param $serv
     * @param $frame
     */
    public function onMessage($serv, $frame)
    {
        $this->_serv->push($frame->fd, 'Server: ' . $frame->data);
    }
    public function onClose($serv, $fd)
    {
        echo "client {$fd} closed.\n";
    }

    /**
     * 校驗(yàn)客戶端連接的合法性,無效的連接不允許連接
     * @param $serv
     * @param $request
     * @return mixed
     */
    public function checkAccess($serv, $request)
    {
        // get不存在或者uid和token有一項(xiàng)不存在锻全,關(guān)閉當(dāng)前連接
        if (!isset($request->get) || !isset($request->get['uid']) || !isset($request->get['token'])) {
            $this->_serv->close($request->fd);
            return false;
        }
        $uid = $request->get['uid'];
        $token = $request->get['token'];
        // 校驗(yàn)token是否正確,無效關(guān)閉連接
        if (md5(md5($uid) . $this->key) != $token) {
            $this->_serv->close($request->fd);
            return false;
        }
    }

    public function start()
    {
        $this->_serv->start();
    }
}

$server = new WebSocketServerValid;
$server->start();

可以看到,checkAccess是授權(quán)方法录煤,我們?cè)趏nOpen回調(diào)內(nèi)對(duì)uid以及token進(jìn)行了校驗(yàn)鳄厌,無效則關(guān)閉連接。

為了模擬效果妈踊,我們分別貼上兩種客戶端代碼了嚎,連接失敗和連接成功

連接失敗的主要jsdiamante如下(詳細(xì)代碼見源碼的websocket-client-faild.html)

var ws = new WebSocket('ws://127.0.0.1:9501');
ws.onopen = function(event) {
    ws.send('This is websocket client.');
};
ws.onmessage = function(event) {
    console.log(event.data);
};
ws.onclose = function(event) {
    console.log('Client has closed.\n');
};

無論是console控制臺(tái)還是server終端我們都可以看到客戶端連接被關(guān)閉的提醒。下面我們?cè)倏茨M一種成功的結(jié)果

部分php代碼和js代碼如下(詳細(xì)代碼見源碼的websocket-client-success.html)

<?php
$key = '^manks.top&swoole$';
$uid = 100;
$token = md5(md5($uid) . $key);
?>

<script>
var ws = new WebSocket("ws://127.0.0.1:9501?uid=<?php echo $uid; ?>&token=<?php echo $token; ?>");
ws.onopen = function(event) {
    ws.send('This is websocket client.');
};
ws.onmessage = function(event) {
    console.log(event.data);
};
ws.onclose = function(event) {
    console.log('Client has closed.\n');
};</script>

可以看到廊营,這次連接沒有被關(guān)閉且console控制臺(tái)會(huì)正常輸出一些信息

Server: This is websocket client.

即我們完成了校驗(yàn)連接有效性的案例歪泳,下面我們接著看最后一個(gè)問題

客戶端重連機(jī)制

有同學(xué)注意到,我們剛剛設(shè)置的心跳檢測(cè)時(shí)間是30秒露筒,如果客戶端62秒內(nèi)沒有與server通信呐伞,server會(huì)關(guān)閉該連接,即部分人在上述success案例中的console控制臺(tái)上會(huì)看到Client has closed.的提醒慎式。這是我們?cè)O(shè)置的機(jī)制伶氢,屬于正常現(xiàn)象瘪吏。

那我們要說的重連機(jī)制又是什么呢癣防?

客戶端重連機(jī)制又可以理解為一種保活機(jī)制掌眠,你也可以跟服務(wù)端的心跳檢測(cè)在一起理解為雙向心跳蕾盯。即我們有一種需求是,如何能保證客戶端和服務(wù)端的連接一直是有效的蓝丙,不斷開的级遭。

其實(shí)很簡(jiǎn)單,對(duì)客戶端而言渺尘,只要觸發(fā)error或者close再或者連接失敗挫鸽,就主動(dòng)重連server,這便是我們的目的沧烈。

下面貼一段js代碼掠兄,來解決這個(gè)問題(詳細(xì)代碼見commentClient.html)

<script>
var ws;//websocket實(shí)例
var lockReconnect = false;//避免重復(fù)連接
var wsUrl = 'ws://127.0.0.1:9501';

function createWebSocket(url) {
    try {
        ws = new WebSocket(url);
        initEventHandle();
    } catch (e) {
        reconnect(url);
    }
}

function initEventHandle() {
    ws.onclose = function () {
        reconnect(wsUrl);
    };
    ws.onerror = function () {
        reconnect(wsUrl);
    };
    ws.onopen = function () {
        //心跳檢測(cè)重置
        heartCheck.reset().start();
    };
    ws.onmessage = function (event) {
        //如果獲取到消息像云,心跳檢測(cè)重置
        //拿到任何消息都說明當(dāng)前連接是正常的
        heartCheck.reset().start();
    }
}

function reconnect(url) {
    if(lockReconnect) return;
    lockReconnect = true;
    //沒連接上會(huì)一直重連锌雀,設(shè)置延遲避免請(qǐng)求過多
    setTimeout(function () {
        createWebSocket(url);
        lockReconnect = false;
    }, 2000);
}

//心跳檢測(cè)
var heartCheck = {
    timeout: 60000,//60秒
    timeoutObj: null,
    serverTimeoutObj: null,
    reset: function(){
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
        return this;
    },
    start: function(){
        var self = this;
        this.timeoutObj = setTimeout(function(){
            //這里發(fā)送一個(gè)心跳蚂夕,后端收到后,返回一個(gè)心跳消息腋逆,
            //onmessage拿到返回的心跳就說明連接正常
            ws.send("");
            self.serverTimeoutObj = setTimeout(function(){//如果超過一定時(shí)間還沒重置婿牍,說明后端主動(dòng)斷開了
                ws.close();//如果onclose會(huì)執(zhí)行reconnect,我們執(zhí)行ws.close()就行了.如果直接執(zhí)行reconnect 會(huì)觸發(fā)onclose導(dǎo)致重連兩次
            }, self.timeout);
        }, this.timeout);
    }
}

createWebSocket(wsUrl);

</script>

在這種情況下惩歉,你可以嘗試把server中斷或者斷網(wǎng)試試等脂,結(jié)果是client會(huì)不停的每隔一定時(shí)間嘗試連接server,直至連接成功撑蚌。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末上遥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子争涌,更是在濱河造成了極大的恐慌粉楚,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亮垫,死亡現(xiàn)場(chǎng)離奇詭異模软,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)饮潦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門燃异,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人继蜡,你說我怎么就攤上這事回俐。” “怎么了稀并?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵鲫剿,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我稻轨,道長(zhǎng)灵莲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任殴俱,我火速辦了婚禮政冻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘线欲。我一直安慰自己明场,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布李丰。 她就那樣靜靜地躺著苦锨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舟舒,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天拉庶,我揣著相機(jī)與錄音,去河邊找鬼秃励。 笑死氏仗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的夺鲜。 我是一名探鬼主播皆尔,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼币励!你這毒婦竟也來了慷蠕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤食呻,失蹤者是張志新(化名)和其女友劉穎砌们,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搁进,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浪感,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了饼问。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片影兽。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖莱革,靈堂內(nèi)的尸體忽然破棺而出峻堰,到底是詐尸還是另有隱情,我是刑警寧澤盅视,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布捐名,位于F島的核電站,受9級(jí)特大地震影響闹击,放射性物質(zhì)發(fā)生泄漏镶蹋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一赏半、第九天 我趴在偏房一處隱蔽的房頂上張望贺归。 院中可真熱鬧,春花似錦断箫、人聲如沸拂酣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽婶熬。三九已至剑勾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赵颅,已是汗流浹背虽另。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留性含,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓鸳惯,卻偏偏與公主長(zhǎng)得像商蕴,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芝发,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一绪商、內(nèi)容概覽 WebSocket的出現(xiàn),使得瀏覽器具備了實(shí)時(shí)雙向通信的能力辅鲸。本文由淺入深格郁,介紹了WebSocket...
    Calvin李閱讀 2,522評(píng)論 2 10
  • 原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-WebSo...
    敢夢(mèng)敢當(dāng)閱讀 8,911評(píng)論 0 50
  • 前文再續(xù),就書接上一回独悴,隨著與Server例书、TCP、Protocol的邂逅刻炒,Swoole終于迎來了自己的故事决采,今天...
    蝸牛淋雨閱讀 1,735評(píng)論 1 14
  • 項(xiàng)目中的消息通知用到了websocket,感覺比http長(zhǎng)連接分塊發(fā)送好用坟奥,特此記錄一下树瞭。WebSocket協(xié)議用...
    ywhu閱讀 33,814評(píng)論 5 10
  • 新生兒衣服準(zhǔn)備:(秋天出生的孩子) 長(zhǎng)袖包屁衣 2套 薄款和尚服 長(zhǎng)袖 2套 秋冬連體衣 長(zhǎng)袖 2套 秋冬上衣和褲...
    新手媽媽luke閱讀 197評(píng)論 0 0