心跳重连缘由
websocket
是前后端交互的长连接,前后端也都可能因为一些情况导致连接失效并且相互之间没有反馈提醒。因此为了保证连接的可持续性和稳定性,websocket
心跳重连就应运而生。
在使用原生 websocket
的时候,如果设备网络断开,不会触发 websocket
的任何事件函数,前端程序无法得知当前连接已经断开。这个时候如果调用 websocket.send
方法,浏览器就会发现消息发不出去,便会立刻或者一定短时间后(不同浏览器或者浏览器版本可能表现不同)触发 onclose
函数。
后端 websocket
服务也可能出现异常,连接断开后前端也并没有收到通知,因此需要前端定时发送心跳消息 ping
,后端收到 ping
类型的消息,立马返回 pong
消息,告知前端连接正常。如果一定时间没收到 pong
消息,就说明连接不正常,前端便会执行重连。
为了解决以上两个问题,以前端作为主动方,定时发送 ping
消息,用于检测网络和前后端连接问题。一旦发现异常,前端持续执行重连逻辑,直到重连成功。
如何实现
在 websocket
实例化的时候,我们会绑定一些事件:1
2
3
4
5
6
7
8
9
10
11
12
13
14var ws = new WebSocket(url);
ws.onclose = function () {
//something
};
ws.onerror = function () {
//something
};
ws.onopen = function () {
//something
};
ws.onmessage = function (event) {
//something
}
如果希望 websocket
连接一直保持,我们会在 close
或者 error
上绑定重新连接方法。1
2
3
4
5
6ws.onclose = function () {
reconnect();
};
ws.onerror = function () {
reconnect();
};
这样一般正常情况下失去连接时,触发 onclose
方法,我们就能执行重连了。
那么针对断网的情况的心跳重连,怎么实现呢。
简单的实现:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var 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
在断网7秒之后,直接执行onclose
。说明在Firefox
中不需要心跳检测便能自动onclose
。- 同一代码,
reconnect
方法在chrome
执行了一次,Firefox
执行了两次。当然我们在几处地方(代码逻辑处和websocket
事件处)绑定了reconnect()
,
所以保险起见,我们还是给reconnect()
方法加上一个锁,保证只执行一次
目前来看不同的浏览器,有不同的机制,无论浏览器 websocket
自身会不会在断网情况下执行 onclose
,加上心跳重连后,已经能保证 onclose
的正常触发。
判断后端断开:
如果后端因为一些情况断开了 ws
,是可控情况下的话,会下发一个断连的消息通知,之后才会断开,我们便会重连。
如果因为一些异常断开了连接,我们是不会感应到的,所以如果我们发送了心跳一定时间之后,后端既没有返回心跳响应消息,前端又没有收到任何其他消息的话,我们就能断定后端主动断开了。
一点特别重要的发送心跳到后端,后端收到消息之后必须返回消息,否则超过 60
秒之后会判定后端主动断开了。再改造下代码:
1 | var heartCheck = { |
PS:
因为目前这种方式会一直重连如果没连接上或者断连的话,如果有两个设备同时登陆并且会踢另一端下线,一定要发送一个踢下线的消息类型,这边接收到这种类型的消息,逻辑判断后就不再执行 reconnect
,否则会出现一只相互挤下线的死循环。
此篇博文转自@子暮大诗人,代码已封装为库websocket-heartbeat-js