如何在使用时间戳验证位置差异时考虑延迟差异(防作弊)?

Posted

技术标签:

【中文标题】如何在使用时间戳验证位置差异时考虑延迟差异(防作弊)?【英文标题】:Howto take latency differences into consideration when verifying location differences with timestamps (anti-cheating)? 【发布时间】:2010-12-12 00:56:39 【问题描述】:

当您在多人游戏中服务器从客户端接收移动(位置)信息时,您希望验证此信息作为反作弊措施。

这可以这样完成:

maxPlayerSpeed = 300; // = 300 pixels every 1 second
if ((1000 / (getTime() - oldTimestamp) * (newPosX - oldPosX)) > maxPlayerSpeed)

   disconnect(player); //this is illegal!

这是一个简单的例子,只考虑了 X 坐标。这里的问题是,只要服务器收到最后一次位置更新,就会存储 oldTimestamp。这意味着,如果当时出现延迟峰值,则旧时间戳将比服务器接收到的新位置更新晚得多。这意味着时差将不准确。

例子:

    客户说:我现在在位置 5x10 延迟峰值:服务器在时间戳 500 收到此消息(它通常应该在 30 左右到达) ....1 秒移动... 客户说:我现在在位置 20x15 没有延迟峰值:服务器在时间戳 1530 收到消息

现在服务器会认为这两个位置的时间差是1030,但实际的时间差是1500。这可能导致反作弊检测认为1030不够长,从而踢掉客户端。

可能的解决方案:让客户端在发送的同时发送一个时间戳,这样服务器就可以使用这些时间戳来代替

问题:该解决方案的问题是玩家可以操纵客户端发送一个非法的时间戳,因此反作弊系统不会启动。这不是很好的解决方案。

也可以简单地允许 maxPlayerSpeed * 2 速度(例如),但是这基本上允许速度达到正常速度的两倍。这也不是一个好的解决方案。

那么:您对如何解决“服务器时间戳和延迟”问题以使我的反作弊措施有价值有什么建议吗?

【问题讨论】:

【参考方案1】:

不不不..恕我直言,这都是错误的,以及如何不这样做。

补救措施是不信任您的客户。不要让客户发送他们的位置,让他们发送他们的按钮状态!将按钮状态视为客户说“我正在前进,除非你反对”的请求。如果客户端发送“向前移动”消息并且无法向前移动,服务器可以忽略它或做任何它喜欢的事情以确保一致性。在这种情况下,客户只会自欺欺人。

至于通过数据包泛洪实现的速度黑客,请保留一个数据包计数器。弹出在特定时间范围内发送的数据包多于允许设置的客户端。客户端应在每个滴答/帧/世界时间步长发送一个数据包。在整个时间步长增量中根据时间命名数据包很方便。然后可以识别并忽略相同时间步长的过多数据包。请注意,在使用 UDP 时,最好多次发送相同的数据包,以防止数据包丢失。

再一次,永远不要相信客户。这一点怎么强调都不过分。

【讨论】:

1) 我正在使用按钮状态,位置更新仅用于由于滞后而需要的更正 2) 我不信任客户端,因为如果我愿意,我不会验证它 -仅仅发送按钮状态不会让玩家保持同步。 您的客户端数据是按钮状态?这不是您的摘要所说的:当您在多人游戏中服务器从客户端接收移动(位置)信息时,您希望验证此信息作为反作弊措施。 我的意思是“信任客户”,我的意思是给他们任何机会成为权威。客户的全球世界地位是权威的。 “前进”状态不是。有一个至关重要的区别。如果您能澄清这是延迟/延迟规避,还是反作弊措施,那就太好了。我很困惑(见上文)。 @Mads,发送按钮状态很好,但大多数游戏也必须在客户端上运行单独的预测模拟。所以问题又回到了保持客户端模拟与服务器同步,如果你发送位置,你会遇到更少的抖动问题。魔兽世界是众多让客户报告自己位置的游戏之一。 疯狂,我的总结就是游戏发送服务器移动(位置)信息。这是真的,即使是我的按钮状态系统。就像我之前说的,这些位置信息更新是为了同步客户端,以防出现延迟。客户端可以发送无效的位置更新,这将导致无效的校正,因此他可以传送。这就是反作弊在服务器端出现的地方。我不同意所有的移动都应该由服务器计算,专业的多人游戏也没有选择这条路。 可能取决于游戏类型,但在服务器上集成世界状态在很大程度上是 FPS 游戏中的标准实际方式。所以相反,我会声称大多数游戏都选择了这种方案。除非您给出一些非常有说服力的理由将位置发送到服务器,否则我的回答仍然有效。也就是说,我认为这通常是个坏主意。没有什么好东西可以从中产生。不过,您的时间戳问题应该不是问题。执行一个世界框架意味着一个客户端数据包的规则。忽略未来的数据包和(古代)过去的数据包。【参考方案2】:

通过过滤消除延迟峰值。或者换句话说,不是总是将他们的新位置与之前的位置进行比较,而是将其与几次更新前的位置进行比较。这样,任何短期抖动都会被平均化。在您的示例中,服务器可以查看延迟峰值之前的位置,并查看玩家总体上以合理的速度移动。

对于每个玩家,您可以简单地持有最后 X 个位置,或者您可能持有很多最近的位置以及一些较旧的位置(例如 2、3、5、10 秒前)。

通常,您无论如何都会在正常移动速度范围内在服务器上执行插值/外推,以隐藏其他玩家的抖动 - 您所做的只是将其扩展到您的作弊检查机制。所有合法的加速都会在明显减速之后出现,而插值有助于弥补这种错误。

【讨论】:

前几次更新的位置仍然会超出允许的最大距离,因为其间有一两个延迟峰值。您认为需要考虑什么门槛? maxSpeed * 1.1,又名 10%? 游戏开始时可能存在系统中的潜在缺陷。如果到目前为止您只有两个位置更新,则必须使用前一个。如果这是一个滞后峰值,它会踢你。如果你忽略服务器端的前几次更新,那意味着玩家总是可以通过重新启动游戏进行传送,然后每次重新启动一次传送。有什么想法吗? 我认为阈值必须是您通过实验选择的东西 - 例如,您可能希望 FPS 与 MMO 不同。但请记住,您的服务器是这里的权威 - 如果有人始终太快 10%,那么您可以将他们移回。至于缺陷,这是问题所固有的 - 要估计一个值的导数,您首先需要该值的至少 2 个样本。同样,您的服务器应该是权威的 - 将玩家推回沿其行进方向的合理位置。只是不要假设他们在作弊(还)。 您可以通过将它们重新定位到它们发送给您的第二个位置来阻止这种情况,因为您的服务器知道它们不可能在那里。但在您获得更多样本之前,您不会认为他们一定是在作弊。 与其说是几种方法,不如说是几个问题。一是消除抖动以获得准确的位置。另一个是检查潜在的作弊行为。他们共享相同的数据,但应该以不同的方式处理它。将某人向后拉到不太远的位置而不假设他们为了到达更远的位置而作弊是很常见的。【参考方案3】:

无论对方法的意见如何,您正在寻找的是被认为是“作弊”的速度阈值。 给定距离和时间增量,您可以根据您的作弊阈值轻松查看它们是否“移动得太远”。

time = thisTime - lastTime;
speed = distance / time;
If (speed > threshold) dudeIsCheating();

用于测量的时间是服务器接收数据包的时间。虽然看起来微不足道,但它正在计算每个角色移动的距离,这最终可能会非常昂贵。最佳路线是服务器根据速度计算位置,即角色的位置。客户端从不传达位置或绝对速度,相反,客户端发送“最大百分比”速度。


澄清: 这只是为了作弊检查。您的代码有可能在服务器上延迟或长时间处理影响您的结果。公式应该是:

maxPlayerSpeed = 300; // = 300 pixels every 1 second
if (maxPlayerSpeed < 
    (distanceTraveled(oldPos, newPos) / (receiveNewest() - receiveLast()))

   disconnect(player); //this is illegal!

这会将玩家的移动速度与最大移动速度进行比较。时间戳由您收到数据包的时间决定,而不是由您处理数据的时间决定。您可以使用任何您关心的方法来确定要发送给客户端的更新,但是对于您想要确定作弊的阈值方法,上述不会受到延迟的影响。

在第 1 秒接收数据包 1:位置 1 的字符

在第 100 秒接收数据包 2:位置 3000 处的字符

行驶距离 = 2999

时间 = 99

率 = 30

没有作弊发生。

在第 101 秒接收数据包 3:位置 3301 处的字符

行驶距离 = 301

时间 = 1

率 = 301

检测到作弊。

您所说的“延迟峰值”实际上是数据包传输中的高延迟。但这并不重要,因为您不会在处理数据时经过,而是在收到每个数据包时经过。如果您保持时间计算独立于您的游戏滴答处理(因为它们应该与“滴答”期间发生的事情一样)高和低延迟只会影响服务器对角色位置的确定程度,您可以使用插值+外推来解决.

如果客户端不同步到他们没有收到对其位置的任何更正并且与服务器严重不同步的情况下,则会出现严重的数据包丢失和高延迟,您的作弊检查将无法占。您需要通过处理实际网络通信在较低层考虑这一点。

对于任何游戏数据,理想的方法是让除服务器之外的所有系统都落后 100-200ms。假设您每 50 毫秒进行一次预期更新。客户端收到第一个和第二个。客户端在收到第二次更新之前没有任何数据要显示。在接下来的 50 毫秒内,它显示了已经发生的变化进程(即,它处于非常轻微的延迟播放状态)。客户端将其按钮状态发送到服务器。本地客户端还根据这些按钮按下预测移动、效果等,但只向服务器发送“按钮状态”(由于按钮数量有限,表示每个状态所需的比特数量有限,这允许更紧凑的数据包格式)。

服务器是权威模拟,决定实际结果。服务器每隔 50 毫秒向客户端发送一次更新。服务器不是在两个已知帧之间进行插值,而是为任何丢失的数据推断位置等。服务器知道最后的真实位置是什么。当它接收到更新时,发送给每个客户端的下一个数据包包括更新的信息。然后客户端应该在到达该时间点之前收到此信息,并且玩家会在它发生时做出反应,不会看到任何奇怪的跳跃,因为它从未显示错误的位置。

可以让客户端对某些事情具有权威性,或者让客户端充当权威服务器。关键是确定对客户的信任有多大影响。


客户端应该定期发送更新,例如每 50 毫秒。这意味着 500 毫秒的“延迟峰值”(数据包接收延迟),在延迟期内发送的所有数据包都将延迟相似的数量,或者数据包将被无序接收。底层网络应该优雅地处理这些延迟(通过丢弃具有过大延迟的数据包,强制按顺序发送数据包等)。最终结果是,通过适当的数据包处理,预期的问题不会发生。此外,不从客户端接收显式字符位置,而是让服务器显式更正客户端并仅从客户端接收控制状态可以防止此问题。

【讨论】:

我不知道您的系统如何处理同步校正,因为其他客户端不知道相应客户端的确切位置。如果你想让服务器计算位置然后发送给客户端,请参阅我对 Mads 的回复。 感谢您的澄清,但这对我来说没有多大意义,因为我已经在使用服务器接收消息的时间,而不是处理消息的时间。在您的示例中,一切正常,但是当第一个包裹被延迟时,它将在时间 50 显示位置 1 而不是 1,例如。然后当第二个包没有延迟时,它会在时间 100 位置为 3000。这是 50 的时间差,这可以使它检测为作弊。我看不到你的系统是如何解决这个问题的,因为你使用的是服务器接收到包的时间。 此方法需要一个“基线”位置和一个基线“时间戳”。解决这个问题的方法是连接协商。服务器发送客户端开始位置。客户以完全相同的位置响应,任何偏差 = 作弊。你有时差和位置差。涵盖了客户端处理的延迟。涵盖了网络延迟。这不是往返时间,只有当服务器收到它时。在您的示例中,您没有使用服务器接收时间,而是使用当前时间。即使花费了 1/10 秒,它也可能跳到下一个并违反时间值。 我还是不明白,我从来没有说过我以为你在谈论往返时间。这确实是服务器接收它的时间,就像我之前所说的,这可能会有所不同。因此,当在 100 毫秒内收到第一条消息,在 25 毫秒内收到第二条消息时,就会造成麻烦。我仍然看不到您的系统在哪里解决了这个问题。

以上是关于如何在使用时间戳验证位置差异时考虑延迟差异(防作弊)?的主要内容,如果未能解决你的问题,请参考以下文章

SQL 获取时间戳差异大于 30 时的 id 计数

MySQL:如何在几秒钟内获得两个时间戳之间的差异

如何使用休眠查询语言查找两个时间戳之间的差异

如何在 BigQuery 中获取连续时间戳之间的差异

如何从 DB2 中的两个时间戳中获取差异?

如何使用 SQL 显示两个不同区域中时间戳之间的实时差异?