请选择 进入手机版 | 继续访问电脑版

默认
打赏 发表评论 12
想开发IM:买成品怕坑?租第3方怕贵?找开源自已撸?尽量别走弯路了... 找站长给点建议
Web端即时通讯实践干货:如何让WebSocket断网重连更快速?
微信扫一扫关注!

本文由作者网易智慧企业web前端开发工程师马莹莹,发布于公众号“网易智慧企业技术+”,原题“细说websocket快速重连机制”(链接:mp.weixin.qq.com/s/fMXv7hYobUeZzth-ehRNAw)。
为了提升内容质量,即时通讯网收录时有修订和改动。


1、引言


在一个完善的即时通讯IM应用中,WebSocket是极其关键的一环,它为基于Web的即时通讯应用提供了一种全双工的通信机制。但为了提升IM等实际应用场景下的消息即时性和可靠性,我们需要克服WebSocket及其底层依赖的TCP连接对于复杂网络情况下的不稳定性,即时通讯的开发者们通常都需要为其设计一套完整的连接保活、验活以及断片网重连方案。

就断网重连而言,其重连响应速度将严重影响了上层应用的“即时性”和用户体验。试想打开网络一分钟后,微信的网络不能即时感知到socket连接的恢复,无法即时收发聊天消息的话,是不是很崩溃?

因此,如何在复杂网络场景下,更即时快速地感知网络变动,并快速恢复WebSocket的可用性,就变得尤为重要。本文将基于笔者的开发实践,分享WebSocket在不同状态下、不同的网络场景下,应该如何实现快速断网重连

cover-2.jpg

* 阅读对象:本文适合有过IM底层网络实际开发经验,或者对底层网络实现有较深了解的开发者阅读。如果对底层网络了解甚少,建议跳过本文,直接阅读网络本文末尾附录部分的基础后再回头来看。

* 站长点评:本文内容没有高大上,但比较干货,实用性较高,内容也很通俗,建议可详细阅读。文中虽讲的是WebSocket,但思想可以延伸应用到基于TCP协议的同类技术中。

本文已同步发布于“即时通讯技术圈”公众号,欢迎关注:
52im_qr_即时通讯技术圈_400px.png
▲ 本文在公众号上的链接是:点此进入

2、预备知识


本文中将要分享的内容是基于实践总结,如果你对Web端的即时通讯知识还一头雾水,务必先读:《新手入门贴:史上最全Web端即时通讯技术原理详解》、《Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE》。

限于篇幅,本文不会深究WebSocket技术细节,如有兴趣请系统学习:


3、快速了解WebSocket


Websocket诞生于2008年,在2011年成为国际标准,现在所有的浏览器都已支持(详见《新手快速入门:WebSocket简明教程)。它是一种全新的应用层协议,是专门为web客户端和服务端设计的真正的全双工通信协议,可以类比HTTP协议来了解websocket协议。

OIP.jpg
图片引用自《WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)

它们的不同点:

  • 1)HTTP的协议标识符是http,WebSocket的是ws;
  • 2)HTTP请求只能由客户端发起,服务器无法主动向客户端推送消息,而WebSocket可以;
  • 3)HTTP请求有同源限制,不同源之间通信需要跨域,而WebSocket没有同源限制。

它们的相同点:

  • 1)都是应用层的通信协议;
  • 2)默认端口一样,都是80或443;
  • 3)都可以用于浏览器和服务器间的通信;
  • 4)都基于TCP协议。

两者和TCP的关系图:
x1.jpg
图片引用自《新手快速入门:WebSocket简明教程

有关Http和WebSocket的关系,可以详读:


有关WebSocket和Socket的关系,可以详读:WebSocket详解(六):刨根问底WebSocket与Socket的关系》.

4、WebSocket重连过程拆解


首先考虑一个问题,何时需要重连?

最容易想到的是WebSocket连接断了,为了接下来能收发消息,我们需要再发起一次连接。

但在很多场景下,即便WebSocket连接没有断开,实际上也不可用了。

比如以下场景:

  • 1)设备切换网络;
  • 2)链路中间路由崩溃(常识是一条socket连接对应的网络通路上,会存在很多路由设备);
  • 3)链路的前端出口不可用(比如家庭WiFi中,网络连接正常,但实际运营商的宽带已经欠费被停机);
  • 4)服务器负载持续过高无法响应等。

这些场景下的WebSocket都没有断开,但对上层来说,都没办法正常的收发数据了。

因此在重连前,我们需要一种机制来感知连接是否可用、服务是否可用,而且要能快速感知,以便能够快速从不可用状态中恢复。

一旦感知到了连接不可用,那便可以弃旧图新了,弃用并断开旧连接,然后发起一次新连接。这两个步骤看似简单,但若想达到快,且不是那么容易的。

首先:是断开旧连接,对客户端来说,如何快速断开?协议规定客户端必须要和服务器协商后才能断开WebSocket连接,但是当客户端已经联系不上服务器、无法协商时,如何断开并快速恢复?

其次:是快速发起新连接。此快非彼快,这里的快并非是立即发起连接,立即发起连接会对服务器带来不可预估的影响。重连时通常会采用一些退避算法,延迟一段时间后再发起重连。但如何在重连间隔和性能消耗间做出权衡?如何在“恰当的时间点”快速发起连接?

带着这些疑问,我们来细看下这三个过程:
2.jpg

5、快速重连关键1:快速感知何时需要重连


5.1场景


需要重连的场景可以细分为三种:

  • 1)连接明确断开了;
  • 2)连接没断但是不可用了;
  • 3)连接对端的服务不可用了。

对于第一种场景:这很简单,连接直接断开了,肯定需要重连了。

对于后两者:无论是连接不可用,还是服务不可用,对上层应用的影响都是不能再收发即时消息了。

5.2心跳包主动探测网络可用性


所以从上面这个角度出发,感知何时需要重连的一种简单粗暴的方法就是通过心跳包超时:发送一个心跳包,如果超过特定的时间后还没有收到服务器回包,则认为服务不可用,如下图中左侧的方案(这种方法最直接)。

3.jpg

那如果想要快速感知呢,就只能多发心跳包,加快心跳频率。但是心跳太快对移动端流量、电量的消耗又会太多,所以使用这种方法没办法做到快速感知,可以作为检测连接和服务可用的兜底机制。

5.3被动监听网络状态改变


如果要检测连接不可用,除了用心跳检测,还可以通过判断网络状态来实现,因为断网、切换wifi、切换网络是导致连接不可用的最直接原因,所以在网络状态由offline变为online时,大多数情况下需要重连下,但也不一定,因为webscoket底层是基于TCP的,TCP连接不能敏锐的感知到应用层的网络变化,所以有时候即便网络断开了一小会,对WebSocket连接是不会有影响的,网络恢复后,仍然能够正常地进行通信。

因此在网络由断开到连接上时,立即判断下连接是否可用,可以通过发一个心跳包判断,如果能够正常收到服务器的心跳回包,则说明连接仍是可用的,如果等待超时后仍没有收到心跳回包,则需要重连,如上图中的右侧。这种方法的优点是速度快,在网络恢复后能够第一时间感知连接是否可用,不可用的话可以快速执行恢复,但它只能覆盖应用层网络变化导致WebSocket不可用的情况。

5.4小结


综上所述:

  • 1)定时发送心跳包检测的方案贵在稳定,能够覆盖所有场景,但速度不即时(心跳间隔是固定的);
  • 2)判断网络状态的方案速度快,无需等待心跳间隔,较为灵敏,但覆盖场景较为局限。

因此,我们可以结合两种方案:

  • 1)定时以不太快的频率发送心跳包,比如40s/次、60s/次等,具体可以根据应用场景来定;
  • 2)然后在网络状态由offline变为online时立即发送一次心跳,检测当前连接是否可用,不可用的话立即进行恢复处理。

这样在大多数情况下,上层的应用通信都能较快从不可用状态中恢复,对于少部分场景,有定时心跳作为兜底,在一个心跳周期内也能够恢复。

6、快速重连关键2:快速断开旧连接


通常情况下,在发起下一次连接前,如果旧连接还存在的话,应该先把旧连接断开。

这样做的目的:

  • 1)一来可以释放客户端和服务器的资源;
  • 2)二来可以避免之后误从旧连接收发数据。

我们知道WebSocket底层是基于TCP协议传输数据的,连接两端分别是服务器和客户端,而TCP的TIME_WAIT状态是由服务器端维持的,因此在大多数正常情况下,应该由服务器发起断开底层TCP连接,而不是客户端。

也就是说:

  • 1)要断开WebSocket连接时,如果是服务器收到指示要断开WebSocket,那它应该立即发起断开TCP连接;
  • 2)如果是客户端收到指示要断开WebSocket,那它应该发信号给服务器,然后等待底层TCP连接被服务器断开或直至超时。

那如果客户端想要断开旧的WebSocket,可以分为WebSocket连接可用和不可用两种情况来讨论

具体如下:

  • 1)当旧连接可用时,客户端可以直接给服务器发送断开信号,然后服务器发起断开连接即可;
  • 2)当旧连接不可用时,比如客户端切换了wifi,客户端发送了断开信号,但是服务器收不到,客户端只能迟迟等待,直至超时才能被允许断开。

超时断开的过程相对来说是比较久的,那有没有办法可以快点断开?

上层应用无法改变只能由服务器发起断开连接这种协议层面的规则,所以只能从应用逻辑入手,比如在上层通过业务逻辑保证旧连接完全失效,模拟连接断开,然后在发起新连接,恢复通讯。

这种方法相当于尝试断开旧连接不行时,直接弃之,然后就能快速进入下一流程,所以在使用时一定要确保在业务逻辑上旧连接已完全失效。

比如:

  • 1)保证丢掉从旧连接收到所有数据;
  • 2)旧连接不能阻碍新连接的建立
  • 3)旧连接超时断开后不能影响新连接和上层业务逻辑等等。

7、快速重连关键3:快速发起新连接


有IM开发经验的同学应该有所了解,遇到因网络原因导致的重连时,是万万不能立即发起一次新连接的,否则当出现网络抖动时,所有的设备都会立即同时向服务器发起连接,这无异于黑客通过发起大量请求消耗网络带宽引起的拒绝服务攻击,这对服务器来说简直是灾难(即:服务端雪崩效应)。

所以在重连时通常采用一些退避算法,延迟一段时间再发起重连,如下图中左侧的流程。

4.jpg

如果要快速连上呢?最直接的做法就是缩短重试间隔,重试间隔越短,在网络恢复后就能越快的恢复通讯。但是太频繁的重试对性能、带宽、电量的消耗就比较严重。

如何在这之间做一个较好的权衡呢?

  • 1)一种比较合理的方式是随着重试次数增多,逐渐增大重试间隔;
  • 2)另一方面监听网络变化,在网络状态由offline变为online这种比较可能重连上的时刻,适当地减小重连间隔。

上述第2)种方案,如上图中的右侧所示,随重试次数的增多,重连间隔也会变大。这两种方式配合使用,更为合理。

除此之外,还可以结合业务逻辑,根据成功重连上的可能性适当的调整间隔,如网络未连接时或应用在后台时重连间隔可以调大一些,网络正常的状态下可以适当调小一些等等,加快重连上的速度。

8、本文小结


最后总结一下。

本文将WebSocket断网重连逻辑细分为三个步骤:

  • 1)确定何时需要重连;
  • 2)断开旧连接;
  • 3)发起新连接。

然后分别分析了在WebSocket的不同状态下、不同的网络场景下,如何快速完成这个三个步骤。

过程具体总结就是:

  • 1)首先:通过定时发送心跳包的方式检测当前连接是否可用,同时监测网络恢复事件,在恢复后立即发送一次心跳,快速感知当前状态,判断是否需要重连;
  • 2)其次:正常情况下由服务器断开旧连接,与服务器失去联系时直接弃用旧连接,上层模拟断开,来实现快速断开;
  • 3)最后:发起新连接时使用退避算法延迟一段时间再发起连接,同时考虑到资源浪费和重连速度,可以在网络离线时调大重连间隔,在网络正常或网络由offline变为online时缩小重连间隔,使之尽可能快地重连上。

以上就是我关于如何实现WebSocket快速重连的技术分享,欢迎留言与我探讨。

9、参考资料


[1] RFC 6455 文档
[2] 新手快速入门:WebSocket简明教程
[3] WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)
[4] WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)
[5] WebSocket详解(六):刨根问底WebSocket与Socket的关系

附录:更多Web端即时通讯资料


新手入门贴:史上最全Web端即时通讯技术原理详解
Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE
SSE技术详解:一种全新的HTML5服务器推送事件技术
Comet技术详解:基于HTTP长连接的Web端实时通信技术
socket.io实现消息推送的一点实践及思路
LinkedIn的Web端即时通讯实践:实现单机几十万条长连接
Web端即时通讯技术的发展与WebSocket、Socket.io的技术实践
Web端即时通讯安全:跨站点WebSocket劫持漏洞详解(含示例代码)
开源框架Pomelo实践:搭建Web端高性能分布式IM聊天服务器
使用WebSocket和SSE技术实现Web端消息推送
详解Web端通信方式的演进:从Ajax、JSONP 到 SSE、Websocket
MobileIMSDK-Web的网络层框架为何使用的是Socket.io而不是Netty?
理论联系实际:从零理解WebSocket的通信原理、协议格式、安全性
微信小程序中如何使用WebSocket实现长连接(含完整源码)
八问WebSocket协议:为你快速解答WebSocket热门疑问
快速了解Electron:新一代基于Web的跨平台桌面技术
一文读懂前端技术演进:盘点Web前端20年的技术变迁史
Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!
Web端即时通讯实践干货:如何让你的WebSocket断网重连更快速?
>> 更多同类文章 ……

即时通讯网 - 即时通讯开发者社区! 来源: - 即时通讯开发者社区!

本帖已收录至以下技术专辑

推荐方案
评论 12
这篇文章真的不错, 很少见到这种一手干货了,赞群主整理分享
"网络状态由offline变为online"
文中一直提到的这种状态变化事件,是如何得知的
签名: 哒哒,学习学习
引用:Fly463061655 发表于 2020-08-19 13:53
"网络状态由offline变为online"
文中一直提到的这种状态变化事件,是如何得知的

如果是android的话,是有系统网络事件的。但ios或普通的pc端(ios端有个官方的Reachability演示,具体看看这个帖子:https://www.cnblogs.com/sunfuyou/p/6838609.html),基本就是只能自已额外写网络探测逻辑。
签名: 《难得干货,揭秘支付宝的2维码扫码技术优化实践之路》:http://www.52im.net/thread-3150-1-1.html
都是干货啊。我简单写了退避算法。就是起始重连间隔,最大重连时间间隔,每次递增间隔。这三个值都是服务器返回给客户端的。不知道有没有其他更好的方法
引用:ym_im 发表于 2020-08-26 18:21
都是干货啊。我简单写了退避算法。就是起始重连间隔,最大重连时间间隔,每次递增间隔。这三个值都是服务器 ...

同时让上层在wifi或者流量切换后。直接调用重连接口
引用:ym_im 发表于 2020-08-26 18:21
都是干货啊。我简单写了退避算法。就是起始重连间隔,最大重连时间间隔,每次递增间隔。这三个值都是服务器 ...

确实没有更好的办法,够用就行了。
签名: 《难得干货,揭秘支付宝的2维码扫码技术优化实践之路》:http://www.52im.net/thread-3150-1-1.html
引用:JackJiang 发表于 2020-08-26 22:15
确实没有更好的办法,够用就行了。

手机客户端使用 epoll 监听 socekt,正常服务器断开客户端可以获取断开事件,但是如果客户端断网等情况,客户端没有监听到事件,有什么好办法快速解决获取断开状态吗
引用:ym_im 发表于 2020-08-27 13:58
手机客户端使用 epoll 监听 socekt,正常服务器断开客户端可以获取断开事件,但是如果客户端断网等情况, ...

只有自已做心跳是最靠谱的。默认的tcp协议实现中,客户端到服务端间的路由这么多跳,随便一个环节出故障,你的这条tcp连接也是无法感知的(也就是没法知道是否已经断开),只有自已在应用层做心跳是最好的方法。
签名: 《难得干货,揭秘支付宝的2维码扫码技术优化实践之路》:http://www.52im.net/thread-3150-1-1.html
引用:JackJiang 发表于 2020-08-27 14:15
只有自已做心跳是最靠谱的。默认的tcp协议实现中,客户端到服务端间的路由这么多跳,随便一个环节出故障 ...

心跳间隔事件短费电,时间长体验不好。

手机wifi切换4g、5g、或者4g、5g切换手机wifi,这些事件,app是不是都可以获取到
引用:ym_im 发表于 2020-08-27 14:19
心跳间隔事件短费电,时间长体验不好。

手机wifi切换4g、5g、或者4g、5g切换手机wifi,这些事件,app ...

理想测试下是的,但事件通知只是个通知,极端情况下并不能保证你的app一定就能收到,你可以自已写套算法试试看。心跳是主流做法,你不需要去纠结,因为这是大家都趟过坑后的实践总结。

另外,这一篇文章你可以看看:《为什么说基于TCP的移动端IM仍然需要心跳保活?
签名: 《难得干货,揭秘支付宝的2维码扫码技术优化实践之路》:http://www.52im.net/thread-3150-1-1.html
引用:JackJiang 发表于 2020-08-27 15:19
理想测试下是的,但事件通知只是个通知,极端情况下并不能保证你的app一定就能收到,你可以自已写套算法 ...

那订阅消息或者渠道消息。就是比如某个企业开始直播了。这个企业下面的所有员工都能收到一条企业开播的消息。可能这个企业人特别多达到几万人。这个消息虽然实时性要求不高,但是也不能太慢。

--------------------------------------------------------------------------------------------------------------------------------
我目前是在客户端的心跳中包含自己的订阅号。每次心跳到服务器,服务器从cache里面查一下,有没有
新的订阅消息。

这个做法怎么样。还是有新的订阅消息了。直接遍历发送给这个企业的所有人
引用:ym_im 发表于 2020-08-28 14:25
那订阅消息或者渠道消息。就是比如某个企业开始直播了。这个企业下面的所有员工都能收到一条企业开播的消 ...

你这个做法,有个专业的叫法:“轮询”,如果你的实时性要求不需要太高的话,轮询是很经济适用的,成本又低,技术又简单。
签名: 《难得干货,揭秘支付宝的2维码扫码技术优化实践之路》:http://www.52im.net/thread-3150-1-1.html
打赏楼主 ×
使用微信打赏! 使用支付宝打赏!

返回顶部