网络游戏同步机制:Lockstep、状态同步与延迟补偿
在延迟、带宽、一致性和反作弊之间,比较 lockstep 与状态同步,并解释客户端预测、服务端和解、插值和 lag compensation 如何协作。

网络游戏里没有一个天然共享的“现在”。服务器和每个客户端都有自己的时钟、状态和输入队列;数据包会延迟、抖动、丢失和乱序,设备还可能以不同速度运行。
同步的目标不是让所有机器在物理上的同一瞬间拥有完全相同的画面,而是在可接受延迟下,让关键游戏结果收敛到服务器认可的状态,同时为玩家提供连续、及时的反馈。
先区分三个频率
讨论同步前,需要把三件常被都叫作“帧”的东西分开:
- Simulation tick rate:游戏逻辑和物理每秒推进多少次;
- Network send rate:状态或输入每秒发送多少次;
- Render frame rate:客户端每秒绘制多少帧。
它们可以完全不同。例如服务器以 60 Hz 模拟、20 Hz 发送快照,客户端以 144 FPS 渲染。渲染帧之间的运动由预测和插值补齐,而不是等待每个网络包。
两种基本同步思路
| 维度 | Lockstep / 输入同步 | Snapshot / 状态同步 |
|---|---|---|
| 网络主要传输 | 玩家输入 | 权威状态或状态增量 |
| 逻辑执行 | 每个参与模拟的节点 | 主要在权威服务器 |
| 带宽 | 通常较低 | 随实体数和快照频率增长 |
| 关键要求 | 模拟必须确定性 | 状态压缩、插值和纠错 |
| 重连/观战 | 回放输入或加载检查点后追帧 | 获取最新完整快照后继续 |
| 常见场景 | RTS、部分 MOBA/格斗游戏 | FPS、MMO、动作游戏 |
现实项目经常混合两者:战斗核心采用确定性输入同步,外围系统走状态复制;或者服务器权威模拟角色,客户端本地预测自己的移动。
Lockstep:同步输入,重复同一场模拟
Lockstep 的思想是:所有节点从同一初始状态出发,在相同 tick 使用相同输入,就应该得到相同结果。服务器负责收集并排序玩家输入,再广播某个 tick 应执行的命令。

它节省带宽,也天然适合回放:保存初始状态、随机种子和输入流即可重演比赛。但“输入相同就会一致”有很强的前提:
- 随机数生成器及种子一致;
- 浮点运算、物理引擎和迭代顺序在各平台确定;
- tick 步长固定;
- 容器遍历、线程调度等不会引入非确定顺序;
- 丢失输入可以按时补齐或所有节点共同等待。
任何一点不满足都可能造成 desync。工程上通常定期计算状态校验和,并在必要时加载权威检查点修复。
纯 lockstep 的交互延迟至少受到最慢输入的影响。RTS 可以通过固定 input delay 换取稳定;格斗游戏更常使用 rollback netcode:先预测远端输入并立即推进,预测错误时回滚到历史状态重新模拟。
状态同步:服务器决定真实世界
状态同步由服务器运行权威模拟,并周期性发送实体快照或增量。客户端不是简单的“显示器”,它仍要负责预测、动画和视觉效果,但不能最终决定伤害、位置、库存等关键结果。
快照不可能覆盖每个渲染帧,因此客户端通常让画面落后服务器少量时间,在两个已知快照之间插值其他玩家。这样增加了一点显示延迟,却能吸收网络抖动,避免角色跳跃。
对于本地玩家,等待服务器往返后再移动会感觉迟钝,所以要加入两套机制。
客户端预测
客户端收到本地输入后立即模拟自己的移动,同时把带序号的输入发给服务器。玩家先看到预测结果,不需要等待一个 RTT。
服务端和解
服务器处理输入后返回权威状态,并告知已经确认到哪个输入序号。客户端收到后:
- 回到服务器确认的位置;
- 丢弃已经确认的输入;
- 重新执行尚未确认的本地输入;
- 对视觉误差平滑修正,或在误差过大时直接纠正。
这就是 prediction + reconciliation。它允许即时反馈,又不把最终裁决权交给客户端。
FPS 为什么还需要延迟补偿
射击时,玩家瞄准的是自己屏幕中稍早的敌人位置。如果服务器只按“现在”的位置判断,网络延迟高的玩家会频繁看到子弹穿过目标。
Lag compensation 的常见做法是:服务器保存短时间的历史碰撞状态。收到带时间信息的射击请求后,把相关目标临时回溯到射手当时看到的位置进行命中判定,再恢复当前世界。
这仍然是服务器判定,不是客户端直接上报“我打中了”。服务器要限制可回溯窗口,校验射速、弹药、视角与移动,否则延迟补偿本身会成为作弊入口。
它也带来体验冲突:射手可能合理地命中,而被击中的玩家已经在自己的画面里躲到墙后。系统只能在射手公平、受击者公平和延迟容忍之间选择权衡,不存在让所有观察者同时满意的绝对方案。
一个时间线例子
假设客户端 A 在本地 tick 100 按下向前:
- A 立即预测移动到 ,发送
input#41; - 服务器稍后收到该输入,在自己的 tick 上模拟,得到权威位置 ;
- 服务器广播快照:
ack=41, x=10.0; - A 收到后发现误差 0.2,回到 10.0,重放
input#42、input#43; - 逻辑位置立即正确,渲染位置则用几帧平滑到新位置。
如果误差来自碰撞判断差异,客户端必须接受服务器结果;如果误差持续出现,则说明两边的移动代码、时间步或输入处理存在系统性不一致。
TCP 与 UDP 不是“可靠慢、丢包快”这么简单
TCP 提供可靠、有序字节流,但丢失一个包会阻塞后续数据交付,形成 head-of-line blocking。对“最新位置覆盖旧位置”这类时效性消息,等待旧包重传反而没有价值。
UDP 不提供可靠性、顺序和拥塞控制;选择 UDP 意味着应用层要自己处理:
- 序号、去重和乱序;
- 对关键消息的确认与重传;
- 分片与 MTU;
- 速率限制和拥塞响应;
- 加密、认证和防重放。
很多游戏在 UDP 上按消息类型选择可靠策略:聊天、结算必须可靠;高频位置快照可以丢旧保新。QUIC 虽基于 UDP 并支持多路流,但是否适合实时游戏仍取决于实现、平台和延迟目标。
同步方案与安全性
服务器权威能显著缩小作弊面,却不能让外挂消失。即使服务器计算伤害,客户端仍可以透视本地已收到的信息、自动瞄准、伪造输入节奏或利用协议漏洞。
Lockstep 客户端通常拥有更完整的模拟状态,信息型作弊风险更高;状态同步可以按可见范围裁剪数据,但服务器成本和带宽更高。反作弊不是同步模式附带的免费属性,而是信息最小化、权威校验、行为检测和客户端完整性保护的组合。
如何选择
- 强确定性、单位多、命令少、可接受输入延迟:优先考虑 lockstep;
- 强实时动作、世界复杂、需要权威反作弊:优先考虑状态同步;
- 本地操控必须立即响应:加入客户端预测与服务端和解;
- 远端实体要平滑:使用插值缓冲,必要时短期外推;
- 对历史视角判定敏感:评估受限的 lag compensation 或 rollback。
真正的设计单位不是“帧同步还是状态同步”这个二选一标签,而是每类状态的权威归属、允许的延迟、更新频率、纠错方式和作弊成本。