关于WebSocket的心跳包

闲言

在优化排队机Client程序时,有一个叫号功能,叫号是通过柜面系统调用排队机Server,然后排队机Server排队机Client推送叫号的信息。而排队机Client排队机Server之间是通过 WebSocket 建立的长连接实现这项推送消息的功能。当时程序中建立 WebSocket 连接时,并未加心跳包,另一个市场同事建议 让我加进去,给出的原因是F5(负载均衡服务器)检测到没有通讯会把长连接关闭。其实很早就有个疑惑,WebSocket 是基于 TCP 的应用层协议,而 TCP 本身自带一个 KeepAlive 机制,为何还要多此一举在 WebSocket 这加心跳包?为此,我查了很多资料,终于有了答案。

Websocket

WebSocket协议是基于TCP的应用层协议,关于它的详细介绍,大家可以参考阮一峰老师写的《WebSocket 教程》在此不再赘述。

什么是心跳包

心跳包,顾名思义它像客户端的心跳一样每隔固定时间向服务器发一次,以此来告诉服务器自己还活着,服务器同时也会响应心跳包给客户端。目的是为了保持长连接(实质是保持长连接的可用性),至于内容并没有什么特别的规定。

保持长连接可用性意义

对于客户端而言,使用 TCP 长连接来实现业务的最大驱动力在于:在当前连接可用的情况下,每一次请求都只是简单的数据发送和接受,免去了 DNS 解析、连接建立等时间,大大加快了请求的速度,同时也有利于接受服务器的实时消息。但前提是:连接可用。如果连接无法很好地保持,每次请求就会变成碰运气:运气好,通过长连接发送请求并收到反馈。运气差,当前连接已失效,请求迟迟没有收到反馈直到超时,又需要一次连接建立的过程,其效率甚至还不如 HTTP。

对于服务器而言,能够及时获悉连接可用性也非常重要:一方面服务器需要及时清理无效连接以减轻负载;另一方面也有可能是业务的需求,如游戏副本中服务器需要及时处理玩家掉线带来的问题。

因此保持连接的前提必然是检测连接的可用性,并在连接不可用时主动放弃当前连接并建立新的连接。

TCP的KeepAlive

什么是KeepAlive?
引用自《计算机网络第7版》5-9-2

为何需要KeepAlive?
引用自《TCP-IP详解卷1:协议》18.7.3

KeepAlive原理:
引用自《TCP-IP详解卷1:协议》23.2

关于第4点:

为何ICMP不可达并不会导致TCP连接关闭?

KeepAlive间隔默认2小时,虽可配置,但是不建议修改(实际也不会改)
引用自《TCP-IP详解卷1:协议》23.2

总结

经过上边的描述值得注意的是:

1.这里的客户端、服务器指的是 end to end 即端到端的。
现实中,我们的客户端到服务器并不是端到端的,因为它俩之间会有很多节点,请求会经过这些节点或转发、或过滤等。例如代理服务器 Niginx、F5 等,它们认为一份长连接在一段时间内没有数据发送就等于失效,并会自作主张的切断这些连接。可参考《WebSocket加入心跳包防止自动断开连接》

2.TCP 是端到端的,WebSocket端点不等于TCP端点,如在两个WebSocket端点的连接中可以有好几个 TCP 连接。

3.KeepAlive 在一些极端情况下,如:在 client 异常挂死、异常断电断网等,无法反馈真实的 client 状态;

因此,我们需要在使用 WebSocket 时加心跳包。

参考文章

《WebSocket :用WebSocket实现推送你必须考虑的几个问题》
《为什么说基于TCP的移动端IM仍然需要心跳保活?》
《TCP协议的KeepAlive机制与HeartBeat心跳包》
《TCP keepalive的探究 (1) : NAT和保活机制》
《WebSockets ping/pong, why not TCP keepalive?》