心跳重連緣由
在使用websocket過程中,可能會出現(xiàn)網(wǎng)絡(luò)斷開的情況昌犹,比如信號不好,或者網(wǎng)絡(luò)臨時性關(guān)閉缚忧,這時候websocket的連接已經(jīng)斷開闪水,
而瀏覽器不會執(zhí)行websocket 的 onclose方法,我們無法知道是否斷開連接持钉,也就無法進行重連操作州刽。
如果當(dāng)前發(fā)送websocket數(shù)據(jù)到后端,一旦請求超時门坷,onclose便會執(zhí)行,這時候便可進行綁定好的重連操作框冀。
因此websocket心跳重連就應(yīng)運而生流椒。
如何實現(xiàn)
在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();
};
這樣一般正常情況下失去連接時,觸發(fā)onclose方法温数,我們就能執(zhí)行重連了绣硝。
那么針對斷網(wǎng)的情況的心跳重連,怎么實現(xiàn)呢撑刺。
簡單的實現(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方法主要用來控制心跳的定時。
什么條件下執(zhí)行心跳:
當(dāng)onopen也就是連接上時,我們便開始start計時甫菠,如果在定時時間范圍內(nèi)挠铲,onmessage獲取到了后端的消息,我們就重置倒計時寂诱,距離上次從后端獲取到消息超過60秒之后拂苹,執(zhí)行心跳檢測,看是不是斷連了痰洒,這個檢測時間可以自己根據(jù)自身情況設(shè)定瓢棒。
判斷前端ws斷開(斷網(wǎng)但不限于斷網(wǎng)的情況):
當(dāng)心跳檢測send方法執(zhí)行之后,如果當(dāng)前websocket是斷開狀態(tài)(或者說斷網(wǎng)了)丘喻,發(fā)送超時之后脯宿,瀏覽器的ws會自動觸發(fā)onclose方法,重連也執(zhí)行了(onclose方法體綁定了重連事件)仓犬,如果當(dāng)前一直是斷網(wǎng)狀態(tài)嗅绰,重連會2秒(時間是自己代碼設(shè)置的)執(zhí)行一次直到網(wǎng)絡(luò)正常后連接成功。
如此一來搀继,我們判斷前端主動斷開ws的心跳檢測就實現(xiàn)了窘面。為什么說是前端主動斷開,因為當(dāng)前這種情況主要是通過前端ws的事件來判斷的叽躯,后面說后端主動斷開的情況财边。
我本想測試websocket超時時間,又發(fā)現(xiàn)了一些新的問題
- 在chrome中点骑,如果心跳檢測 也就是websocket實例執(zhí)行send之后酣难,15秒內(nèi)沒發(fā)送到另一接收端,onclose便會執(zhí)行黑滴。那么超時時間是15秒憨募。
- 我又打開了Firefox ,F(xiàn)irefox在斷網(wǎng)7秒之后袁辈,直接執(zhí)行onclose菜谣。說明在Firefox中不需要心跳檢測便能自動onclose。
- 同一代碼晚缩, reconnect方法 在chrome 執(zhí)行了一次尾膊,F(xiàn)irefox執(zhí)行了兩次。當(dāng)然我們在幾處地方(代碼邏輯處和websocket事件處)綁定了reconnect()
所以保險起見荞彼,我們還是給reconnect()方法加上一個鎖冈敛,保證只執(zhí)行一次
目前來看不同的瀏覽器,有不同的機制鸣皂,無論瀏覽器websocket自身會不會在斷網(wǎng)情況下執(zhí)行onclose抓谴,加上心跳重連后暮蹂,已經(jīng)能保證onclose的正常觸發(fā)。
判斷后端斷開:
如果后端因為一些情況斷開了ws齐邦,是可控情況下的話椎侠,會下發(fā)一個斷連的消息通知,之后才會斷開措拇,我們便會重連我纪。
如果因為一些異常斷開了連接,我們是不會感應(yīng)到的丐吓,所以如果我們發(fā)送了心跳一定時間之后浅悉,后端既沒有返回心跳響應(yīng)消息,前端又沒有收到任何其他消息的話券犁,我們就能斷定后端主動斷開了术健。
一點特別重要的發(fā)送心跳到后端,后端收到消息之后必須返回消息粘衬,否則超過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會執(zhí)行reconnect,我們執(zhí)行ws.close()就行了.如果直接執(zhí)行reconnect 會觸發(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();
};
因為目前我們這種方式會一直重連如果沒連接上或者斷連的話稚新,如果有兩個設(shè)備同時登陸并且會踢另一端下線勘伺,一定要發(fā)送一個踢下線的消息類型,這邊接收到這種類型的消息褂删,邏輯判斷后就不再執(zhí)行reconnect飞醉,否則會出現(xiàn)一只相互擠下線的死循環(huán)。
整理了一個簡單的demo到github屯阀,點擊查看https://github.com/iaiai/websocket