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

心跳重连缘由

websocket是前后端交互的长连接,前后端也都可能因为一些情况导致连接失效并且相互之间没有反馈提醒。因此为了保证连接的可持续性和稳定性,websocket心跳重连就应运而生。

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

后端websocket服务也可能出现异常,造成连接断开,这时前端也并没有收到断开通知,因此需要前端定时发送心跳消息ping,后端收到ping类型的消息,立马返回pong消息,告知前端连接正常。如果一定时间没收到pong消息,就说明连接不正常,前端便会执行重连。

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

如何实现

在websocket实例化的时候,我们会绑定一些事件:

  1. var ws = new WebSocket(url);
  2. ws.onclose = function () {
  3. //something
  4. };
  5. ws.onerror = function () {
  6. //something
  7. };
  8.  
  9. ws.onopen = function () {
  10. //something
  11. };
  12. ws.onmessage = function (event) {
  13. //something
  14. }

如果希望websocket连接一直保持,我们会在close或者error上绑定重新连接方法。

  1. ws.onclose = function () {
  2. reconnect();
  3. };
  4. ws.onerror = function () {
  5. reconnect();
  6. };

这样一般正常情况下失去连接时,触发onclose方法,我们就能执行重连了。

那么针对断网情况的心跳重连,怎么实现呢,我们只需要定时的发送消息,去触发websocket.send方法,如果网络断开了,浏览器便会触发onclose。

简单的实现:

  1. var heartCheck = {
  2. timeout: 60000,//60ms
  3. timeoutObj: null,
  4. reset: function(){
  5. clearTimeout(this.timeoutObj);
         this.start();
  6. },
  7. start: function(){
  8. this.timeoutObj = setTimeout(function(){
  9. ws.send("HeartBeat");
  10. }, this.timeout)
  11. }
  12. }
  13.  
  14. ws.onopen = function () {
  15. heartCheck.start();
  16. };
  1. ws.onmessage = function (event) {
  2. heartCheck.reset();
  3. }

如上代码,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秒之后会判定后端主动断开了。再改造下代码:

  1. var heartCheck = {
  2. timeout: 60000,//60ms
  3. timeoutObj: null,
  4. serverTimeoutObj: null,
  5. reset: function(){
  6. clearTimeout(this.timeoutObj);
  7. clearTimeout(this.serverTimeoutObj);
  8.      this.start();
  9. },
  10. start: function(){
  11. var self = this;
  12. this.timeoutObj = setTimeout(function(){
  13. ws.send("HeartBeat");
  14. self.serverTimeoutObj = setTimeout(function(){
  15. ws.close();//如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次
  16. }, self.timeout)
  17. }, this.timeout)
  18. },
  19. }
  20.  
  21. ws.onopen = function () {
  22. heartCheck.start();
  23. };
  24. ws.onmessage = function (event) {
  25. heartCheck.reset();
  26. }
  1. ws.onclose = function () {
  2. reconnect();
  3. };
  4. ws.onerror = function () {
  5. reconnect();
  6. };
  1.  

PS:

因为目前我们这种方式会一直重连如果没连接上或者断连的话,如果有两个设备同时登陆并且会踢另一端下线,一定要发送一个踢下线的消息类型,这边接收到这种类型的消息,逻辑判断后就不再执行reconnect,否则会出现一只相互挤下线的死循环。

结语

由于断开等原因可能会导致发送的数据没有发送出去,要保证数据不丢失的话,可以做消息回执,也就是a给b发送消息id=1,b返回收到id=1的消息,如果没有回执a可以再次发送消息id=1。

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

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

  1. 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. C++哈夫曼树编码和译码的实现

    一.背景介绍: 给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman Tree).哈夫曼树是带权路径长度最短的树,权值较大的 ...

  2. mysql 连接慢的问题(超过了1秒)

    http://www.cnblogs.com/isenhome/p/5133547.html 症状描述 本机连接mysql速度很快 远程ping mysql主机速度正常 远程连接mysql速度需要等待 ...

  3. 用微信小程序开发的Canvas绘制可配置的转盘抽奖

    使用https://github.com/givebest/GB-canvas-turntable代码移植过而来. 其它 微信小程序感觉是个半成品,代码移植过程比较繁琐麻烦.canvas API 部分 ...

  4. Myeclipse中web project各种常见错误及解决方法(持续更新)

    创建web project时的问题 error:Install Dynamic web Module Facet卡住 solution:把网络关掉再创建就可以 Servlet error:The se ...

  5. PHP基础知识之遍历

    遍历对象的时候,默认遍历对象的所有属性 class MyClass{    public $var1 = 'value 1';    public $var2 = 'value 2';    publ ...

  6. PHP基础知识之类

    类中的方法访问方式: class A{    function foo()    {            }} 1.A::foo(); 2.$a = new A(); $a->foo(); 3 ...

  7. You are attempting to run the 32-bit installer on a 64-bit version of Window

    您正试图在64位版本的窗口中运行32位安装程序. 系统有32位操作系统和64位操作系统的分别,相同的软件的安装也需要区分操作操作系统的位数. 解决办法:查看自己系统类型,根据类型下载安装相应位数的软件 ...

  8. 使用 MimeKit 和 MailKit 发送邮件

    MimeKit 给.NET 社区带来了一流的 MIME 解析器,它能处理复杂的各种Mime, 性能好.而且开箱即用支持 S/MIME 和 PGP.MimeKit 和 MailKit 支持最新的国际化的 ...

  9. UI控件(UISegmentedControl)

    @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSArray* segmentArray = [[ ...

  10. 人生苦短,我用python——当我在玩python的时候我玩些什么

    文章背景 家里的第一台电脑是在2006年夏天买的,10年上大学之后基本上就没人用,过没两年就当二手卖给一个熟人. 弟弟小我10岁,今年刚上初一.他在我毕业前半年就整天用妈妈的手机发短信给我,问我什么时 ...