websocket心跳重連實(shí)現(xiàn)探索(npm:websocket-heartbeat-js)
心跳重連緣由
提示:文章最下方有倉(cāng)庫(kù)地址
websocket是前后端交互的長(zhǎng)連接,前后端也都可能因?yàn)橐恍┣闆r導(dǎo)致連接失效并且相互之間沒(méi)有反饋提醒。因此為了保證連接的可持續(xù)性和穩(wěn)定性,websocket心跳重連就應(yīng)運(yùn)而生。
在使用原生websocket的時(shí)候,如果設(shè)備網(wǎng)絡(luò)斷開(kāi),不會(huì)立刻觸發(fā)websocket的任何事件,前端也就無(wú)法得知當(dāng)前連接是否已經(jīng)斷開(kāi)。這個(gè)時(shí)候如果調(diào)用websocket.send方法,瀏覽器才會(huì)發(fā)現(xiàn)鏈接斷開(kāi)了,便會(huì)立刻或者一定短時(shí)間后(不同瀏覽器或者瀏覽器版本可能表現(xiàn)不同)觸發(fā)onclose函數(shù)。
后端websocket服務(wù)也可能出現(xiàn)異常,造成連接斷開(kāi),這時(shí)前端也并沒(méi)有收到斷開(kāi)通知,因此需要前端定時(shí)發(fā)送心跳消息ping,后端收到ping類型的消息,立馬返回pong消息,告知前端連接正常。如果一定時(shí)間沒(méi)收到pong消息,就說(shuō)明連接不正常,前端便會(huì)執(zhí)行重連。
為了解決以上兩個(gè)問(wèn)題,以前端作為主動(dòng)方,定時(shí)發(fā)送ping消息,用于檢測(cè)網(wǎng)絡(luò)和前后端連接問(wèn)題。一旦發(fā)現(xiàn)異常,前端持續(xù)執(zhí)行重連邏輯,直到重連成功。
如何實(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)呢,我們只需要定時(shí)的發(fā)送消息,去觸發(fā)websocket.send方法,如果網(wǎng)絡(luò)斷開(kāi)了,瀏覽器便會(huì)觸發(fā)onclose。
簡(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也就是連接成功后,我們便開(kāi)始start計(jì)時(shí),如果在定時(shí)時(shí)間范圍內(nèi),onmessage獲取到了后端的消息,我們就重置倒計(jì)時(shí),
距離上次從后端獲取到消息超過(guò)60秒之后,執(zhí)行心跳檢測(cè),看是不是斷連了,這個(gè)檢測(cè)時(shí)間可以自己根據(jù)自身情況設(shè)定。
判斷前端websocket斷開(kāi)(斷網(wǎng)但不限于斷網(wǎng)的情況):
當(dāng)心跳檢測(cè)執(zhí)行send方法之后,如果當(dāng)前websocket是斷開(kāi)狀態(tài)(或者說(shuō)斷網(wǎng)了),發(fā)送超時(shí)之后,瀏覽器的websocket會(huì)自動(dòng)觸發(fā)onclose方法,重連就會(huì)立刻執(zhí)行(onclose方法體綁定了重連事件),如果當(dāng)前一直是斷網(wǎng)狀態(tài),重連會(huì)2秒(時(shí)間是自己代碼設(shè)置的)執(zhí)行一次直到網(wǎng)絡(luò)正常后連接成功。
如此一來(lái),判斷前端斷開(kāi)websocket的心跳檢測(cè)就實(shí)現(xiàn)了。為什么說(shuō)是前端主動(dòng)斷開(kāi),因?yàn)楫?dāng)前這種情況主要是通過(guò)前端websocket.send來(lái)檢測(cè)并觸發(fā)的onclose,后面說(shuō)后端斷開(kāi)的情況。
我本想測(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. 我又打開(kāi)了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)起見(jiàn),我們還是給reconnect()方法加上一個(gè)鎖,保證只執(zhí)行一次
目前來(lái)看不同的瀏覽器,有不同的機(jī)制,無(wú)論瀏覽器websocket自身會(huì)不會(huì)在斷網(wǎng)情況下執(zhí)行onclose,加上心跳重連后,已經(jīng)能保證onclose的正常觸發(fā)。 其實(shí)這是由于socket本身就有底層的心跳,socket消息發(fā)送不出去的時(shí)候,會(huì)等待一定時(shí)間看是否能在這個(gè)時(shí)間之內(nèi)再次連接上,如果超時(shí)便會(huì)觸發(fā)onclose。
判斷后端斷開(kāi):
如果后端因?yàn)橐恍┣闆r斷開(kāi)了ws,是可控情況下的話,會(huì)下發(fā)一個(gè)斷連的通知,這樣會(huì)觸發(fā)前端weboscket的onclose方法,我們便會(huì)重連。
如果因?yàn)橐恍┊惓嚅_(kāi)了連接,前端是不會(huì)感應(yīng)到的,所以如果前端發(fā)送了心跳一定時(shí)間之后,后端既沒(méi)有返回心跳響應(yīng)消息,前端也沒(méi)有收到任何其他消息的話,我們就能斷定后端發(fā)生異常斷開(kāi)了。
一點(diǎn)特別重要的發(fā)送心跳到后端,后端收到消息之后必須返回消息,否則超過(guò)60秒之后會(huì)判定后端主動(dòng)斷開(kāi)了。再改造下代碼:
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(); };
PS:
因?yàn)槟壳拔覀冞@種方式會(huì)一直重連如果沒(méi)連接上或者斷連的話,如果有兩個(gè)設(shè)備同時(shí)登陸并且會(huì)踢另一端下線,一定要發(fā)送一個(gè)踢下線的消息類型,這邊接收到這種類型的消息,邏輯判斷后就不再執(zhí)行reconnect,否則會(huì)出現(xiàn)一只相互擠下線的死循環(huán)。
結(jié)語(yǔ)
由于斷開(kāi)等原因可能會(huì)導(dǎo)致發(fā)送的數(shù)據(jù)沒(méi)有發(fā)送出去,要保證數(shù)據(jù)不丟失的話,可以做消息回執(zhí),也就是a給b發(fā)送消息id=1,b返回收到id=1的消息,如果沒(méi)有回執(zhí)a可以再次發(fā)送消息id=1。
由上文可以看到,我們使用了前端發(fā)送ping,后端返回pong的這樣一種心跳的方式。也有一種方式是后端主動(dòng)發(fā)送心跳,前端判斷是否超時(shí)。因?yàn)閣s鏈接必須是前端主動(dòng)請(qǐng)求建立連接,因此重連肯定是給前端來(lái)做,所以判斷重連邏輯都是寫(xiě)在前端。
上面所說(shuō)第二種方式是讓服務(wù)端發(fā)送心跳,前端來(lái)接收,這樣的方式會(huì)多節(jié)約一點(diǎn)帶寬,因?yàn)槿绻乔岸税l(fā)送心跳,后端需要返回心跳,也就是ping pong的過(guò)程會(huì)有兩次數(shù)據(jù)傳遞。 而后端來(lái)發(fā)送心跳的話,就只需要發(fā)送ping,前端不需要回應(yīng)。但是這樣造成了一個(gè)問(wèn)題。前端需要和后端約定好心跳間隔,比如后端設(shè)置10秒發(fā)送一次心跳,那前端就需要設(shè)置一個(gè)安全值,比如距離上次收到心跳超過(guò)12秒還沒(méi)收到下一個(gè)心跳就重連。這種方式的問(wèn)題在于調(diào)節(jié)時(shí)間就變得不那么靈活了,需要雙方都同時(shí)確定一個(gè)時(shí)間約定。后端的邏輯也會(huì)比較多一點(diǎn)。
而如果前端來(lái)發(fā)送ping 后端返回pong的話,那么間隔時(shí)間就只需要前端自己控制了。加上我的代碼把收到的任何后端信息都可以當(dāng)作是連接正常,從而重置心跳時(shí)間,這樣也節(jié)約了一些請(qǐng)求次數(shù)。
使用我這樣的方式,后端比較輕松,只需要在 onmessage 寫(xiě)一段代碼,大概如下:
if(msg=='heartbeat') socket.send(anything);
封裝了一個(gè)npm包,歡迎使用
https://github.com/zimv/websocket-heartbeat-js
https://www.npmjs.com/package/websocket-heartbeat-js
以上就是websocket心跳重連實(shí)現(xiàn)探索(npm:websocket-heartbeat-js)的詳細(xì)內(nèi)容,更多關(guān)于websocket心跳重連的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Redux 和 Mobx的選擇問(wèn)題:讓你不再困惑!
Redux 和 Mobx 都是當(dāng)下比較火熱的數(shù)據(jù)流模型,本文就開(kāi)發(fā)者在面臨二者的選擇問(wèn)題時(shí),應(yīng)該如何選擇作了詳細(xì)介紹,需要的朋友可以參考下。2017-09-09javascript進(jìn)階篇深拷貝實(shí)現(xiàn)的四種方式
這篇文章主要為大家介紹了javascript進(jìn)階篇深拷貝實(shí)現(xiàn)的四種方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-0713個(gè)JavaScript 一行程序,讓你看起來(lái)就是個(gè)專家
JavaScript 可以做很多好玩的事, 從復(fù)雜的框架到處理API,有太多的東西需要學(xué)習(xí)。但是,它也能讓我們只用一行就能做一些了不起的事情。今天的文章小編就為大家介紹13 個(gè)JavaScript 行程序,需要的朋友可以參考下2021-08-08JS精髓原型鏈繼承及構(gòu)造函數(shù)繼承問(wèn)題糾正
這篇文章主要為大家介紹了JS精髓原型鏈繼承及構(gòu)造函數(shù)繼承問(wèn)題糾正,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06js解決移動(dòng)端滾動(dòng)穿透問(wèn)題方案詳解
這篇文章主要為大家介紹了js解決移動(dòng)端滾動(dòng)穿透問(wèn)題方案詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07javascript字符串單個(gè)字符訪問(wèn)方法
這篇文章主要為大家介紹了javascript字符串單個(gè)字符訪問(wèn)方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07