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

Q12: 如何处理网络抖动和丢包?

核心结论

处理网络抖动和丢包,目标不是“让网络变好”,而是让系统在坏网络下仍然可玩、可控、可恢复。

要先分清三件事:

  • 哪些问题可以在客户端视觉层掩盖
  • 哪些问题必须在传输层恢复
  • 哪些问题必须由服务端维持权威状态

真正成熟的做法通常不是单一手段,而是多层配合:

  • 客户端做插值、外推、缓冲和平滑
  • 传输层做序列号、重传、丢包统计
  • 服务端做权威校正、限流和状态收敛

一、先分清网络里的几种问题

讨论“网络不好”时,最容易把不同问题混成一类。

1.1 延迟

消息到达慢,但相对稳定。

表现:

  • 操作响应慢
  • 技能反馈滞后
  • 角色动作整体有拖后感

1.2 抖动

延迟不是恒定的,而是上下波动。

表现:

  • 角色一会流畅一会卡
  • 位置更新间隔不均匀
  • 画面出现轻微顿挫和跳变

1.3 丢包

部分包根本没到。

表现:

  • 操作偶发失效
  • 位置突然跳变
  • 某些状态更新缺失

1.4 乱序

包到了,但顺序不是发送顺序。

表现:

  • 旧状态覆盖新状态
  • 回退感
  • 动作先后错乱

这四类问题会一起出现,但处理方式并不相同。


二、先决定哪些消息值得“救”

网络问题处理得好不好,核心不在算法,而在于有没有先给消息分层。

2.1 必须恢复的消息

例如:

  • 登录结果
  • 技能释放确认
  • 伤害结算
  • 交易结果
  • 背包修改

这类消息不能简单丢掉,否则逻辑会直接错。

2.2 可以丢旧保新的消息

例如:

  • 位置同步
  • 朝向同步
  • 高频动作状态
  • 高频表现层广播

这类消息里,旧包到得再完整也没意义,重要的是最新状态尽快到。

2.3 可以纯表现层平滑的消息

例如:

  • 远端玩家移动显示
  • 远端玩家转身表现
  • 某些低价值动画触发

这里的重点不是重传,而是不要让玩家看到跳变。


三、抖动的核心处理:不要按到达时间直接播放

抖动最典型的问题是:

包不是稳定间隔到达的。

如果客户端收到就立刻渲染,结果往往是:

  • 一帧没数据
  • 下一帧来两三个包
  • 画面时快时慢

3.1 更合理的思路:延迟播放

客户端不要追着“最新到达包”立即播,而是故意落后一个小窗口。

例如:

  • 服务端每 100ms 发一次位置
  • 客户端展示时故意滞后 100ms 到 200ms

这样做的好处是:

  • 可以等一等晚到的包
  • 可以在两个样本之间平滑插值
  • 视觉上更稳定

代价是:

  • 画面会多出一个固定延迟

这就是典型的“用稳定性换一点时效性”。

3.2 为什么抖动缓冲不是越大越好

缓冲越大,画面越稳,但手感越钝。

所以抖动缓冲的关键不是“尽量大”,而是:

  • 够覆盖典型抖动
  • 不把交互时延拖得太高

很多时候它应该是一个动态值,而不是写死的常数。


四、客户端最常用的三种掩盖手段

4.1 插值

插值适合:

  • 已经有前后两个样本
  • 只是想让表现更平滑

典型场景:

  • 远端角色移动
  • 远端角色转向
  • 远端位置广播显示

它的优点是稳定,缺点是一定引入展示延迟。

4.2 外推

外推适合:

  • 新样本暂时没到
  • 但可以根据旧速度、旧方向做短时间预测

典型场景:

  • 高频移动对象
  • 包短时间丢失

它的风险是:

  • 网络坏得越厉害,误差越大

所以外推一般不能无限做,通常只适合很短时间窗口。

4.3 校正

客户端预测或外推之后,最终还是要对齐服务端权威状态。

校正通常分两种:

  • 软校正:小误差平滑拉回
  • 硬校正:大误差直接纠正

如果没有这一层,客户端只会越跑越偏。


五、丢包的核心处理:区分“重传”还是“忽略”

不是所有丢包都应该重传。

5.1 必须重传的情况

例如:

  • 技能释放事件
  • 重要状态切换
  • 结算结果
  • 可靠确认消息

因为丢了就不是“卡一下”,而是逻辑错误。

5.2 不应该重传的情况

例如:

  • 300ms 前的位置包
  • 已经过时的姿态包
  • 高频广播里的旧状态

因为即使补回来,也只会制造更多乱序和视觉噪声。

5.3 所以丢包处理必须和消息分层绑在一起

正确的问题不是“系统支不支持重传”,而是:

  • 哪些消息要重传
  • 哪些消息要直接丢
  • 哪些消息只要最新一份

六、服务端必须做什么

客户端可以掩盖体验,但不能替代服务端的权威控制。

6.1 保持权威状态

无论客户端插值、外推还是预测,服务端都应该是:

  • 位置权威源
  • 战斗结果权威源
  • 资产状态权威源

否则弱网下客户端会很快把状态跑偏。

6.2 做状态收敛

服务端需要允许客户端暂时偏离,但最终要把系统拉回权威轨道。

典型手段包括:

  • 定期状态快照
  • 序列号检查
  • 超时丢弃过旧输入
  • 定期校正位置和状态

6.3 控制发送频率

有时网络抖动和丢包不是外部网络太差,而是自己把链路打爆了。

所以服务端还要做:

  • 限频
  • 合包
  • 降采样
  • AOI 裁剪
  • 优先级发送

不要把“网络差”全怪给运营商。


七、网络差时要不要降低发送频率

通常要。

因为在差网络环境下继续高频发送,常见结果是:

  • 队列堆积
  • 更多丢包
  • 更严重的乱序
  • 更长的恢复时间

更合理的自适应策略通常是:

  • 网络好时,高频同步
  • 网络一般时,适度降频
  • 网络差时,只保留高优先级消息

也就是说:

网络变差时,不是“拼命发”,而是“更有选择地发”。


八、一个更实用的处理框架

8.1 表现层

处理目标:

  • 不要让玩家看到明显卡顿和瞬移

常见方法:

  • 插值
  • 外推
  • 抖动缓冲
  • 平滑校正

8.2 传输层

处理目标:

  • 区分可靠消息与非可靠消息
  • 能统计丢包和 RTT
  • 能对关键消息做重传

常见方法:

  • 序列号
  • ACK / NACK
  • 重传队列
  • 丢包率统计

8.3 逻辑层

处理目标:

  • 保持服务端权威
  • 控制状态漂移
  • 避免旧状态反向污染新状态

常见方法:

  • 时间戳校验
  • 状态版本号
  • 过期包丢弃
  • 定期权威回写

九、几个常见误区

9.1 误区一:有重传就够了

不对。

重传解决的是关键消息可靠性,不解决视觉抖动,也不解决旧状态已经没价值的问题。

9.2 误区二:客户端平滑了就不用服务端校正

不对。

客户端平滑只是表现修饰,不是逻辑真相。

9.3 误区三:弱网就把所有消息都做可靠

这通常会更糟。

因为你会把大量低价值旧消息也一起补回来,进一步增加拥塞和队头阻塞。


十、总结

处理抖动和丢包的关键,不是单独押注某一个技术,而是先把消息分层,再分层处理。

更实用的原则通常是:

  • 关键事件要可靠恢复
  • 高频状态要允许丢旧保新
  • 客户端负责把画面变稳
  • 服务端负责把逻辑拉回权威

如果这几层边界清楚,弱网下即使体验会下降,系统仍然能保持可玩和可收敛。


参考资料

  • KBEngine GitHub - Network
  • Jitter Buffer
  • Forward Error Correction
在 GitHub 上编辑此页
最后更新: 3/20/26, 6:06 AM
贡献者: cuihairu