如何使Quake 3等精密网络游戏的服务器-客户端时钟保持同步?


15

我正在研究2D自上而下的射手,并尽力复制类似Quake 3这样的网络游戏中使用的概念。

  • 我有一个权威服务器。
  • 服务器将快照发送到客户端。
  • 快照包含时间戳和实体位置。
  • 实体插值在快照位置之间,因此移动看起来很流畅。
  • 根据需要,实体插值会稍微“过去”发生,因此我们可以在多个快照之间进行插值。

我面临的问题是“时钟同步”。

  • 为了简单起见,让我们假设一下,在与服务器之间传输数据包时,延迟为零。
  • 如果服务器时钟比客户端时钟早60秒,则快照时间戳将比客户端本地时间戳早60000ms。
  • 因此,在客户端看到任何给定的实体进行移动之前,实体快照将收集并静置约60秒钟,因为客户端时钟要花很长时间才能赶上。

通过设法在每次接收快照时计算服务器时钟和客户端时钟之间的差异,我设法解决了这一问题。

// For simplicity, don't worry about latency for now...
client_server_clock_delta = snapshot.server_timestamp - client_timestamp;

确定实体插入插值的距离时,我只需将差值添加到客户的当前时间即可。但是,这样做的问题是,由于快照比其他快照更快或更慢地到达,这两个时钟之间的差异将突然波动,这将导致混乱。

如何才能使时钟足够紧密地同步,以使唯一可感知的延迟是硬编码用于插值的延迟,而该延迟是由普通网络延迟引起的?

换句话说,如何在时钟严重失步时防止插值开始得太晚或太早而又不引起混乱呢?

编辑:根据Wikipedia的说法,NTP可以用于在几毫秒内将互联网上的时钟同步。但是,该协议似乎很复杂,也许在游戏中使用会显得过分杀伤力?


有多复杂?这是一个请求和响应,每个都有发送和到达的时间戳,然后是一点数学运算就可以得出增量
棘手的怪胎2015年

@ratchetfreak:根据(mine-control.com/zack/timesync/timesync.html),“不幸的是,NTP非常复杂,更重要的是,收敛于准确的时间增量很慢。这使NTP 不适用于网络玩家期望游戏立即开始的游戏...”
Joncom

Answers:


10

在四处搜寻之后,似乎要同步两台或更多台计算机的时钟并不是一件容易的事。像NTP这样的协议做得很好,但据认为它很慢并且太复杂,无法在游戏中使用。另外,它使用的UDP对我不起作用,因为我正在使用不支持UDP的网络套接字。

但是我在这里找到了一个方法,它似乎相对简单:

它声称可以将时钟同步到彼此的150ms(或更短)之内。

我不知道这是否足以满足我的目的,但是我还没有找到更精确的选择。

这是它提供的算法:

游戏需要一种简单的时钟同步技术。理想情况下,它应具有以下属性:相当准确(150毫秒或更佳),收敛速度快,易于实现,能够在基于流的协议(例如TCP)上运行。

具有这些属性的简单算法如下:

  1. 客户端在“时间请求”数据包上标记当前本地时间,然后发送给服务器
  2. 服务器收到后,服务器会在服务器上盖章并返回
  3. 客户端收到后,客户端从发送时间中减去当前时间,然后除以2以计算延迟。它从服务器时间中减去当前时间,以确定客户端-服务器时间增量,并加上半延迟以获取正确的时钟增量。(到目前为止,此algothim与SNTP非常相似)
  4. 第一个结果应立即用于更新时钟,因为它将使本地时钟至少进入正确的时间(至少正确的时区!)。
  5. 客户重复步骤1至3五次或更多次,每次都暂停几秒钟。在此期间可能允许其他流量,但应将其最小化以获得最佳结果
  6. 分组收据的结果被累积并以最低延迟到最高延迟的顺序排序。中值等待时间是通过从此有序列表中选择中点样本来确定的。
  7. 丢弃与中位数相差大约1个标准偏差的所有样本,并使用算术平均值对其余样本取平均值。

该算法的唯一微妙之处是,丢弃了高于中位数一个标准偏差的数据包。这样做的目的是消除TCP重新传输的数据包。为了可视化,假设通过TCP发送了五个数据包的样本,而没有重传。在这种情况下,等待时间直方图将具有一个以中值等待时间为中心的单一模式(集群)。现在想象一下,在另一个试验中,将五个分组中的一个分组重新发送。重传将导致此样本在等待时间直方图上落到右侧,平均距离是原始模式中值的两倍。通过简单地切掉所有偏离中值超过一个标准偏差的样本,假设这些杂散模式不占统计数据的大部分,就可以轻松消除这些杂散模式。

该解决方案似乎可以令人满意地回答我的问题,因为它可以同步时钟,然后停止运行,从而使时间线性地流动。而我的最初方法会不断更新时钟,导致时间在接收快照时略有跳动。


那如何证明对您有用?我现在处在同样的情况。我使用的服务器框架仅支持TCP,因此无法使用NTP,后者发送UDP数据报。我很难找到任何声称可以通过TCP进行可靠的时间同步的时间同步算法。一秒钟的同步就足以满足我的需求。
dynamokaj

@dynamokaj效果很好。
Joncom '17

凉。您是否可以共享实施?
dynamokaj

@dynamokaj似乎我现在无法想到的任何项目中都找不到这样的实现。或者,对我来说效果很好的方法是:1)立即使用您从一个ping请求/响应计算的延迟,然后2)对所有将来的此类响应逐渐而不是立即过渡到新值。就我的目的而言,这具有“平均”效果。
Joncom '17

没问题。我在Google App Engine上运行后端服务,因此在使用Google NTP服务器同步服务器的Google基础设施上运行:time.google.com(developers.google.com/time)因此,我将以下NTP客户端用于Xamarin Mobile客户端获取客户端和服务器之间的偏移量。components.xamarin.com/view/rebex-time-感谢您抽出宝贵的时间回答。
dynamokaj

1

基本上,您无法解决整个问题,最终您将不得不划清界限。

如果服务器和所有客户端共享相同的帧速率,则它们仅需要在连接时进行同步,有时甚至在此后进行同步,尤其是在发生延迟事件之后。延迟不会影响时间流或PC对其进行测量的能力,因此,在许多情况下,您必须进行推断而不是内插。这同样会产生不想要的效果,但是事实又是如此,您必须在所有可用的弊端中选择最少的。

考虑到在许多流行的MMO中,落后者在视觉上是显而易见的。如果您看到它们就位运行,直接进入墙壁,说明您的客户正在推断。当您的客户端接收到新数据时,播放器(在其客户端上)可能已经移动了相当远的距离,并且将“橡皮筋”传送到新位置(您提到的“怪癖”?)。即使在大型的名牌游戏中,也会发生这种情况。

从技术上讲,这是玩家的网络基础结构的问题,而不是您的游戏的问题。它从一个点到另一个点的点就是您必须绘制的线。您的代码在3台不同的计算机上应该或多或少地记录相同的经过时间。如果您没有收到更新,则它不会影响您的Update()帧速率;如果有的话,它应该更快,因为更新的次数可能更少。

“如果您的互联网简陋,您将无法竞争性地玩这款游戏。”
那不是passing花一现或一个bug。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.