初探和實現websocket心跳重連
初探和實現websocket心跳重連
心跳重連緣由
在使用websocket過程中,可能會出現網絡斷開的情況,比如信號不好,或者網絡臨時性關閉,這時候websocket的連接已經斷開,
而瀏覽器不會執行websocket 的 onclose方法,我們無法知道是否斷開連接,也就無法進行重連操作。
如果當前發送websocket數據到後端,一旦請求超時,onclose便會執行,這時候便可進行綁定好的重連操作。
因此websocket心跳重連就應運而生。
如何實現
在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上綁定重新連接方法。
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超時時間,又發現了一些新的問題
- 在chrome中,如果心跳檢測 也就是websocket實例執行send之後,15秒內沒發送到另一接收端,onclose便會執行。那麽超時時間是15秒。
- 我又打開了Firefox ,Firefox在斷網7秒之後,直接執行onclose。說明在Firefox中不需要心跳檢測便能自動onclose。
- 同一代碼, 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,否則會出現一只相互擠下線的死循環。
整理了一個簡單的demo到github,點擊查看https://github.com/zimv/WebSocketHeartBeat
初探和實現websocket心跳重連