关于游戏同步的二三事

网络游戏同步法则

定义
通常来说的状态同步,即狭义上的状态同步,即在状态发生变化时将变动的数据同步给客户端
所谓帧同步其实是指以一帧数据为单位进行同步,但是通常来说我们认为的帧同步是 锁定帧同步(或者变种)
那么广义上来说,其实存在既是帧同步又是状态同步,即以帧为单位的状态同步

帧同步基础共识

实现目标

A状态 + N个操作 = 确定的B状态

同步频率

看到的一些同步频率都比较高2050次每秒,即20ms50ms
本地测试例子时 同步频率超过100ms就能明显的感觉到操作卡顿的感觉

什么是锁定帧

客户端将每帧执行的行为发送给服务端,服务端在每个步进结束时广播当前帧发生的变动,客户端演绎当前帧的变化
标准定义下,服务端在未收集到当前帧所有客户端的上报前是不会发送当前帧同步的,即所有客户端需要等最慢的返回,才能获取返回

改进乐观锁:
固定服务端的帧频,每个固定间隔将收到的客户端行为发送给客户端,并不确保收齐所有客户端当前帧的包,
优点: 避免因为最慢的客户端导致全局卡顿
缺点: 需要预测行为确保操作流畅,需要回滚修复预测偏差

预测帧尽量使得用户操作平滑

通过对接收数据进行buffer缓存,按帧滑动并根据延迟缩放执行窗口和执行间隔达到平滑效果
对于异常、回退帧添加插值帧,例如通过算法预测线性趋势下运行轨迹

帧回滚快进,消弭预测帧不正确导致的逻辑异常

帧回滚是如何实现的,或者说帧切片如何保存现场并恢复,注意这里说的帧切片是逻辑帧而不是渲染帧
如何校验是否一致,通用做法是将逻辑帧切片md5后发送到服务器,服务器进行广播

扩展思考:市面上多人游戏中,真的存在A物件运动会影响到10个以上其他物件运动的例子嘛?即在物理引擎展示中的子弹撞击方块山时的情景。如果存在是如何保存恢复逻辑帧的

为什么大多数都是UDP,而不是TCP

多数例子都是通过udp传输的,通过业务帧序列号保证前端能感知到丢包,并且额外发包降低丢包率(例如每n帧有一帧关键帧,关键帧内包含1~n帧的内容)
不过王者荣耀第一个测试版本也是tcp的,所以目前来说先用tcp先上

那么为什么不是tcp呢?

  • tcp通信本身在网络层存在拥塞控制,在网络状况不好的情况下会导致延迟稍大的情况引发更大的雪崩效应(可以通过切换拥塞算法或者修改TCP_NODELAY缓解,但是无法彻底避免)
  • tcp本身时最求完全可靠和顺序性的,因此,丢包后会持续重传直至该包被确认,否则后续包也不会被上层接收,且重传采用指数避让策略,决定重传时间间隔的RTO (Retransmission Timeout)不可控制,Linux内核实现中最低值为200ms

可能存在的一些TCP优化方案:

  • 切换拥塞算法bbr或者修改TCP_NODELAY
  • 基于UDP定制传输层协议,引入顺序性和适当程度或者可调节程度的可靠性,修改流控算法。适当放弃重传,如:设置最大重传次数,即使重传失败,也不需要重新建立连接。比较知名的TCP加速开源方案有:quic、enet、kcp、udt。其中,quic是源自google的TCP替代方案,其主要目的是为了整合TCP协议的可靠性和UDP协议的速度和效率,其主要特性包括:避免前序包阻塞、减少数据包、向前纠错、会话重启和并行下载等,然而QUIC对标的是TCP+TLS+SPDY,相比其他方案更重,目前国内用于网络游戏较少。kcp的作者是国内优秀开发者,社区也发展良好,kcp的作者和社区开发者对enet、kcp、udt做了性能测试,详情可参见:https://github.com/skywind3000/kcp/wiki/KCP-Benchmark, 从测试情况可以看到,kcp表现不错,其次是enet,表现最差的是udt。

实现一个帧同步游戏我们需要什么

物理引擎至少需要满足以下两点
根据冲量驱动对象移动
碰撞分离,两个对象重叠后如何根据冲量,位置,时间信息计算出不分离后的位置

定点数运算,即一个数值稳定的物理引擎

拥有一个稳定的tick驱动物理引擎, 注意实现过程中帧会被其他逻辑拖累,我们需要一个独立并且稳定的帧频,这是物理引擎结果一致性的必要条件

增加udp通信 不是所有情况下udp都是通的,只有在udp连上的时候采用udp 因此传统做法都是先建立tcp

伪随机数

提高同步频率,尽量不要低于物理引擎渲染帧的3倍

同步逻辑参考:

Author

Nevermore

Posted on

2022-01-07

Updated on

2024-02-21

Licensed under