网络同步学习(状态同步,帧同步)

参考:【网络同步】浅析帧同步和状态同步 - 知乎 (zhihu.com)

网络同步 = 实时的多端数据同步 + 实时的多端表现

帧同步(LockStep)

基本原理

  • 帧同步的战斗逻辑在客户端
  • 在帧同步下,服务端只转发操作,不做任何逻辑处理。
  • 客户端按照一定的帧速率(理解为逻辑帧,而不是客户端的渲染帧)去上传当前的操作指令,服务端将操作指令广播给所有客户端,
  • 当客户端收到指令后执行本地代码,如果输入的指令一致,计算的过程一致,那么计算的结果肯定是一致的,这样就能保证所有客户端的同步,这就是帧同步。

缺陷

  • 由于帧同步战斗逻辑都在客户端,服务器没有验证,带来的问题就是外挂的产生(加速、透视、自动瞄准、数据修改等)
  • 网络条件较差的客户端会影响其他玩家的游戏体验。(优化方案:乐观帧锁定、渲染与逻辑帧分离、客户端预执行、指令流水线化、操作回滚等)
  • 不同机器浮点数精度问题、容器排序不确定性、RPC时序、随机数值计算不统一

乐观帧锁定

针对传统严格帧锁定算法中网速慢会卡到网速快的问题,实践中线上动作游戏通常用“定时不等待”的乐观方式在每次Interval时钟发生时固定将操作广播给所有用户不依赖具体每个玩家是否有操作更新

  1. 单个用户当前键盘上下左右攻击跳跃是否按下用一个32位整数描述,服务端描述一局游戏中最多8玩家的键盘操作为:int player_keyboards[8];
  2. 服务端每秒钟20-50次向所有客户端发送更新消息(包含所有客户端的操作和递增的帧号):
  3. update=(FrameID,player_keyboards)
  4. 客户端就像播放游戏录像一样不停的播放这些包含每帧所有玩家操作的 update消息。
  5. 客户端如果没有update数据了,就必须等待,直到有新的数据到来。
  6. 客户端如果一下子收到很多连续的update,则快进播放。
  7. 客户端只有按键按下或者放开,就会发送消息给服务端(而不是到每帧开始才采集键盘),消息只包含一个整数。服务端收到以后,改写player_keyboards

状态同步

同步各个客户端的状态,保证每一次操作之后的状态是一致的

通过开发服务端程序,把用户的操作作为输入实时上传到服务端,服务端通过计算返回结果给各个客户端,这样的过程就是状态同步。

基本原理

  • 状态同步的战斗逻辑在服务端
  • 在状态同步下,客户端更像是一个服务端数据的表现层
  • 一般的流程是
    • 客户端上传操作到服务器,
    • 服务器收到后计算游戏行为的结果,然后以广播的方式下发游戏中各种状态,
    • 客户端收到状态后再根据状态显示内容。

如今的状态同步:增量同步、RPC(远程过程调用)两种同步手段。

目前状态同步多用于CS架构,客户端通过RPC向服务器发送指令信息,服务器通过属性同步(增量状态同步)向客户端发送各个对象的状态信息。我们才有预测回滚、延迟补偿、插值等优化方式

缺陷

  • 状态同步做回放系统的话会是个灾难。
  • 延迟过大、客户端性能浪费、服务端压力大
  • 对带宽的浪费。对于对象少的游戏,可以用快照保存整个游戏的状态发送,但一旦数量多起来,数量的占用就会直线上升。(优化:增量快照同步,协议同步指定数据)

区别(帧同步 状态同步)

  • 最大的区别就是 核心逻辑写在哪里,状态同步的战斗逻辑写在服务端,帧同步的战斗逻辑写在客户端
  • 状态同步比帧同步流量消耗大,比如有一个英雄有100条属性,状态同步需要同步属性,而帧同步不需要同步属性
  • 帧同步的回放&观战比状态同步好做得多,因为只需要保存每局所有人的操作就好了,而状态同步的回放&观战,需要有一个回放&观战服务器,当一局战斗打响,战斗服务器在给客户端发送消息的同时,还需要把这些消息发给放&观战服务器,回放&观战服务器做储存,如果有其他客户端请求回放或者观战,则回放&观战服务器把储存起来的消息按时间发给客户端。

区别表格

属性帧同步(LockStep)状态同步
确定性严格确定允许小误差,定时纠正误差数据
表现与响应速度传统严格帧锁定要等其他客户端消息全部到达,响应比较慢;乐观帧锁定可以做到本地立刻响应,但是需要回滚的时候,体验就没那么好了一般会做预测,可以做到立刻响应。不做预测的话,响应时间是一个往返时间(RTT)
带宽与流量带宽随人数增加而增加,不适合MMO需要发送各种状态数据,带宽占用比较高。可以通过压缩、裁剪、增量等方式优化。人数较少时候不如帧同步剩流量
网络延迟适应性要求较低的延迟。如果延迟较高,所有玩家体验都不好。即使采用乐观帧锁定优化,高延迟下也容易产生卡顿适应性较高,方便做各种插值优化。当然高延迟下,也容易产生位置突变
开发难度初期开发减法,框架容易实现,但是后期解决bug和完善系统很困难。比如浮点数、随机数、执行顺序导致计算结果不一致,问题很难排查框架比较复杂,客户端服务端一套代码,每个功能都需要客户端服务端联调。问题定位比较容易。也会出现时序问题
玩家数量适合少量的玩家,比如ACT、MOBA可多可少
跨平台不适合跨平台,会有浮点数问题,可以用定点数来将误差控制在一个可接受范围,同时可以定时纠正结果适合。有权威服务器
反外挂P2P架构不适合反外挂,如果引入战斗服务器来校验各个客户端结果,可以解决常见外挂,但是透视和全图视野防不了与服务器加入校验机制,可以起到比较好的反外挂效果。但是一样防不了透视外挂
中途加入和断线重连比较复杂。可以在断线的时候,通过快捷播放服务器同步的帧数据来快速跟上游戏容易。由于实时记录了各个对象的状态信息,所以重连的时候,直接创建这些对象,并同步信息即可
性能(客户端)客户端要跑完整逻辑,还要执行渲染逻辑,开销比较大可以灵活优化,客户端跑较少逻辑
回放(离线)本身收集了所有玩家的输入信息进行逻辑推进,天然支持回放,且回放文件比较小可以支持回放,但是逻辑比较复杂,需要不断记录状态信息,同时回放时候需要读取合适的时间。回放文件大
回放(实时)比较复杂,客户端需要本地对全场状态进行序列化,才能回到目标时间。播完回放后还需要加速追上实时游戏状态相对容易,可以方便的记录快照信息,并按照录制内容随时播放

网络传输协议的选择

老话题,选择 TCP还是 UDP,答案是大部分时候,TCP打开 NODELAY即可,现在网络情况好了很多,没必要引入新的复杂度。即便是“帧锁定算法”上线的多人实时格斗游戏,也有在用 TCP跑着的。

当然,等到你的游戏发布出去了,开始挣钱了,你想改进你的游戏效果,特别是高峰期的卡顿比例(需要收集客户端统计),那么你可以使用 UDP来改进,《街霸4》和《英雄联盟》都是使用 UDP的,比如你可以使用 libenet(英雄联盟用的)。不过 libenet所采用的传输技术,是上世纪的标准 ARQ做法了,《街霸4》所采用的传输技术远远高过 libenet,如果你想采用更为现代的传输技术,赢得更低延迟的话,可以使用“快速传输协议-KCP”,被再若干上线项目和开源项目使用的协议,效果远远 PK libenet。

在使用 KCP时,你可以用在你 TCP的基础上,再登陆时服务端返回 UDP端口和密钥,客户端通过 TCP收到以后,向服务端的 UDP端口每隔一秒重复发送包含握手信息,直到服务端返回成功或者失败。服务端通过 UDP传上来的密钥得知该客户端 sockaddr对应的 TCP连接,这样就建立 TCP连接到 UDP连接的映射关系。为了保持连接和 NAT出口映射,客户端一般需要每 60秒就发送一个 UDP心跳,服务端收到后回复客户端,再在这个 UDP连接的基础上增加调用 KCP的逻辑,实现快速可靠传输,这样一套 TCP/UDP两用的传输系统就建立了。

中国的网络情况比较特殊,会存在有些网络 UDP连接不上的情况,因此都是先连接 TCP,然后试图 UDP,UDP不通的情况下,退回 TCP也能正常游戏,一旦 TCP断开,则认为 UDP也断开了。

不果归根结底,还是先上 TCP,再根据自己游戏的特点和是否出现传输问题,选择 UDP。

网络同步优化技术

我们进行网络同步优化,主要是想对网络延迟进行优化

  • 在单机游戏,我们从按下按键到画面响应,中间经历了:采样延迟、渲染流水线、刷新延迟、显示延迟
  • 在网络游戏,我们从按下按键到另一个机器收到指令,则会经历一个极为耗时的网络延迟
  • 网络延迟其实包括处理延迟、传输延迟(主要延迟)、排队延迟、传播延迟

表现优化

弱化玩家对于延迟的感受

插值优化

在状态同步中,由于客户端每次收到的是其他角色的位置信息,为了避免位置突变,客户端会采用插值技术,让表现更平滑,而不是跳帧。

  • 内插值的目的是解决客户端离散信息更新导致的突变问题。
  • 外插值的目的是解决网络延迟过大或者抖动导致间歇性收不到数据的卡顿问题。
  • 两种方案不冲突,可以同时采用

具体应用的时候,可以用使逻辑帧和渲染帧分离

客户端预测+回滚

  • 预测的目的是让玩家输入后,本地立刻收到反馈,提高游戏体验
  • 回滚是为了保证服务器的权威性

关于预测,就是本地先执行。

把玩家本地预执行的指令都记录好时间戳并存放到一个Move_Buffer列表里(类似滑动窗口)。在添加了时间戳条件下,收到了一条过时的服务器位置数据。

  • 如果玩家本地预测结果与服务器几乎一致,服务器回复一个ACKMOVE。客户端把Move_Buffer列表中对应数据清除
  • 如果预测结果与服务器不一致:需要本地回滚到服务器指定位置,把错误时刻后面的Move_Buffer列表指令执行一遍

把玩家本地预执行的指令都记录好时间戳并存放到一个Move_Buffer列表里(类似滑动窗口)。在添加了时间戳条件下,收到了一条过时的服务器位置数据。

  • 如果玩家本地预测结果与服务器几乎一致,服务器回复一个ACKMOVE。客户端把Move_Buffer列表中对应数据清除
  • 如果预测结果与服务器不一致:需要本地回滚到服务器指定位置,把错误时刻后面的Move_Buffer列表指令执行一遍

延迟对抗

延迟对抗主要是想弱化玩家对延迟的感受

延迟补偿(Lag Compensation)

延迟补偿就是弥补客户端到服务器同步延迟的一项技术,核心就是服务器在指定时刻对玩家角色进行位置回滚与计算处理。

实现方式:服务端会定时记录所有玩家位置。假设客户端到服务端的延迟为Xms,当服务端收到客户端操作后,服务端使用记录的Xms前所有玩家位置来计算是否命中,从而抵消延迟带来的问题

延迟补偿缺陷:使用延迟补偿要考虑游戏类型。不适合ACT网游

命令缓冲区

把远端数据缓存在一个buffer里面,然后按照固定频率从buffer里面取,可以解决客户端卡顿以及网络抖动问题。

不过缓冲区和延迟是有冲突的,缓冲区越大,证明我们缓存远端数据越多,延迟越大!

假表现

真实开发环境下,我们可以根据游戏的具体情况,加一些前摇动画来掩盖延迟。比如在无敌状态前播一个前摇动画等。

丢包对抗

丢包对抗主要也是弱化玩家对延迟的感受

使用TCP
TCP不会丢包,对于延迟不敏感的游戏,优先采用TCP
冗余UDP数据包

一次性发送多个帧的数据来对抗丢包。

对于数据量比较小的游戏,可以采用冗余UDP的方案,即后续的UDP包会冗余一定量前面已发送的UDP包。

带宽优化

带宽优化的目的是减小客户端及服务器的同步压力。

同步对象裁剪

剔除不需要同步的对象。

常见的裁剪方式有很多:SOI(Spheres Of Influence)、静态区域(把场景划分成多个区域,不在一个区域不同步)、视椎裁剪、八叉树裁剪、AOI(Area Of Interest)

分区、分房间

对于大型MMO来说,这是常见手段。将不同玩家分散到不同场景(不同服务器),这样可以减小服务器压力,降低延迟。但是也带来了跨服数据同步的问题

数据压缩和裁剪

坐标和旋转是我们场景的同步内容,但很多数据是不需要同步的。比如旋转的话,如果只需要一个Y轴旋转,我们可以传个float,而不是vector。比如我们可以压缩浮点数的精度。

然后对于状态同步,可以采用增量发送的方式来减少数据量

减少遍历和更细粒度的优化

对同步对象做优先级划分,发送频率调整等。

帧率优化

提升帧率

不同游戏的性能瓶颈不同,需要具体分析,包括:内存问题(GC、频繁申请与释放)、IO(资源加载、频繁读写文件,网络包发送频率过大,频繁读取数据库)、逻辑问题(大量遍历循环、无意义的Tick、过多的锁、高频率Log)、AI(寻路耗时)、物理问题(复杂模拟、碰撞检测)、语言特性等,客户端还有各种复杂的渲染问题(Draw Call太多,半透明,动态阴影),不断地优化,才能把帧率提高和稳定

保持帧率稳定和匹配

保持服务器帧率稳定

分摊计算压力

对于MMO这种服务器压力比较大的游戏,可以把一些复杂计算转交给客户端进行计算(甚至计算后返还给服务端),比如物理、寻路、AI等。

再谈网游同步技术 - Skywind Inside   参考

 RTT,数据往返一周的毫秒时间

参考,拓展阅读:

网络游戏同步法则 - Skywind Inside

帧锁定同步算法 - Skywind Inside

影子跟随算法(2007年老文一篇) - Skywind Inside

网络游戏同步法则

1. 基本情况:
(A) 网络性能指标一:带宽,限制了实时游戏的人数容量
(B) 网络性能指标二:延时,决定了实时游戏的最低反应时间

算法比较:

1. 帧间同步:不同客户端每帧显示相同的内容,键盘/时钟数据传到服务器,服务器确认后所有终端做出响应,多用于局域网游戏,比如红警(需要等待客户端),街霸II的网络版(360),网速要求高,复杂度低。参考 LockStep 和 TimeWrap 算法,以及 帧锁定算法

2. 插值同步:不同客户端显示不同步,但是状态同步,常见的Dead Reckoning(或叫导航插值),效果好,但复杂度高。常见于竞速类游戏和 FPS游戏。

拓展阅读:

《守望先锋》回放技术-阵亡镜头、全场最佳和亮眼表现-腾讯游戏学堂 (tencent.com)

  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值