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 误区三:弱网就把所有消息都做可靠
这通常会更糟。
因为你会把大量低价值旧消息也一起补回来,进一步增加拥塞和队头阻塞。
十、总结
处理抖动和丢包的关键,不是单独押注某一个技术,而是先把消息分层,再分层处理。
更实用的原则通常是:
- 关键事件要可靠恢复
- 高频状态要允许丢旧保新
- 客户端负责把画面变稳
- 服务端负责把逻辑拉回权威
如果这几层边界清楚,弱网下即使体验会下降,系统仍然能保持可玩和可收敛。
