初探和實現websocket心跳重連(npm: websocket-heartbeat-js)

提示:文章最下方有倉庫地址前端

 

心跳重連原因git

 

websocket是先後端交互的長鏈接,先後端也均可能由於一些狀況致使鏈接失效而且相互之間沒有反饋提醒。所以爲了保證鏈接的可持續性和穩定性,websocket心跳重連就應運而生。github

在使用原生websocket的時候,若是設備網絡斷開,不會觸發websocket的任何事件函數,前端程序沒法得知當前鏈接已經斷開。這個時候若是調用websocket.send方法,瀏覽器就會發現消息發不出去,便會馬上或者必定短期後(不一樣瀏覽器或者瀏覽器版本可能表現不一樣)觸發onclose函數。web

後端websocket服務也可能出現異常,鏈接斷開後前端也並無收到通知,所以須要前端定時發送心跳消息ping,後端收到ping類型的消息,立馬返回pong消息,告知前端鏈接正常。若是必定時間沒收到pong消息,就說明鏈接不正常,前端便會執行重連。chrome

爲了解決以上兩個問題,之前端做爲主動方,定時發送ping消息,用於檢測網絡和先後端鏈接問題。一旦發現異常,前端持續執行重連邏輯,直到重連成功。npm

 

 

如何實現後端

在websocket實例化的時候,咱們會綁定一些事件:瀏覽器

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

若是但願websocket鏈接一直保持,咱們會在close或者error上綁定從新鏈接方法。websocket

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

這樣通常正常狀況下失去鏈接時,觸發onclose方法,咱們就能執行重連了。網絡

 

那麼針對斷網的狀況的心跳重連,怎麼實現呢。

簡單的實現:

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方法主要用來控制心跳的定時。

什麼條件下執行心跳:

當onopen也就是鏈接上時,咱們便開始start計時,若是在定時時間範圍內,onmessage獲取到了後端的消息,咱們就重置倒計時,

距離上次從後端獲取到消息超過60秒以後,執行心跳檢測,看是否是斷連了,這個檢測時間能夠本身根據自身狀況設定。

判斷前端ws斷開(斷網但不限於斷網的狀況):

小心跳檢測send方法執行以後,若是當前websocket是斷開狀態(或者說斷網了),發送超時以後,瀏覽器的ws會自動觸發onclose方法,重連也執行了(onclose方法體綁定了重連事件),若是當前一直是斷網狀態,重連會2秒(時間是本身代碼設置的)執行一次直到網絡正常後鏈接成功。

如此一來,咱們判斷前端主動斷開ws的心跳檢測就實現了。爲何說是前端主動斷開,由於當前這種狀況主要是經過前端ws的事件來判斷的,後面說後端主動斷開的狀況。

 

我本想測試websocket超時時間,又發現了一些新的問題

1. 在chrome中,若是心跳檢測 也就是websocket實例執行send以後,15秒內沒發送到另外一接收端,onclose便會執行。那麼超時時間是15秒。

2. 我又打開了Firefox ,Firefox在斷網7秒以後,直接執行onclose。說明在Firefox中不須要心跳檢測便能自動onclose。

3.  同一代碼, reconnect方法 在chrome 執行了一次,Firefox執行了兩次。固然咱們在幾處地方(代碼邏輯處和websocket事件處)綁定了reconnect(),

因此保險起見,咱們仍是給reconnect()方法加上一個鎖,保證只執行一次

 

目前來看不一樣的瀏覽器,有不一樣的機制,不管瀏覽器websocket自身會不會在斷網狀況下執行onclose,加上心跳重連後,已經能保證onclose的正常觸發。

 

判斷後端斷開:

    若是後端由於一些狀況斷開了ws,是可控狀況下的話,會下發一個斷連的消息通知,以後纔會斷開,咱們便會重連。

若是由於一些異常斷開了鏈接,咱們是不會感應到的,因此若是咱們發送了心跳必定時間以後,後端既沒有返回心跳響應消息,前端又沒有收到任何其餘消息的話,咱們就能判定後端主動斷開了。

一點特別重要的發送心跳到後端,後端收到消息以後必須返回消息,不然超過60秒以後會斷定後端主動斷開了。再改造下代碼:

 

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會執行reconnect,咱們執行ws.close()就好了.若是直接執行reconnect 會觸發onclose致使重連兩次
            }, self.timeout)
        }, this.timeout)
    },
}

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

 

PS:

    由於目前咱們這種方式會一直重連若是沒鏈接上或者斷連的話,若是有兩個設備同時登錄而且會踢另外一端下線,必定要發送一個踢下線的消息類型,這邊接收到這種類型的消息,邏輯判斷後就再也不執行reconnect,不然會出現一隻相互擠下線的死循環。

 

封裝了一個npm包,歡迎使用

https://github.com/zimv/websocket-heartbeat-js

https://www.npmjs.com/package/websocket-heartbeat-js