Web端即时通讯实践干货:如何让WebSocket断网重连更快速?
本文作者网易智慧企业web前端开发工程师马莹莹。为了提升内容质量,收录时有修订和改动。
1、引言
在一个完善的即时通讯IM应用中,WebSocket是极其关键的一环,它为基于Web的即时通讯应用提供了一种全双工的通信机制。但为了提升IM等实际应用场景下的消息即时性和可靠性,我们需要克服WebSocket及其底层依赖的TCP连接对于复杂网络情况下的不稳定性,即时通讯的开发者们通常都需要为其设计一套完整的连接保活、验活以及断片网重连方案。
就断网重连而言,其重连响应速度将严重影响了上层应用的“即时性”和用户体验。试想打开网络一分钟后,微信的网络不能即时感知到socket连接的恢复,无法即时收发聊天消息的话,是不是很崩溃?
因此,如何在复杂网络场景下,更即时快速地感知网络变动,并快速恢复WebSocket的可用性,就变得尤为重要。本文将基于笔者的开发实践,分享WebSocket在不同状态下、不同的网络状态下,应该如何实现快速断网重连。
* 阅读对象:本文适合有过IM底层网络实际开发经验,或者对底层网络实现有较深了解的开发者阅读。如果对底层网络了解甚少,建议跳过本文,直接阅读网络本文末尾附录部分的基础后再回头来看。
* 内容点评:本文内容没有高大上,但比较干货,实用性较高,内容也很通俗,建议可详细阅读。文中虽讲的是WebSocket,但思想可以延伸应用到基于TCP协议的同类技术中。
2、预备知识
本文中将要分享的内容是基于实践总结,如果你对Web端的即时通讯知识还一头雾水,务必先读:《新手入门贴:史上最全Web端即时通讯技术原理详解》、《Web端即时通讯技术盘点:短轮询、Comet、Websocket、SSE》。
3、快速了解WebSocket
Websocket诞生于2008年,在2011年成为国际标准,现在所有的浏览器都已支持(详见《新手快速入门:WebSocket简明教程》)。它是一种全新的应用层协议,是专门为web客户端和服务端设计的真正的全双工通信协议,可以类比HTTP协议来了解websocket协议。
(图片引用自《WebSocket详解(四):刨根问底HTTP与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的关系》.
4、WebSocket重连过程拆解
首先考虑一个问题,何时需要重连?
最容易想到的是WebSocket连接断了,为了接下来能收发消息,我们需要再发起一次连接。
但在很多场景下,即便WebSocket连接没有断开,实际上也不可用了。
比如以下场景:
- 1)设备切换网络;
- 2)链路中间路由崩溃(常识是一条socket连接对应的网络通路上,会存在很多路由设备);
- 3)链路的前端出口不可用(比如家庭WiFi中,网络连接正常,但实际运营商的宽带已经欠费被停机);
- 4)服务器负载持续过高无法响应等。
这些场景下的WebSocket都没有断开,但对上层来说,都没办法正常的收发数据了。
因此在重连前,我们需要一种机制来感知连接是否可用、服务是否可用,而且要能快速感知,以便能够快速从不可用状态中恢复。
一旦感知到了连接不可用,那便可以弃旧图新了,弃用并断开旧连接,然后发起一次新连接。这两个步骤看似简单,但若想达到快,且不是那么容易的。
首先:是断开旧连接,对客户端来说,如何快速断开?协议规定客户端必须要和服务器协商后才能断开WebSocket连接,但是当客户端已经联系不上服务器、无法协商时,如何断开并快速恢复?
其次:是快速发起新连接。此快非彼快,这里的快并非是立即发起连接,立即发起连接会对服务器带来不可预估的影响。重连时通常会采用一些退避算法,延迟一段时间后再发起重连。但如何在重连间隔和性能消耗间做出权衡?如何在“恰当的时间点”快速发起连接?
带着这些疑问,我们来细看下这三个过程:
5、快速重连关键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时立即发送一次心跳,检测当前连接是否可用,不可用的话立即进行恢复处理。
这样在大多数情况下,上层的应用通信都能较快从不可用状态中恢复,对于少部分场景,有定时心跳作为兜底,在一个心跳周期内也能够恢复。
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开发经验的同学应该有所了解,遇到因网络原因导致的重连时,是万万不能立即发起一次新连接的,否则当出现网络抖动时,所有的设备都会立即同时向服务器发起连接,这无异于黑客通过发起大量请求消耗网络带宽引起的拒绝服务攻击,这对服务器来说简直是灾难(即:服务端雪崩效应)。
所以在重连时通常采用一些退避算法,延迟一段时间再发起重连,如下图中左侧的流程。
如果要快速连上呢?最直接的做法就是缩短重试间隔,重试间隔越短,在网络恢复后就能越快的恢复通讯。但是太频繁的重试对性能、带宽、电量的消耗就比较严重。
如何在这之间做一个较好的权衡呢?
- 1)一种比较合理的方式是随着重试次数增多,逐渐增大重试间隔;
- 2)另一方面监听网络变化,在网络状态由offline变为online这种比较可能重连上的时刻,适当地减小重连间隔。
上述第2)种方案,如上图中的右侧所示,随重试次数的增多,重连间隔也会变大。这两种方式配合使用,更为合理。
除此之外,还可以结合业务逻辑,根据成功重连上的可能性适当的调整间隔,如网络未连接时或应用在后台时重连间隔可以调大一些,网络正常的状态下可以适当调小一些等等,加快重连上的速度。
8、本文小结
最后总结一下。
本文将WebSocket断网重连逻辑细分为三个步骤:
- 1)确定何时需要重连;
- 2)断开旧连接;
- 3)发起新连接。
然后分别分析了在WebSocket的不同状态下、不同的网络状态下,如何快速完成这个三个步骤。
过程具体总结就是:
- 1)首先:通过定时发送心跳包的方式检测当前连接是否可用,同时监测网络恢复事件,在恢复后立即发送一次心跳,快速感知当前状态,判断是否需要重连;
- 2)其次:正常情况下由服务器断开旧连接,与服务器失去联系时直接弃用旧连接,上层模拟断开,来实现快速断开;
- 3)最后:发起新连接时使用退避算法延迟一段时间再发起连接,同时考虑到资源浪费和重连速度,可以在网络离线时调大重连间隔,在网络正常或网络由offline变为online时缩小重连间隔,使之尽可能快地重连上。
以上就是我关于如何实现WebSocket快速重连的技术分享,欢迎留言与我探讨。
9、参考资料
[1] RFC 6455 文档
[3] WebSocket详解(四):刨根问底HTTP与WebSocket的关系(上篇)
[4] WebSocket详解(五):刨根问底HTTP与WebSocket的关系(下篇)
[5] WebSocket详解(六):刨根问底WebSocket与Socket的关系
本文已同步发布于“即时通讯技术圈”公众号:

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
不要一把梭了,这才是SQL优化的正确姿势!|原创干货
这是我的第83篇原创文章 作者 | 王磊 来源 | Java中文社群(ID:javacn666) 转载请联系授权(微信ID:GG_Stone) 年少不知优化苦,遇坑方知优化难。——村口王大爷 全文内容预览: 我之前有很多文章都在讲性能优化的问题,比如下面这些: 《switch 的性能提升了 3 倍,我只用了这一招!》 《String性能提升10倍的几个方法!(源码+原理分析)》 《局部变量竟然比全局变量快 5 倍?》 《池化技术到达有多牛?看了线程和线程池的对比吓我一跳!》 《链表竟然比数组慢了1000多倍?(动图+性能评测)》 《HashMap 的 7 种遍历方式与性能分析!》 更多性能优化文章 当然,本篇也是关于性能优化的,那性能优化就应该一把梭子吗?还是要符合一些规范和原则呢? 所以,在开始之前(MySQL 优化),咱们先来聊聊性能优化的一些原则。 性能优化原则和分类 性能优化一般可以分为: 主动优化 被动优化 所谓的主动优化是指不需要外力的推动而自发进行的一种行为,比如当服务没有明显的卡顿、宕机或者硬件指标异常的情况下,自我出发去优化的行为,就可以称之为主动优化。 而被动优化刚...
- 下一篇
Docker三剑客之Swarm集群搭建
1. Docker Swarm 简介 Docker Swarm 是 Docker 官方三剑客项目之一,提供 Docker 容器集群服务,是 Docker 官方对容器云生态进行支持的核心方案。 使用它,用户可以将多个 Docker 主机封装为单个大型的虚拟 Docker 主机,快速打造一套容器云平台。 注意:Docker 1.12.0+ Swarm mode 已经内嵌入 Docker 引擎,成为了 docker 子命令 docker swarm,绝大多数用户已经开始使用 Swarm mode,Docker 引擎 API 已经删除 Docker Swarm。为避免大家混淆旧的 Docker Swarm 与新的 Swarm mode,旧的 Docker Swarm 内容已经删除,请查看 Swarm mode 一节。 2. Docker Swarm mode Docker 1.12 Swarm mode 已经内嵌入 Docker 引擎,成为了 docker 子命令 docker swarm。请注意与旧的 Docker Swarm 区分开来。 Swarm mode 内置 kv 存储功能,提供了众...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Hadoop3单机部署,实现最简伪集群
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2整合Redis,开启缓存,提高访问速度
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7,8上快速安装Gitea,搭建Git服务器
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库