IM (即时通讯应用) 的特点是实时、可靠性,即可以快速、正确的收 / 发消息,在本篇我们着重讨论实时性。
# IM 中保持有效长连接的重要性
使用长连接来实现的驱动力来自于,每次收发消息我们希望只是一次简单的数据发送 / 接收,而不是建立一次新的 HTTP,因为那会带来 DNS 解析、连接建立的开销。
实现上面的前提是这个长连接可以有效的保持,如果长连接的请求迟迟没有收到反馈,经常断连,那么带来的体验还不如每次新建一个 HTTP。所以保持一个长连接的可用性是非常重要的,一种做法是我们定期的检测连接的可用性,并在发现连接不可用时及时断开重建。
这个也叫做心跳检测,它一方面可以优化客户端的体验,也可以让服务器及时地清理无效连接减轻负载以及知悉每个客户端的在线状态。
# Keep-Alive 是什么
Keep-Alive 的核心作用是保持网络连接的持久性,它在传输层 (TCP)、应用层 (HTTP) 均有实现。
# TCP Keep-Alive 的作用
TCP 首先是一个基于连接的协议,它们在上层没有任何调用时会持续保持连接状态。比如我们意外拔出自己的网线,此时的网络变化不会被 TCP 检测出,当重新插回网线会仍会复用之前的 TCP,没有重连的发生。
TCP 有一个 Keep-Alive 属性,当这个机制开启后,客户端默认会在定时时间 (空闲 7200s) 后发送 Keep-Alive 探针来检测连接的可用性,并在失败后进行重试,看起来时间是不满足 IM 的要求的。虽然这个时间可以被手动设置,但即使我们设置了较短的时间,仍然在 IM 场景是不适用的。
这是因为 TCP Keep-Alive 是用于检测连接的死活,而传统的心跳检测是检测通讯双方的存活状态,听起来是一个意思但其实大相径庭。
考虑一种情况,某台服务器负载过高导致 CPU100% 占用,此时无法响应客户端的业务请求。但是 TCP Keep-Alive 探针仍然认为连接是可用的,这就是一个经典的假死状态,因此我们不能以 TCP 连接的有效性来判断双方的存活性。
破除这个假死状态的方法就是我们需要对方的回应来确保存活状态,所以,我们需要在应用层做一个心跳机制,它有更大的灵活性,可以更好地控制检测时间、间隔和处理流程。

# HTTP Keep-Alive 的作用
上面说到应用层,那么 HTTP 这个应用层协议刚好也有 Keep-Alive 字段。但是不能将 HTTP 和 TCP 的 Keep-Alive 混淆。HTTP Keep-Alive 在 HTTP1.1 后被加入请求头字段,它的作用是保持多路复用来让多次 HTTP 请求能够复用同一个连接,提高通信效率,并没有检测存活状态的作用。


上图为打开了 Keep-Alive 字段后,HTTP1.0 和 1.1 的区别。
# 心跳机制的实现参考
首先最简单的方式就是我们可以在客户端设置一个定时器,每隔 30s 发送一个请求包,期望在 15s 内收到回应,收到回应 30s 后再发送一次心跳检测包,否则就需要进行重连。
然后还可以做一些小的优化,比如在没有收到回应时放宽标准,可以认为网络波动等原因,我们再多发送几次心跳包,如果仍然失败再进行重连,因为重连的开销是比较大的。
或是我们不固定死这个发送间隔,将心跳包与业务包 (聊天消息) 结合起来,当最后一次较晚的心跳响应包 / 业务响应包时间超过 30s 后,再去发送新的心跳请求包。
参考文章:
为什么说基于 TCP 的移动端 IM 仍然需要心跳保活?