【轉載】websocket斷開重連解決方案初探和實現websocket心跳重連
https://blog.svenhetin.com/chu-tan-he-shi-xian-websocketxin-tiao-zhong-lian/
https://www.crifan.com/websocket_ping_pong_best_interval_time/
https://www.oschina.net/question/137225_27929
心跳重連緣由
websocket是前后端交互的長連接,前后端也都可能因為一些情況導致連接失效并且相互之間沒有反饋提醒。因此為了保證連接的可持續性和穩定性,websocket心跳重連就應運而生。
在使用原生websocket的時候,如果設備網絡斷開,不會觸發websocket的任何事件函數,前端程序無法得知當前連接已經斷開。這個時候如果調用websocket.send方法,瀏覽器就會發現消息發不出去,便會立刻或者一定短時間后(不同瀏覽器或者瀏覽器版本可能表現不同)觸發onclose函數。
后端websocket服務也可能出現異常,連接斷開后前端也并沒有收到通知,因此需要前端定時發送心跳消息ping,后端收到ping類型的消息,立馬返回pong消息,告知前端連接正常。如果一定時間沒收到pong消息,就說明連接不正常,前端便會執行重連。
為了解決以上兩個問題,以前端作為主動方,定時發送ping消息,用于檢測網絡和前后端連接問題。一旦發現異常,前端持續執行重連邏輯,直到重連成功。
如何實現
在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,//60s 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,//60s 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包,歡迎使用
Altaba強化版
相信隨著H5的演進,我們越來越多接觸到websocket的使用,本身就使用此技術并不難,但是在開發中會遇到各種無法預測的原因,有瀏覽器兼容問題,有后臺的意外斷開,狀態百出。
本人前端開發遇到這樣的問題:websocket部分使用了nginx服務,默認配置是60s,就是60s,如果一直沒有數據傳輸,連接會在過了這個時間之后自動關閉。
解決:nginx配置改為600s,前端在onclose 函數體加入判斷 然后重連
//2017年11月27日 修改websocket連接 var ws;//websocket實例 var lockReconnect = false;//避免重復連接 var wsUrl = "ws://"+'xxxxxxx'; function createWebSocket(url) { try { if ('WebSocket' in window) { ws = new WebSocket(url); } else if ('MozWebSocket' in window) { ws = new MozWebSocket(url); } else { url = "http://" + 'xxxxxxx'; ws = new SockJS(url); } initEventHandle(); } catch (e) { reconnect(url); } } function initEventHandle() { ws.onclose = function (evnt) { //console.log('websocket服務關閉了'); reconnect(wsUrl); }; ws.onerror = function (evnt) { //console.log('websocket服務出錯了'); reconnect(wsUrl); }; ws.onopen = function (evnt) { //心跳檢測重置 heartCheck.reset().start(); }; ws.onmessage = function (evnt) { //如果獲取到消息,心跳檢測重置 //拿到任何消息都說明當前連接是正常的 //console.log('websocket服務獲得數據了'); //接受消息后的UI變化 doWithMsg(evnt.data); heartCheck.reset().start(); } //收到消息推送 function doWithMsg(msg) { //...... } } function reconnect(url) { if(lockReconnect) return; lockReconnect = true; //沒連接上會一直重連,設置延遲避免請求過多 setTimeout(function () { createWebSocket(url); lockReconnect = false; }, 2000); } //心跳檢測 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(){ //這里發送一個心跳,后端收到后,返回一個心跳消息, //onmessage拿到返回的心跳就說明連接正常 ws.send("HeartBeat"); self.serverTimeoutObj = setTimeout(function(){//如果超過一定時間還沒重置,說明后端主動斷開了 ws.close();//如果onclose會執行reconnect,我們執行ws.close()就行了.如果直接執行reconnect 會觸發onclose導致重連兩次 }, self.timeout) }, this.timeout) } } //初始化websocket createWebSocket(wsUrl);

浙公網安備 33010602011771號