0. 先交代一下背景

白天刚从发版流水线里爬出来,晚上回到终端,翻上一篇 AOI 那篇下面的评论区。有老哥问:

主策天天嚷嚷着要《街霸》一样手感,要 MMO 级大规模同屏、还要跟《瓦罗兰特》一样搞掉包和外挂防御,团队架构被“状态同步”和“帧同步”来回撕裂……这俩到底在服务端怎么选?

直接答应写个不背八股,专治带病选型的扫盲避坑文。网络模型不是“发包格式”这么浅,根本本质是你把物理世界“时间线裁决权”押在哪一侧。

不搞玄学外链图,只认原生 Mermaid,所有时序逻辑和顶级工业同步结构全部一把画满——黑夜白天都不炸的硬核大图 :sakta_emoji_3:


1. 第一道门:根本世界观的碰撞(发车器 vs 时光机)

想想两种宇宙:

  • 帧同步(Lockstep):服务端就是个“按时发车器”,只发计数包,不判断世界状态
    客户端各自带着相同初始状态、收集来的输入,在自己小黑屋一集体算出来结果,理论上绝对一样。服务端只在每帧 Tick 收齐大家输入后广播出去,自己啥都不算。
  • 状态同步(State Sync):服务端是“权威时光机”,只认自己内存里的状态世界,客户端上报的所有“意图”都要被判决加工,结果全以服务端为准,其它统统都是客户端自娱自乐。

参考 Glenn Fiedler 这张典型模型图 [S1]:

MERMAID · 图表源代码
sequenceDiagram
    autonumber
    participant C1 as 客户端A
    participant S as 服务端
    participant C2 as 客户端B
    rect rgb(240,248,255)
    note over C1,C2: 帧同步 (Lockstep)
    C1->>S: 操作输入(Frame=102)
    C2->>S: 操作输入(Frame=102)
    note over S: Tick对齐封包,打包广播,不跑物理
    S-->>C1: 广播Frame数据
    S-->>C2: 广播Frame数据
    note over C1,C2: 客户端自行计算第102帧世界状态
    end
    rect rgb(255,245,238)
    note over C1,C2: 状态同步 (State Sync)
    C1->>S: 意图输入
    note over S: 跑完整物理
    S-->>C1: 下发状态快照/纠偏
    S-->>C2: 发裁剪快照
    note over C1: 如果预测偏差就回滚补正
    note over C2: 插值/补间播放远程角色
    end

> 💡 原则:帧同步传“因”(输入),状态同步传“果”(快照);谁能认定世界的“真理”,裁决权就在谁手里。


2. 第二道门:状态同步的工程骨架与三大深坑

市面主流射击/大逃杀/MMO 其实都用服务端权威状态同步 [S1],但能跑得顺的,必须搞定:

2.1 快照(Snapshot)与增量压缩(Delta Encoding)

不能死命每帧推全量树,带宽细分分分钟炸掉。最优做法:全量基线快照 + 增量环形基线。每链接自己追踪 LastAckedSequence,服务端按 Ack 做 Delta(全字段位掩码),未变的直接跳过。

2.2 客户端预测与纠错重演闭环

朴素模型下,输入到生效硬生生要走完整 RTT + 动画延迟,体验直线崩盘(比如100ms RTT + 100ms动画就是 200ms 迟滞)[S2]。

所以必须用客户端预测+对账重演闭环,直接 Mermaid 自绘完:

MERMAID · 图表源代码
sequenceDiagram
    autonumber
    participant Client as 客户端
    participant Server as 服务端
    rect rgb(230,245,255)
    Note over Client,Server: RTT+动画延迟=200ms硬延迟
    Client->>Server: 输入(右键)
    Note right of Server: 网络一程100ms
    Server-->>Client: 权威位置x=11
    Note right of Client: 网络回程100ms
    Note right of Client: 动画播放100ms
    Note right of Client: 总延迟=200ms
    end
    rect rgb(240,255,238)
    Note over Client,Server: 预测+重演闭环
    Client->>Server: #1(InputSeq=1)
    Note right of Client: 本地立马预测
    Client->>Server: #2(InputSeq=2)
    Server-->>Client: 状态x=11,带确认#1
    Note right of Client: 扔掉#1,只重演#2,保证收敛
    end

> 💡 方法论:服务端包里带已处理序号,客户端重演未被权威确认的输入,永远不会乱飞。

2.3 延迟补偿:倒流时光 + 环形缓冲区(Lag Compensation)

如 CS/Valorant 这种快节奏射击必须在服务端做时间倒流 [S1][S4]。即,每实体存一段环形历史(1s左右),玩家开火带“射击时间”,服务端回滚所有目标的 Hitbox 状态到对应时刻,做射线判定。

——但副作用:你明明“进墙”了,服务端回溯看你还在旧坐标,所以就有经典“掩体后中枪”物理悖论。

工业标准都是回滚200ms内,超了就不认,防止老赖高延迟作弊。[S1]


3. 第三道门:帧同步的确定性地狱与回滚

常见于 RTS/MOBA/格斗(星际、王者、GGPO)。极省服务端算力,但要无条件锁死“完全一致”!

3.1 浮点灾难与定点数强制

不同平台浮点实现差别小数点后7位都会放大成画面乱飞灾难。必须全逻辑定点(Q32.32 或 Q48.16),所有参与同步的东西都整型表征,离线导表阶段处理完。[S1]

3.2 无序遍历、多线程 & PRNG 地狱

  • 哈希表遍历必须全定序,有序字典/定序数组。
  • 随机生成器专人专种 seed,逻辑层/表现层彻底剥离。
  • 逻辑主循环必须单线程,不准乱调度。

3.3 逻辑帧/渲染帧解耦:Update & Lerp 插值

画面高刷必须插值展示,逻辑算出来N帧,渲染帧在前后一格间做Lerp,不然就是瞬移卡顿地狱。

3.4 现代帧同步救星:预测+Rollback

老式 Lockstep 木桶效应:只要一人卡全房间原地等。现代方案是丢包/延迟时直接预测,等真实输入包来再回滚-快进重演。

回滚要靠极小逻辑块和结构化内存快照,否则 mobile 端瞬间掉帧爆 GC。


4. 手术步骤与架构选型军规:一张思维决策网

别信玄学图片,原生 Mermaid 画工业级同步选型决策树,彻底拔掉透明外链。

MERMAID · 图表源代码
flowchart TD
    A["新项目网络同步选型"] --> B{"同局物理单位/实体数量级?"}
    B -->|"几千~上万单位(RTS/MMO)"| C["确定性帧同步 (Lockstep)"]
    B -->|"几十人/百人"| D{"是格斗/帧对抗还是大地图?"}
    D -->|"格斗/强对抗/极短延迟"| E["预测回滚帧同步 (Rollback)"]
    D -->|"射击/吃鸡/MMO"| F{"防外挂是否死线?"}
    F -->|"必须严格服务端权威"| G["状态同步 (State Sync+LagComp)"]
    F -->|"只要弱联网/无专服预算"| H["分布式权威/无服务端"]
    C --> I["死磕定点/全平台一致/自定义公式"]
    E --> I
    G --> J["专服加逻辑倒流/高昂CPU成本"]

血泪避坑军规

军规 1:权威一律在服务端

任何带“结果”的封包(如 My_Position_Is)全当作弊,判定唯一靠自己内存树。

军规 2:帧同步断线重连必须有快照

DO NOT 刷全输入,工业级每15秒存快照,只补齐断点后操作重播。

军规 3:状态同步要有 fail-open 降级

预测崩就滑动阻尼/平滑过渡,连续断包时允许外推,不让主循环因为网络死锁。

军规 4:只认包层原始时序

排 Bug 必看原始十六进制,别信日志美化后内容,哪怕只差一个空格/序列号都能掉进去。

搞明白是谁裁决“真理”,工程才能不炸服、不掉大坑 :sakta_emoji_84:😋


> 📚 参考来源:[Glenn Fiedler on Mas Bandwidth][S1], [Gabriel Gambetta][S2][S4]

[S1]: [redacted-url] [redacted-url] [redacted-url]

参考资料