Apollo 技术文档Apollo 技术文档
指南
  • 架构概述
  • BigWorld 架构深度解析
  • BigWorld 进程架构与玩家生命周期
  • AOI九宫格系统详解
  • AOI广播与消息去重
  • Base 模块
  • Core 模块
  • Runtime 模块
  • Data 模块
  • Network 模块
  • /modules/actor.html
  • Game 模块
  • BigWorld 模块
服务器应用
API 参考
QA
GitHub
指南
  • 架构概述
  • BigWorld 架构深度解析
  • BigWorld 进程架构与玩家生命周期
  • AOI九宫格系统详解
  • AOI广播与消息去重
  • Base 模块
  • Core 模块
  • Runtime 模块
  • Data 模块
  • Network 模块
  • /modules/actor.html
  • Game 模块
  • BigWorld 模块
服务器应用
API 参考
QA
GitHub
  • MMORPG 架构 QA

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. 周期性扫描超时连接
  3. 超时后先标记失联,再进入回收流程
  4. 在允许重连的窗口内保留必要上下文

这里要特别注意,不同类型连接的超时策略不一定相同:

  • 客户端连接关注体验和恢复
  • 内部服务连接关注稳定性和降级
  • 跨服连接关注幂等与重建顺序

五、断线重连真正要解决什么

重连不只是“再连一次”,而是要回答下面几个问题:

  • 用户是否还是原来那个会话
  • 旧连接是否已经失效
  • 断线期间错过的消息怎么办
  • 权威状态是否仍然保留在内存
  • 当前能否无损恢复,还是只能半恢复

如果这些问题没设计清楚,“自动重连成功”只是一层表象。

六、一个更稳妥的重连流程

1. 客户端侧

客户端通常需要:

  • 检测断开
  • 进入重连状态
  • 使用指数退避重试
  • 携带重连凭证和最近已确认序列
  • 成功后请求状态恢复或补发

指数退避很重要,因为在大面积波动时,立即重试会进一步放大服务器压力。

2. 服务端侧

服务端通常需要:

  • 保留短期会话上下文
  • 让旧连接失效
  • 校验重连凭证是否可复用
  • 识别是否为同一玩家的合法恢复
  • 决定补发哪些消息,丢弃哪些消息

重连令牌最好具备:

  • 短有效期
  • 单次使用
  • 绑定账号、会话或设备信息

七、状态恢复要分级别

不是所有状态都值得完整恢复。更合理的做法是分层。

1. 必须恢复的状态

例如:

  • 账号和角色身份
  • 当前地图或场景
  • 核心角色属性
  • 资产状态

这些通常要么在权威内存中短保留,要么能从持久化快速恢复。

2. 尽量恢复的状态

例如:

  • AOI 内附近实体
  • 最近几条战斗结果
  • 临时 UI 上下文

这些可以在重连后重新下发或局部重建。

3. 不保证无损恢复的状态

例如:

  • 短暂动画表现
  • 非关键提示
  • 已经过时的瞬时广播

为了恢复这些状态而大幅增加系统复杂度,通常并不划算。

八、断线期间的消息补发怎么做

补发并不意味着“把所有漏掉的包原样重放”。

更可行的思路通常有三类:

1. 关键事件补发

适合:

  • 系统奖励
  • 战斗结算
  • 状态变更通知

2. 最新状态重建

适合:

  • 角色属性
  • 场景对象
  • 当前背包和任务数据

很多时候直接下发最新权威状态,比补历史消息更简单也更稳。

3. 局部混合策略

例如:

  • 资产和奖励做事件补发
  • 场景与 AOI 做快照重建
  • 聊天和提示类消息不补

这通常比“一刀切全部补发”更现实。

九、常见误区

1. 心跳越频繁越安全

不是。过短心跳会增加:

  • 服务器定时扫描压力
  • 移动端功耗
  • 弱网下无效流量

合理的心跳频率要匹配玩法和平台。

2. 断线就立刻销毁玩家状态

这会直接破坏重连体验。多数在线游戏都会保留一个短暂恢复窗口,超时后再做彻底清理。

3. 重连成功就等于无损恢复

不对。链路恢复和业务恢复是两层问题。前者只是 socket 回来了,后者要看状态机、缓存和补发机制是否完整。

4. 所有消息都要补发

没必要,也经常做不到。真正应该优先保证的是权威状态正确,而不是把所有历史通知重演一遍。

十、一个实用的工程组合

很多在线游戏最后会落成这样的组合:

  • 应用层轻量心跳
  • 任意业务消息刷新活跃时间
  • 服务端维护失联判定和短暂会话保留
  • 客户端指数退避重连
  • 单次有效的重连 token
  • 关键事件补发加最新状态重建
  • 资产和重要操作继续走幂等校验

这样做的重点不是“重连功能看起来很全”,而是把链路恢复、状态恢复、资产安全三件事拆开分别保证。

参考资料

  • RFC 9293, TCP
  • RFC 6455, WebSocket
  • 各类在线游戏会话恢复与连接保活实践资料
在 GitHub 上编辑此页
最后更新: 3/20/26, 6:06 AM
贡献者: cuihairu