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

心跳重连缘由

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

那么针对断网情况的心跳重连,怎么实现呢,我们只需要定时的发送消息,去触发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

初探和实现websocket心跳重连的更多相关文章

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

    提示:文章最下方有仓库地址 心跳重连缘由 websocket是前后端交互的长连接,前后端也都可能因为一些情况导致连接失效并且相互之间没有反馈提醒.因此为了保证连接的可持续性和稳定性,websocket ...

  2. websocket心跳重连 websocket-heartbeat-js

    初探和实现websocket心跳重连(npm: websocket-heartbeat-js) 心跳重连缘由 websocket是前后端交互的长连接,前后端也都可能因为一些情况导致连接失效并且相互之间 ...

  3. websocket 心跳重连

    websocket 的基本使用: var ws = new WebSocket(url); ws.onclose = function () { //something reconnect(); // ...

  4. 161114、websocket实现心跳重连

    心跳重连缘由 在使用websocket过程中,可能会出现网络断开的情况,比如信号不好,或者网络临时性关闭,这时候websocket的连接已经断开, 而浏览器不会执行websocket 的 onclos ...

  5. WebSocket心跳检测和重连机制

    1. 心跳重连原由 心跳和重连的目的用一句话概括就是客户端和服务端保证彼此还活着,避免丢包发生. websocket连接断开有以下两证情况: 前端断开 在使用websocket过程中,可能会出现网络断 ...

  6. 理解WebSocket心跳及重连机制(五)

    理解WebSocket心跳及重连机制 在使用websocket的过程中,有时候会遇到网络断开的情况,但是在网络断开的时候服务器端并没有触发onclose的事件.这样会有:服务器会继续向客户端发送多余的 ...

  7. 【简记】前端对接WebSocket与心跳重连

    前言 最近又在忙着开发别的模块,其中包含了即时通讯这一块,上一次做即时通讯时还是去年年底,一时间代码都在自己的笔记本里,还没带--这里就记录一下前端对接WebSocket的实现,包含心跳重连,简记之. ...

  8. 小程序websocket心跳库——websocket-heartbeat-miniprogram

    前言 在16年的时候因为项目接触到websocket,而后对心跳重连做了一次总结,写了篇博客,而后18年对之前github上的demo代码进行了再次开发和开源,最终封装成库.如下: 博客:https: ...

  9. ws & websocket & 掉线重连

    ws & websocket & 掉线重连 reconnecting websocket https://github.com/joewalnes/reconnecting-webso ...

随机推荐

  1. 电容参数:X5R,X7R,Y5V,COG 详解

    电容参数:X5R,X7R,Y5V,COG 详解 文章来源:http://www.hzlitai.com.cn/article/ARM9-article/cphard/1777.html 仅供分享学习~ ...

  2. 理解MVC,MVP和MVVM设计模式

    有3个非常受欢迎的MV-*系列设计模式:MVC,MVP,MVVM.他们被广泛应用于不多种结束.这篇文章我回阐述我自己对这3个设计模式的看法. MVC模式: MVC即Model-VIew-Control ...

  3. ntko office在线编辑控件问题记录

    ntko office在线预览插件 http://www.ntko.com/ 问题:火狐或谷歌下保存报[没有打开的文档]错误,ie正常 原因:火狐.谷歌.ie的各方法执行文字不同,ie嵌在页面,而火狐 ...

  4. Nginx模块参考手册:HTTP核心模块

    FROM: http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=17238776&id=2982697 这些模块默认会全部编 ...

  5. Eclipse中自动提示的方法参数都是arg0,arg1的解决方法

    Eclipse中自动提示的方法参数都是arg0,arg1,就不能根据参数名来推断参数的含义,非常不方便. 解决方法:Preferences->Java->Installed JREs,发现 ...

  6. ORA-01861: 文字与格式字符串不匹配

    问题:插入数据不成功 解决:借口实现类里面的sql语句带值放到数据库中运行,如果不成功是sql语句的错误.

  7. iOS并发编程笔记【转】

    线程 使用Instruments的CPU strategy view查看代码如何在多核CPU中执行.创建线程可以使用POSIX 线程API,或者NSThread(封装POSIX 线程API).下面是并 ...

  8. docfx组件介绍--MarkdownLite

    在docfx中,最重要的就是生成文档,把markdown文件(gfm语法)转换成html. 为了这一步,我们找了N个.net开源项目,发现要么是common markdown的,要么不容易扩展,没有一 ...

  9. Java NIO6:选择器2---代码篇

    选择器服务器端代码 上一篇文章毫无条理地讲了很多和选择器相关的知识点,下面进入实战,看一下如何写和使用选择器实现服务端Socket数据接收的程序,这也是NIO中最核心.最精华的部分. 看一下代码: p ...

  10. mysql定义和调用存储过程

    /*定义delimiter为 // */ delimiter // CREATE procedure sp_add3(a int, b int,out c int) begin set c=a+ b; ...