UDP帧同步核心原理

  3.2 游戏引擎技术
  • 本博客总结自网络公开课
  • 开发工具:Unity/服务器
  • 开发语言C#/(C++ Lua)

帧同步如何同步

  • 帧同步:服务器把玩家操作同步给所有玩家,其他玩家在本地客户端根据服务器发过来的操作来推进游戏。同样代码+同样输入->同样结果
  • 优点:实时性很好
    缺点:所有计算放在客户端,容易作弊(即逻辑和单机游戏没有区别),每次同时同步的玩家不能太多
  • 原理:
    1、服务器:每隔一段时间,采集玩家的操作并发送给所有的客户端,并继续采集下一次的操作,等到下一次的发送时间一到,再次发送数据。
    2、客户端:收到服务器操作,计算游戏逻辑,上报下一帧操作给服务器
  • 帧同步:服务器每隔多少时间,向客户端发送一次操作比较合适?
    上限:网络传送时间
    下限:不影响用户体验的情况下的最小数目,人的反应时间一般在50-100ms,即1000ms/50-100->[20,10]即10-20帧为一般的下限。
  • 带宽承受能力如何计算:假设为一个5V5对战游戏
    1秒—->平均每帧,每人6个字节,摇杆—>角度(0, 360,2个字节),1个字节(256种不同技能)
    16* 10* 15 * 500 = 1,200,000 —> 1.2MBytes;—> 1.2M* 8= 9.6M===>10MBlt带宽;

帧同步使用TCP还是UDP

  • TCP:准确,丢包重传机制
    UDP:高效,可能丢包、乱序
  • 通常情况下TCP和UDP都可以使用,但采用UDP主要应对网络波动。
    TCP在一个链路传输时,如果遇到网络波动,那么后续帧数据只能等待前面的数据传输完成才能继续传输,会造成延迟。
    UDP在传输时会采用不同链路发送,且没有检测机制,因此在某个帧因为网络波动没有传输到的时候,后续帧数据仍可继续进行传输。

帧同步的流程详解

  • 服务器: —>比赛对象—>房间内;
    (1)服务器上每个比赛对象,都又一个成员frameid,保存了当前比赛,下一帧要进的id; frameid = 1;
    (2)我们在服务器上定一一个数据结构match_frames,用来保存我们所有的玩家的每帧的操作;
    match_frames作用:录像回放,断线重连,不同步的情况,有无作弊(调试作用),UDP丢包时序问题要补发给客户端,因此首先要保存起来;
    (3) next_frame_opt:每帧服务器将采集来的客户端的操作,都存放在这里:frameid = self.frameid, {有操作玩家的操作,有操作玩家的操作,有操作玩家的操作}
    (4)服务器上启动一个定时器,每隔66MS触发一次on_logic_frame;
    (5)保存我们当前的操作,到match_frames
    (6)遍历每个玩家,每个玩家发送我们的帧操作;
    (7)服务器进入下一帧: self.frameid = self.frameid+ 1
    (8)服务器进入采集下一帧模式,清空上一帧的操作;
    (9)发送服务器认为这个玩家还没有同步的帧,每个玩家对象,sync_frameid,记录当前这个客户端,已经同步到了多少帧;即从sync_frameid+1开始发送到服务器最新的帧—> UDP丢包和时序问题,补发我们的帧;
    (10)采用UDP —》将我们100帧的数据包发送出去[100帧:有可能有需要补发的之前的帧数据,此时发送顺序要从那一帧开始[99帧,100帧]]
  • 客户端:
    (11)客户端,通过网络收到帧同步的数据包以后,调用on_logic_update接收函数
    (12)每个客户端,也都会有一个sync_frameid,记录一下当前你这个客户端真正已经同步到哪个帧;
    (13)如果收到的帧ID,小于客户端已经同步过的帧id,直接丢弃这个帧;因为UDP有后发先到以及先发后到的情况(时序问题),譬如发送的100帧快于99帧先到,此时100帧的数据包中因为服务器记录了sync_frameid,所以一定会补发99-100帧的数据,因此后到的99帧数据就需要舍弃避免重复计算。
    (14)如果上一帧的操作不为null,那么这个时候,我们处理下一帧之前一定要先同步上一帧的结果;
    确保处理下一帧之前也都是同步的:在播放动画的帧与帧之间,我们会出现时间的差异,会导致位置等不同步;logic_pos: 66ms —>迭代计算出新的位置和结果;同一都以66ms来迭代;
    帧同步:每帧都同步,处理下一帧之前,每帧都要同步;—>同样的输入—》同样的输出;
    (15)跳帧:快速的同步完过时的帧(即直接进行逻辑计算,跳过动画表现等内容),直到最新的帧: 98,帧: [99,100]
    (16)控制我们的客户端,来根据操作,来播放动画,更新我们的逻辑推进;
    (17) capture_player_opts:采集自己的操作,上报给服务器,你认为的下一帧(譬如服务器进行到100帧,但客户端仍在上报98帧的内容)next_frame.frameid = this.sync_frameid + 1;
  • 服务器接受客户端消息:
    (18)收到玩家的操作,更新服务器上认为玩家已经处理的帧id;
    (19)如果收到玩家操作的帧ID,next frame_opts.frameid不等于马上要触发的帧ID;收到玩家过时的炒作;
    (20)保存玩家的操作,等待一下帧的触发—–> Goto到逻辑4;

如何克服UDP的时序和丢包问题

  • 客户端:丢包,服务器会补发丢掉和没有到的帧。
  • 服务器:丢包(某一帧)无严重影响

LEAVE A COMMENT