初探和實(shí)現(xiàn)websocket心跳重連

心跳重連緣由

在使用websocket過(guò)程中,可能會(huì)出現(xiàn)網(wǎng)絡(luò)斷開的情況,比如信號(hào)不好,或者網(wǎng)絡(luò)臨時(shí)性關(guān)閉,這時(shí)候websocket的連接已經(jīng)斷開,
而瀏覽器不會(huì)執(zhí)行websocket 的 onclose方法,我們無(wú)法知道是否斷開連接,也就無(wú)法進(jìn)行重連操作。
如果當(dāng)前發(fā)送websocket數(shù)據(jù)到后端,一旦請(qǐng)求超時(shí),onclose便會(huì)執(zhí)行,這時(shí)候便可進(jìn)行綁定好的重連操作。
因此websocket心跳重連就應(yīng)運(yùn)而生。

如何實(shí)現(xiàn)

在websocket實(shí)例化的時(shí)候,我們會(huì)綁定一些事件:

var ws = new WebSocket(url);
ws.onclose = function () {
    //something
};
ws.onerror = function () {
    //something
};
        
ws.onopen = function () {
   //something
};
ws.onmessage = function (event) {
   //something
}

如果希望websocket連接一直保持,我們會(huì)在close或者error上綁定重新連接方法。

ws.onclose = function () {
    reconnect();
};
ws.onerror = function () {
    reconnect();
};

這樣一般正常情況下失去連接時(shí),觸發(fā)onclose方法,我們就能執(zhí)行重連了。

那么針對(duì)斷網(wǎng)的情況的心跳重連,怎么實(shí)現(xiàn)呢。
簡(jiǎn)單的實(shí)現(xiàn):

var heartCheck = {
    timeout: 60000,//60ms
    timeoutObj: null,
    reset: function(){
        clearTimeout(this.timeoutObj);
     this.start();
    },
    start: function(){
        this.timeoutObj = setTimeout(function(){
            ws.send("HeartBeat");
        }, this.timeout)
    }
}

ws.onopen = function () {
   heartCheck.start();
};
ws.onmessage = function (event) {
    heartCheck.reset();
}

如上代碼,heartCheck 的 reset和start方法主要用來(lái)控制心跳的定時(shí)。

什么條件下執(zhí)行心跳:

當(dāng)onopen也就是連接上時(shí),我們便開始start計(jì)時(shí),如果在定時(shí)時(shí)間范圍內(nèi),onmessage獲取到了后端的消息,我們就重置倒計(jì)時(shí),距離上次從后端獲取到消息超過(guò)60秒之后,執(zhí)行心跳檢測(cè),看是不是斷連了,這個(gè)檢測(cè)時(shí)間可以自己根據(jù)自身情況設(shè)定。

判斷前端ws斷開(斷網(wǎng)但不限于斷網(wǎng)的情況):

當(dāng)心跳檢測(cè)send方法執(zhí)行之后,如果當(dāng)前websocket是斷開狀態(tài)(或者說(shuō)斷網(wǎng)了),發(fā)送超時(shí)之后,瀏覽器的ws會(huì)自動(dòng)觸發(fā)onclose方法,重連也執(zhí)行了(onclose方法體綁定了重連事件),如果當(dāng)前一直是斷網(wǎng)狀態(tài),重連會(huì)2秒(時(shí)間是自己代碼設(shè)置的)執(zhí)行一次直到網(wǎng)絡(luò)正常后連接成功。

如此一來(lái),我們判斷前端主動(dòng)斷開ws的心跳檢測(cè)就實(shí)現(xiàn)了。為什么說(shuō)是前端主動(dòng)斷開,因?yàn)楫?dāng)前這種情況主要是通過(guò)前端ws的事件來(lái)判斷的,后面說(shuō)后端主動(dòng)斷開的情況。

我本想測(cè)試websocket超時(shí)時(shí)間,又發(fā)現(xiàn)了一些新的問(wèn)題

  1. 在chrome中,如果心跳檢測(cè) 也就是websocket實(shí)例執(zhí)行send之后,15秒內(nèi)沒(méi)發(fā)送到另一接收端,onclose便會(huì)執(zhí)行。那么超時(shí)時(shí)間是15秒。
  2. 我又打開了Firefox ,F(xiàn)irefox在斷網(wǎng)7秒之后,直接執(zhí)行onclose。說(shuō)明在Firefox中不需要心跳檢測(cè)便能自動(dòng)onclose。
  3. 同一代碼, reconnect方法 在chrome 執(zhí)行了一次,F(xiàn)irefox執(zhí)行了兩次。當(dāng)然我們?cè)趲滋幍胤剑ùa邏輯處和websocket事件處)綁定了reconnect()

所以保險(xiǎn)起見,我們還是給reconnect()方法加上一個(gè)鎖,保證只執(zhí)行一次

目前來(lái)看不同的瀏覽器,有不同的機(jī)制,無(wú)論瀏覽器websocket自身會(huì)不會(huì)在斷網(wǎng)情況下執(zhí)行onclose,加上心跳重連后,已經(jīng)能保證onclose的正常觸發(fā)。

判斷后端斷開:

如果后端因?yàn)橐恍┣闆r斷開了ws,是可控情況下的話,會(huì)下發(fā)一個(gè)斷連的消息通知,之后才會(huì)斷開,我們便會(huì)重連。
如果因?yàn)橐恍┊惓嚅_了連接,我們是不會(huì)感應(yīng)到的,所以如果我們發(fā)送了心跳一定時(shí)間之后,后端既沒(méi)有返回心跳響應(yīng)消息,前端又沒(méi)有收到任何其他消息的話,我們就能斷定后端主動(dòng)斷開了。
一點(diǎn)特別重要的發(fā)送心跳到后端,后端收到消息之后必須返回消息,否則超過(guò)60秒之后會(huì)判定后端主動(dòng)斷開了。再改造下代碼:

var heartCheck = {
    timeout: 60000,//60ms
    timeoutObj: null,
    serverTimeoutObj: null,
    reset: function(){
        clearTimeout(this.timeoutObj);
        clearTimeout(this.serverTimeoutObj);
     this.start();
    },
    start: function(){
        var self = this;
        this.timeoutObj = setTimeout(function(){
            ws.send("HeartBeat");
            self.serverTimeoutObj = setTimeout(function(){
                ws.close();//如果onclose會(huì)執(zhí)行reconnect,我們執(zhí)行ws.close()就行了.如果直接執(zhí)行reconnect 會(huì)觸發(fā)onclose導(dǎo)致重連兩次
            }, self.timeout)
        }, this.timeout)
    },
}

ws.onopen = function () {
   heartCheck.start();
};
ws.onmessage = function (event) {
    heartCheck.reset();
}
ws.onclose = function () {
    reconnect();
};
ws.onerror = function () {
    reconnect();
};

因?yàn)槟壳拔覀冞@種方式會(huì)一直重連如果沒(méi)連接上或者斷連的話,如果有兩個(gè)設(shè)備同時(shí)登陸并且會(huì)踢另一端下線,一定要發(fā)送一個(gè)踢下線的消息類型,這邊接收到這種類型的消息,邏輯判斷后就不再執(zhí)行reconnect,否則會(huì)出現(xiàn)一只相互擠下線的死循環(huán)。

整理了一個(gè)簡(jiǎn)單的demo到github,點(diǎn)擊查看https://github.com/iaiai/websocket

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評(píng)論 25 709
  • 帶來(lái)兩大好處:1:Header互相溝通的Header是很小的,大概只有2 Bytes2:Server Push服務(wù)...
    liuboxx1閱讀 548評(píng)論 0 2
  • 早上好,大榕樹 昨夜可看見——無(wú)邊黑暗中 一只破舊的老電燈 在風(fēng)雨交加里搖曳 燈光下...
    karller閱讀 545評(píng)論 0 0
  • 我們是一家互聯(lián)網(wǎng)公司,前兩天一位同事說(shuō)要離職,驚呼平時(shí)好好地沒(méi)有任何跡象,突然就說(shuō)要離職,于是乎把這位同學(xué)拉到會(huì)議...
    warston閱讀 2,040評(píng)論 2 4
  • 敦煌賦 金漠翡翠,絲路明珠。五千年朗月鑒,三省地龍脈結(jié)。枕祁連之雄偉,帶黨河之雋秀。泉映月牙盈盈,風(fēng)攜羌笛悠悠。鳴...
    明白的石頭閱讀 697評(píng)論 0 1

友情鏈接更多精彩內(nèi)容