初探和实现websocket心跳重连(npm: websocket-heartbeat-js)

提示:文章最下方有仓库地址前端

 

心跳重连原因git

 

websocket是先后端交互的长链接,先后端也均可能由于一些状况致使链接失效而且相互之间没有反馈提醒。所以为了保证链接的可持续性和稳定性,websocket心跳重连就应运而生。github

在使用原生websocket的时候,若是设备网络断开,不会马上触发websocket的任何事件,前端也就没法得知当前链接是否已经断开。这个时候若是调用websocket.send方法,浏览器才会发现连接断开了,便会马上或者必定短期后(不一样浏览器或者浏览器版本可能表现不一样)触发onclose函数。web

后端websocket服务也可能出现异常,形成链接断开,这时前端也并无收到断开通知,所以须要前端定时发送心跳消息ping,后端收到ping类型的消息,立马返回pong消息,告知前端链接正常。若是必定时间没收到pong消息,就说明链接不正常,前端便会执行重连。chrome

为了解决以上两个问题,之前端做为主动方,定时发送ping消息,用于检测网络和先后端链接问题。一旦发现异常,前端持续执行重连逻辑,直到重连成功。npm

 

 

如何实现后端

在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方法,咱们就能执行重连了。websocket

 

那么针对断网状况的心跳重连,怎么实现呢,咱们只须要定时的发送消息,去触发websocket.send方法,若是网络断开了,浏览器便会触发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秒以后,执行心跳检测,看是否是断连了,这个检测时间能够本身根据自身状况设定。

 

判断前端websocket断开(断网但不限于断网的状况):

小心跳检测执行send方法以后,若是当前websocket是断开状态(或者说断网了),发送超时以后,浏览器的websocket会自动触发onclose方法,重连就会马上执行(onclose方法体绑定了重连事件),若是当前一直是断网状态,重连会2秒(时间是本身代码设置的)执行一次直到网络正常后链接成功。

如此一来,判断前端断开websocket的心跳检测就实现了。为何说是前端主动断开,由于当前这种状况主要是经过前端websocket.send来检测并触发的onclose,后面说后端断开的状况。

 

我本想测试websocket超时时间,又发现了一些新的问题

1. 在chrome中,若是心跳检测 也就是websocket实例执行send以后,15秒内没发送到另外一接收端,onclose便会执行。那么超时时间是15秒。

2. 我又打开了Firefox ,Firefox在断网7秒以后,直接执行onclose。说明在Firefox中不须要心跳检测便能自动onclose。

3.  同一代码, reconnect方法 在chrome 执行了一次,Firefox执行了两次。固然咱们在几处地方(代码逻辑处和websocket事件处)绑定了reconnect(),

因此保险起见,咱们仍是给reconnect()方法加上一个锁,保证只执行一次

 

目前来看不一样的浏览器,有不一样的机制,不管浏览器websocket自身会不会在断网状况下执行onclose,加上心跳重连后,已经能保证onclose的正常触发。  其实这是因为socket自己就有底层的心跳,socket消息发送不出去的时候,会等待必定时间看是否能在这个时间以内再次链接上,若是超时便会触发onclose。

 

判断后端断开:

    若是后端由于一些状况断开了ws,是可控状况下的话,会下发一个断连的通知,这样会触发前端weboscket的onclose方法,咱们便会重连。

若是由于一些异常断开了链接,前端是不会感应到的,因此若是前端发送了心跳必定时间以后,后端既没有返回心跳响应消息,前端也没有收到任何其余消息的话,咱们就能判定后端发生异常断开了。

一点特别重要的发送心跳到后端,后端收到消息以后必须返回消息,不然超过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,不然会出现一只相互挤下线的死循环。

 

结语

因为断开等缘由可能会致使发送的数据没有发送出去,要保证数据不丢失的话,能够作消息回执,也就是a给b发送消息id=1,b返回收到id=1的消息,若是没有回执a能够再次发送消息id=1。

由上文能够看到,咱们使用了前端发送ping,后端返回pong的这样一种心跳的方式。也有一种方式是后端主动发送心跳,前端判断是否超时。由于ws连接必须是前端主动请求创建链接,所以重连确定是给前端来作,因此判断重连逻辑都是写在前端。

上面所说第二种方式是让服务端发送心跳,前端来接收,这样的方式会多节约一点带宽,由于若是是前端发送心跳,后端须要返回心跳,也就是ping pong的过程会有两次数据传递。  然后端来发送心跳的话,就只须要发送ping,前端不须要回应。可是这样形成了一个问题。前端须要和后端约定好心跳间隔,好比后端设置10秒发送一次心跳,那前端就须要设置一个安全值,好比距离上次收到心跳超过12秒还没收到下一个心跳就重连。这种方式的问题在于调节时间就变得不那么灵活了,须要双方都同时肯定一个时间约定。后端的逻辑也会比较多一点。
而若是前端来发送ping 后端返回pong的话,那么间隔时间就只须要前端本身控制了。加上个人代码把收到的任何后端信息均可以看成是链接正常,从而重置心跳时间,这样也节约了一些请求次数。
使用我这样的方式,后端比较轻松,只须要在 onmessage 写一段代码,大概以下:

if(msg=='heartbeat') socket.send(anything);

 

封装了一个npm包,欢迎使用

https://github.com/zimv/websocket-heartbeat-js

https://www.npmjs.com/package/websocket-heartbeat-js

相关文章
相关标签/搜索