牛骨文教育服务平台(让学习变的简单)
博文笔记

Go游戏服务器开发的一些思考(十一):IO游戏同步

创建时间:2017-08-05 投稿人: 浏览次数:1349

概要

说到IO游戏,自然要提到如何同步。好的同步是IO游戏成功的基石。

同步分为2类:帧同步和状态同步。本文主要考察状态同步。

好的同步

如图,第N次主循环后,服务器会把状态发给客户端。客户端在服务器N+1次主循环前,把操作指令发给服务器。

从客户端下达操作指令,到服务器下次主循环开始前,2端的状态是一致的。

这是一次非常成功的同步。

图1

坏的同步

如图,操作指令在第N+1次主循环执行后,才到达。服务器端的状态已经发生了变化。从而导致本次操作指令执行失败。造成2端不一致现象。

比如,第N次主循环,发给客户端的状态,某客户端判断他能给角色B一个勾拳。但是勾拳的操作指令到达服务器前,服务器的第N+1次主循环执行后,角色B已经离开了。于是客户端可以使用勾拳;而服务器则勾拳执行失败。

这就是一次坏的同步。

图2

主循环间隔

上图看似把 主循环间隔 变的很大,就可以解决同步问题。

实际上,受到以下因素的制约:

  • 网络延时

    由于服务器收到操作指令 在 下次主循环之前,才是一次成功的同步。考虑到网络延时,理想状态是 主循环间隔>=网络延时

    网络 延时 主循环间隔
    单机 <1ms >=1ms
    局域网 <1ms >=1ms
    wifi 10ms-50ms >=50ms
    4G 10ms-50ms >=50ms
    3G 100ms >=100ms
    2G

    (ps. 网络及延时数据来源于 http://mp.weixin.qq.com/s/0U1dk-Rl78SS6QRuqAR_Kg 文中所述)

  • 单服务器承载人数

    单服务器承载人数越多,意味着 主循环花在运算上的消耗时间也越多。主循环间隔的估算公式就变成了 主循环间隔>=运算消耗时间+网络延时

    假如我们IO游戏的目标是让wifi、4G玩家有很好的体验。设置主循环间隔为100ms。那么留给服务器的运算时间最多是50ms。50ms能算多少个人,就是它的承载人数上限。显然如何精简游戏算法,也是一个重要课题。

  • 游戏操作灵敏度

    主循环间隔 同时 限制了玩家有效操作最小时间(不知专业术语叫什么)。举例说明,下个游戏主循环执行之前,服务器收到 3个操作指令,那么计算下个游戏主循环时,前2个操作指令将会被丢弃。

    主循环间隔的估算公式就变成了:有效操作最小时间>=主循环间隔>=运算消耗时间+网络延时

    从感官上来说,就是主循环间隔越大,操作角色时的反应越慢。

网络抖动

网络理想状态下,以上内容即可解决游戏同步问题。然而网络是抖动的。同步信息到达客户端不可能精确到等间隔。对客户端来说,如何让画面平滑是个挑战。

这里提出4个优化处理:
* 插值逼近
* 预备动作
* 引入硬直
* 丢包处理(UDP丢包章节介绍)

插值逼近

由于网络抖动,通过插值来平滑靠近目的点。(客户端知识,细节不做深究)

预备动作

如下图,我们来看红色线段。这段时间是 客户端发出操作指令(如扔炸弹),到获得服务器返回状态的等待时间。

由于网络原因,同步状态到达客户端的时间是有所差异的

为了有统一的手感、以及画面表现的连续性。一般会为每个指令制作预备动作。

如 扔炸弹,可能会有一个手臂抬起的预备动作;行走 则 预备动作 就是 行走 等等。

图4

注意:如果一个包到达时间,远超过预备动作的时间,那么画面也是很尴尬的。但是引入预备动作,可以让大部分情况下看起来流畅。

引入硬直

如果是玩家1个人的行为,则客户端程序可以根据玩家上次的操作指令来预测处理画面。

如果玩家A的行为是连续的、且作用玩家B。那么玩家B不确定性的操作指令。可以让2个终端的画面显示不一致。

再举勾拳的例子。A玩家在使用勾拳过程中,B玩家也会下达操作指令。让B玩家受击后硬直,A客户端可以完整预测B玩家行为。反之亦然。从而让2个客户端画面的这次攻击表现完全一致。

网络阻塞

网络阻塞,导致多个包延迟到达等。画面必定会卡顿。
使用UDP代替TCP发送状态包。可以改善这种情况。

网络阻塞,只影响该玩家。

使用UDP

应当尽可能的把一次主循环下来,所有需要同步的状态放进一个UDP包;且是状态全量。

UDP包大小不应该超过548字节。

UDP包应该给予编号。便于客户端推算该包是属于哪些帧的。

编号0开始,客户端以零帧开始,那么编号对应的开始帧数为:主循环间隔 / (1000 / 客户端帧率)x 编号。共 主循环间隔 / (1000 / 客户端帧率) 帧。(除不尽情况? 客户端内容不深究)

对时、对帧

上节提到 客户端从UDP包中的编号,来算该包位于哪些帧上。

要实现这个功能,前提条件是 客户端 、 服务器的时间是一致的。

比如客户端可以从服务器那边获取到 战斗是从”xx年xx月xx日 xx秒xx毫秒” 开始的。

客户端可以根据这个时间点,算出当前最新UDP编号;算出战斗中,客户端已经刷了多少帧。

因此 客户端、服务器需要对时。

常见的对时方法如: 获取 客户端、服务器间的时间差。

具体方法可以参见以下文章:

http://blog.codingnow.com/2006/04/sync.html

UDP丢包

两种方式均可

  • 处理一:
    发现没有 可以根据上个指令来预测画面。
    等下个UDP包到达后,做矫正。大部分情况,画面表现是被拉到某处。

  • 处理二:
    卡顿,并向服务器请求丢失的UDP包。收到后,快进至当前帧。

只影响该玩家。

状态同步总结

  • 目标是 wifi 、4G网络下,IO游戏有很好的同步。

  • 主游戏循环间隔为 100ms。

  • 缓存玩家操作指令,主游戏循环执行时,一次性运算所有玩家操作指令。

  • 100ms内发一次状态全量UDP包。

    • 玩家状态(站立、行走、施放技能(技能ID)、受击、死亡 等等)
    • 坐标
    • 速度
    • 方位
    • 血量
  • 非战斗相关的,采用普通状态同步

    • 如 分布到场景中的众多食物、小星星 之类的同步
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。