长连接:为什么"保持连着"这么难
短连接(一次网页请求)几秒内结束,几乎感受不到链路的"新陈代谢"。长连接(流式接口、推送、隧道)要活几分钟到几小时,于是撞上一个事实:互联网的中间设备默认不喜欢长期占用资源的连接。理解这一点,长连接类故障的排查就有了统一的框架。
链路上的计时器清单
一条长连接要活下来,需要沿途所有计时器都不到期。数一遍这些计时器:
| 位置 | 计时器 | 典型值 |
|---|---|---|
| 家用路由器 | NAT 表空闲超时 | 几分钟 |
| 运营商级 NAT(蜂窝) | 会话空闲超时 | 几十秒到几分钟 |
| 企业防火墙 | 会话表超时 | 策略各异 |
| 负载均衡/网关 | idle timeout | 60 秒常见 |
| 反向代理 | read timeout | Nginx 默认 60 秒 |
| 服务端应用 | 空闲回收 / 最大存活时长 | 实现各异 |
最短的那个说了算。 这解释了长连接问题最典型的特征——断开时长的规律性:每次都在接近相同的时长后断开,就是撞上了某个固定计时器。
三类断开机制
- 空闲超时:静默超过阈值即清理。特征是断开总发生在"最后一次数据之后固定间隔"。对策是心跳。对应 LONG-003;
- 最大存活时长:无论是否活跃,到点就断。特征是断开与连接建立时刻的间隔固定。心跳无效,只能靠重连与续传。对应 LONG-002;
- 暴力切断:崩溃、清表、特征识别,以 RST 收场。特征是没有正常关闭流程。对应 WS-003 与 LONG-001。
心跳:作用与局限
心跳(应用层定期发送小数据)的唯一目标是重置沿途的空闲计时器。两个要点:
- 间隔必须明显小于链路最短空闲超时(建议为其三分之一到一半),否则等于没有;
- 心跳打不过"最大存活时长"和"暴力切断"——那两类断开需要的不是保活而是优雅重连:快速重连、指数退避、会话续传。
顺带澄清:操作系统的 TCP keepalive 与应用层心跳不是一回事——前者默认间隔长达两小时,且部分 NAT 不把它当作活跃流量,指望它保活基本会落空。
存活时长:最有价值的证据
排查长连接问题时,别只记录"断了",记录"活了多久"。连续测量三次:
connproof websocket wss://目标地址/路径 --hold 300000三个存活时长的分布直接指认断开机制:高度一致 → 固定计时器;与静默间隔相关 → 空闲超时;完全随机 → 稳定性问题。再配合"换网络后的三次测量",计时器的归属(本地网络还是远端)也水落石出。
移动端的特殊性
手机系统对后台连接的回收比任何中间设备都激进:iOS 后台约 30 秒挂起,Android 各厂商的省电策略五花八门。在移动端,"断开-重连"是常态而不是故障——评价一个移动应用的连接质量,看的是重连是否无感,而不是连接是否永生。