Q20: 长连接如何保持心跳?断线重连如何设计?
核心结论
心跳和重连是两件相关但不同的事:
- 心跳解决“连接是不是还活着”
- 重连解决“连接断了以后,业务怎么继续”
很多系统把“能自动重连”误以为“用户状态不会丢”。这是错误的。重连只是重新建立链路,状态是否可恢复,还取决于会话、缓存、消息补发和权威状态保存策略。
一、为什么应用层还要做心跳
TCP 连接存在一个典型问题:物理链路已经断开,但两端未必能立刻感知。
常见场景包括:
- 手机切网络
- Wi-Fi 断开后系统没有及时上报
- NAT 映射超时
- 中间设备静默丢弃空闲连接
这时候应用层需要主动探测,否则客户端和服务端都可能误以为连接仍然存在。
应用层心跳一般有三个目的:
- 及时发现假连接
- 保活 NAT 或中间链路
- 顺带测量 RTT、抖动、丢包情况
二、心跳不等于 TCP Keepalive
TCP Keepalive 是操作系统级机制,但默认参数通常偏保守,很多环境下并不能满足实时游戏要求。
所以实践里常见做法是:
- TCP Keepalive 作为底层保底
- 应用层心跳作为主要检测手段
应用层的优势是:
- 间隔可控
- 能携带业务上下文
- 能统计延迟
- 能统一跨 TCP、WebSocket、UDP、QUIC 等链路
三、心跳该怎么设计
1. 心跳包尽量简单
一个实用心跳包通常只需要:
- 会话标识
- 时间戳
- 序列号
必要时再加:
- 当前客户端时间
- 最近网络质量信息
- 轻量 ACK 信息
不要把心跳包做成业务大杂烩,否则会抬高频率成本,也会放大异常链路上的流量压力。
2. 有业务流量时不一定要单独发心跳
如果连接上业务消息很频繁,很多系统会把“任意入站或出站流量”都视作活跃信号。
这比机械地每几秒固定打一包更合理,因为:
- 能减少无意义包
- 更符合真实活跃状态
- 对高频实时玩法更省带宽
3. 超时阈值不能只看理想网络
心跳间隔和超时阈值要考虑:
- 用户网络抖动
- GC 或主线程卡顿
- 移动端后台切换
- 中间设备短时拥塞
工程上常见的是:
- 心跳间隔 3 到 10 秒
- 超时阈值设为 2 到 3 个心跳周期
但这不是固定公式,还是要看玩法和链路环境。
四、服务端如何判断断线
服务端通常不会只看“有没有收到 pong”,而是看“最近有没有收到该连接的任何有效消息”。
典型判断方式是:
- 每个会话维护最近接收时间
- 周期性扫描超时连接
- 超时后先标记失联,再进入回收流程
- 在允许重连的窗口内保留必要上下文
这里要特别注意,不同类型连接的超时策略不一定相同:
- 客户端连接关注体验和恢复
- 内部服务连接关注稳定性和降级
- 跨服连接关注幂等与重建顺序
五、断线重连真正要解决什么
重连不只是“再连一次”,而是要回答下面几个问题:
- 用户是否还是原来那个会话
- 旧连接是否已经失效
- 断线期间错过的消息怎么办
- 权威状态是否仍然保留在内存
- 当前能否无损恢复,还是只能半恢复
如果这些问题没设计清楚,“自动重连成功”只是一层表象。
六、一个更稳妥的重连流程
1. 客户端侧
客户端通常需要:
- 检测断开
- 进入重连状态
- 使用指数退避重试
- 携带重连凭证和最近已确认序列
- 成功后请求状态恢复或补发
指数退避很重要,因为在大面积波动时,立即重试会进一步放大服务器压力。
2. 服务端侧
服务端通常需要:
- 保留短期会话上下文
- 让旧连接失效
- 校验重连凭证是否可复用
- 识别是否为同一玩家的合法恢复
- 决定补发哪些消息,丢弃哪些消息
重连令牌最好具备:
- 短有效期
- 单次使用
- 绑定账号、会话或设备信息
七、状态恢复要分级别
不是所有状态都值得完整恢复。更合理的做法是分层。
1. 必须恢复的状态
例如:
- 账号和角色身份
- 当前地图或场景
- 核心角色属性
- 资产状态
这些通常要么在权威内存中短保留,要么能从持久化快速恢复。
2. 尽量恢复的状态
例如:
- AOI 内附近实体
- 最近几条战斗结果
- 临时 UI 上下文
这些可以在重连后重新下发或局部重建。
3. 不保证无损恢复的状态
例如:
- 短暂动画表现
- 非关键提示
- 已经过时的瞬时广播
为了恢复这些状态而大幅增加系统复杂度,通常并不划算。
八、断线期间的消息补发怎么做
补发并不意味着“把所有漏掉的包原样重放”。
更可行的思路通常有三类:
1. 关键事件补发
适合:
- 系统奖励
- 战斗结算
- 状态变更通知
2. 最新状态重建
适合:
- 角色属性
- 场景对象
- 当前背包和任务数据
很多时候直接下发最新权威状态,比补历史消息更简单也更稳。
3. 局部混合策略
例如:
- 资产和奖励做事件补发
- 场景与 AOI 做快照重建
- 聊天和提示类消息不补
这通常比“一刀切全部补发”更现实。
九、常见误区
1. 心跳越频繁越安全
不是。过短心跳会增加:
- 服务器定时扫描压力
- 移动端功耗
- 弱网下无效流量
合理的心跳频率要匹配玩法和平台。
2. 断线就立刻销毁玩家状态
这会直接破坏重连体验。多数在线游戏都会保留一个短暂恢复窗口,超时后再做彻底清理。
3. 重连成功就等于无损恢复
不对。链路恢复和业务恢复是两层问题。前者只是 socket 回来了,后者要看状态机、缓存和补发机制是否完整。
4. 所有消息都要补发
没必要,也经常做不到。真正应该优先保证的是权威状态正确,而不是把所有历史通知重演一遍。
十、一个实用的工程组合
很多在线游戏最后会落成这样的组合:
- 应用层轻量心跳
- 任意业务消息刷新活跃时间
- 服务端维护失联判定和短暂会话保留
- 客户端指数退避重连
- 单次有效的重连 token
- 关键事件补发加最新状态重建
- 资产和重要操作继续走幂等校验
这样做的重点不是“重连功能看起来很全”,而是把链路恢复、状态恢复、资产安全三件事拆开分别保证。
参考资料
- RFC 9293, TCP
- RFC 6455, WebSocket
- 各类在线游戏会话恢复与连接保活实践资料
