本文做者网易智慧企业web前端开发工程师马莹莹。为了提高内容质量,收录时有修订和改动。html
一、引言
在一个完善的即时通信IM应用中,WebSocket是极其关键的一环,它为基于Web的即时通信应用提供了一种全双工的通讯机制。但为了提高IM等实际应用场景下的消息即时性和可靠性,咱们须要克服WebSocket及其底层依赖的TCP链接对于复杂网络状况下的不稳定性,即时通信的开发者们一般都须要为其设计一套完整的链接保活、验活以及断片网重连方案。前端
就断网重连而言,其重连响应速度将严重影响了上层应用的“即时性”和用户体验。试想打开网络一分钟后,微信的网络不能即时感知到socket链接的恢复,没法即时收发聊天消息的话,是否是很崩溃?web
所以,如何在复杂网络场景下,更即时快速地感知网络变更,并快速恢复WebSocket的可用性,就变得尤其重要。本文将基于笔者的开发实践,分享WebSocket在不一样状态下、不一样的网络情形下,应该如何实现快速断网重连。算法

* 阅读对象:本文适合有过IM底层网络实际开发经验,或者对底层网络实现有较深了解的开发者阅读。若是对底层网络了解甚少,建议跳过本文,直接阅读网络本文末尾附录部分的基础后再回头来看。跨域
* 内容点评:本文内容没有高大上,但比较干货,实用性较高,内容也很通俗,建议可详细阅读。文中虽讲的是WebSocket,但思想能够延伸应用到基于TCP协议的同类技术中。浏览器
二、预备知识
本文中将要分享的内容是基于实践总结,若是你对Web端的即时通信知识还一头雾水,务必先读:《新手入门贴:史上最全Web端即时通信技术原理详解》、《Web端即时通信技术盘点:短轮询、Comet、Websocket、SSE》。服务器
三、快速了解WebSocket
Websocket诞生于2008年,在2011年成为国际标准,如今全部的浏览器都已支持(详见《新手快速入门:WebSocket简明教程》)。它是一种全新的应用层协议,是专门为web客户端和服务端设计的真正的全双工通讯协议,能够类比HTTP协议来了解websocket协议。微信

(图片引用自《WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)》)websocket
它们的不一样点:网络
- 1)HTTP的协议标识符是http,WebSocket的是ws;
- 2)HTTP请求只能由客户端发起,服务器没法主动向客户端推送消息,而WebSocket能够;
- 3)HTTP请求有同源限制,不一样源之间通讯须要跨域,而WebSocket没有同源限制。
它们的相同点:
- 1)都是应用层的通讯协议;
- 2)默认端口同样,都是80或443;
- 3)均可以用于浏览器和服务器间的通讯;
- 4)都基于TCP协议。
二者和TCP的关系图:

(图片引用自《新手快速入门:WebSocket简明教程》)
有关Http和WebSocket的关系,能够详读:
有关WebSocket和Socket的关系,能够详读:《WebSocket详解(六):刨根问底WebSocket与Socket的关系》.
四、WebSocket重连过程拆解
首先考虑一个问题,什么时候须要重连?
最容易想到的是WebSocket链接断了,为了接下来能收发消息,咱们须要再发起一次链接。
但在不少场景下,即使WebSocket链接没有断开,实际上也不可用了。
好比如下场景:
- 1)设备切换网络;
- 2)链路中间路由崩溃(常识是一条socket链接对应的网络通路上,会存在不少路由设备);
- 3)链路的前端出口不可用(好比家庭WiFi中,网络链接正常,但实际运营商的宽带已经欠费被停机);
- 4)服务器负载持续太高没法响应等。
这些场景下的WebSocket都没有断开,但对上层来讲,都没办法正常的收发数据了。
所以在重连前,咱们须要一种机制来感知链接是否可用、服务是否可用,并且要能快速感知,以便可以快速从不可用状态中恢复。
一旦感知到了链接不可用,那即可以弃旧图新了,弃用并断开旧链接,而后发起一次新链接。这两个步骤看似简单,但若想达到快,且不是那么容易的。
首先:是断开旧链接,对客户端来讲,如何快速断开?协议规定客户端必需要和服务器协商后才能断开WebSocket链接,可是当客户端已经联系不上服务器、没法协商时,如何断开并快速恢复?
其次:是快速发起新链接。此快非彼快,这里的快并不是是当即发起链接,当即发起链接会对服务器带来不可预估的影响。重连时一般会采用一些退避算法,延迟一段时间后再发起重连。但如何在重连间隔和性能消耗间作出权衡?如何在“恰当的时间点”快速发起链接?
带着这些疑问,咱们来细看下这三个过程:

五、快速重连关键1:快速感知什么时候须要重连
5.1 场景
须要重连的场景能够细分为三种:
- 1)链接明确断开了;
- 2)链接没断可是不可用了;
- 3)链接对端的服务不可用了。
对于第一种场景:这很简单,链接直接断开了,确定须要重连了。
对于后二者:不管是链接不可用,仍是服务不可用,对上层应用的影响都是不能再收发即时消息了。
5.2 心跳包主动探测网络可用性
因此从上面这个角度出发,感知什么时候须要重连的一种简单粗暴的方法就是经过心跳包超时:发送一个心跳包,若是超过特定的时间后尚未收到服务器回包,则认为服务不可用,以下图中左侧的方案(这种方法最直接)。

那若是想要快速感知呢,就只能多发心跳包,加快心跳频率。可是心跳太快对移动端流量、电量的消耗又会太多,因此使用这种方法没办法作到快速感知,能够做为检测链接和服务可用的兜底机制。
5.3 被动监听网络状态改变
若是要检测链接不可用,除了用心跳检测,还能够经过判断网络状态来实现,由于断网、切换wifi、切换网络是致使链接不可用的最直接缘由,因此在网络状态由offline变为online时,大多数状况下须要重连下,但也不必定,由于webscoket底层是基于TCP的,TCP链接不能敏锐的感知到应用层的网络变化,因此有时候即使网络断开了一小会,对WebSocket链接是不会有影响的,网络恢复后,仍然可以正常地进行通讯。
所以在网络由断开到链接上时,当即判断下链接是否可用,能够经过发一个心跳包判断,若是可以正常收到服务器的心跳回包,则说明链接还是可用的,若是等待超时后仍没有收到心跳回包,则须要重连,如上图中的右侧。这种方法的优势是速度快,在网络恢复后可以第一时间感知链接是否可用,不可用的话能够快速执行恢复,但它只能覆盖应用层网络变化致使WebSocket不可用的状况。
5.4 小结
综上所述:
- 1)定时发送心跳包检测的方案贵在稳定,可以覆盖全部场景,但速度不即时(心跳间隔是固定的);
- 2)判断网络状态的方案速度快,无需等待心跳间隔,较为灵敏,但覆盖场景较为局限。
所以,咱们能够结合两种方案:
- 1)定时以不太快的频率发送心跳包,好比40s/次、60s/次等,具体能够根据应用场景来定;
- 2)而后在网络状态由offline变为online时当即发送一次心跳,检测当前链接是否可用,不可用的话当即进行恢复处理。
这样在大多数状况下,上层的应用通讯都能较快从不可用状态中恢复,对于少部分场景,有定时心跳做为兜底,在一个心跳周期内也可以恢复。
六、快速重连关键2:快速断开旧链接
一般状况下,在发起下一次链接前,若是旧链接还存在的话,应该先把旧链接断开。
这样作的目的:
- 1)一来能够释放客户端和服务器的资源;
- 2)二来能够避免以后误从旧链接收发数据。
咱们知道WebSocket底层是基于TCP协议传输数据的,链接两端分别是服务器和客户端,而TCP的TIME_WAIT状态是由服务器端维持的,所以在大多数正常状况下,应该由服务器发起断开底层TCP链接,而不是客户端。
也就是说:
- 1)要断开WebSocket链接时,若是是服务器收到指示要断开WebSocket,那它应该当即发起断开TCP链接;
- 2)若是是客户端收到指示要断开WebSocket,那它应该发信号给服务器,而后等待底层TCP链接被服务器断开或直至超时。
那若是客户端想要断开旧的WebSocket,能够分为WebSocket链接可用和不可用两种状况来讨论。
具体以下:
- 1)当旧链接可用时,客户端能够直接给服务器发送断开信号,而后服务器发起断开链接便可;
- 2)当旧链接不可用时,好比客户端切换了wifi,客户端发送了断开信号,可是服务器收不到,客户端只能迟迟等待,直至超时才能被容许断开。
超时断开的过程相对来讲是比较久的,那有没有办法能够快点断开?
上层应用没法改变只能由服务器发起断开链接这种协议层面的规则,因此只能从应用逻辑入手,好比在上层经过业务逻辑保证旧链接彻底失效,模拟链接断开,而后在发起新链接,恢复通信。
这种方法至关于尝试断开旧链接不行时,直接弃之,而后就能快速进入下一流程,因此在使用时必定要确保在业务逻辑上旧链接已彻底失效。
好比:
- 1)保证丢掉从旧链接收到全部数据;
- 2)旧链接不能阻碍新链接的创建
- 3)旧链接超时断开后不能影响新链接和上层业务逻辑等等。
七、快速重连关键3:快速发起新链接
有IM开发经验的同窗应该有所了解,遇到因网络缘由致使的重连时,是万万不能当即发起一次新链接的,不然当出现网络抖动时,全部的设备都会当即同时向服务器发起链接,这无异于黑客经过发起大量请求消耗网络带宽引发的拒绝服务攻击,这对服务器来讲简直是灾难(即:服务端雪崩效应)。
因此在重连时一般采用一些退避算法,延迟一段时间再发起重连,以下图中左侧的流程。

若是要快速连上呢?最直接的作法就是缩短重试间隔,重试间隔越短,在网络恢复后就能越快的恢复通信。可是太频繁的重试对性能、带宽、电量的消耗就比较严重。
如何在这之间作一个较好的权衡呢?
- 1)一种比较合理的方式是随着重试次数增多,逐渐增大重试间隔;
- 2)另外一方面监听网络变化,在网络状态由offline变为online这种比较可能重连上的时刻,适当地减少重连间隔。
上述第2)种方案,如上图中的右侧所示,随重试次数的增多,重连间隔也会变大。这两种方式配合使用,更为合理。
除此以外,还能够结合业务逻辑,根据成功重连上的可能性适当的调整间隔,如网络未链接时或应用在后台时重连间隔能够调大一些,网络正常的状态下能够适当调小一些等等,加快重连上的速度。
八、本文小结
最后总结一下。
本文将WebSocket断网重连逻辑细分为三个步骤:
- 1)肯定什么时候须要重连;
- 2)断开旧链接;
- 3)发起新链接。
而后分别分析了在WebSocket的不一样状态下、不一样的情形状态下,如何快速完成这个三个步骤。
过程具体总结就是:
- 1)首先:经过定时发送心跳包的方式检测当前链接是否可用,同时监测网络恢复事件,在恢复后当即发送一次心跳,快速感知当前状态,判断是否须要重连;
- 2)其次:正常状况下由服务器断开旧链接,与服务器失去联系时直接弃用旧链接,上层模拟断开,来实现快速断开;
- 3)最后:发起新链接时使用退避算法延迟一段时间再发起链接,同时考虑到资源浪费和重连速度,能够在网络离线时调大重连间隔,在网络正常或网络由offline变为online时缩小重连间隔,使之尽量快地重连上。
以上就是我关于如何实现WebSocket快速重连的技术分享,欢迎留言与我探讨。
九、参考资料
[1] RFC 6455 文档
[3] WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)
[4] WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)
[5] WebSocket详解(六):刨根问底WebSocket与Socket的关系
本文已同步发布于“即时通信技术圈”公众号:
