关于初学者游戏中的网络模型

图片

在过去的两个星期中,我一直在为游戏开发网络引擎。 在此之前,我对游戏中的网络技术一无所知,因此我阅读了许多文章并进行了许多实验,以了解所有概念并能够编写自己的网络引擎。

在本指南中,我想与您分享在编写自己的游戏引擎之前需要学习的各种概念,以及学习这些概念的最佳资源和文章。

通常,网络体系结构主要有两种类型:对等网络和客户端服务器。 在对等(p2p)架构中,数据在任何一对连接的播放器之间传输,而在客户端-服务器架构中,数据仅在播放器和服务器之间传输。

尽管在某些游戏中仍使用对等体系结构,但该标准是客户端-服务器:易于实施,需要较小的通道宽度并有助于防止作弊。 因此,在本指南中,我们将重点介绍客户端-服务器体系结构。

特别是,我们对威权服务器最感兴趣:在这样的系统中,服务器总是正确的。 例如,如果玩家认为自己在坐标(10,5)中,而服务器告诉他自己在(5,3)中,则客户端应将其位置替换为服务器发送的位置,反之亦然。 使用专制服务器可以更轻松地识别作弊者。

游戏网络系统包含三个主要组件:

  • 传输协议:客户端和服务器之间如何传输数据。
  • 应用协议:从客户端到服务器以及从服务器到客户端的传输格式以及什么。
  • 应用逻辑:如何使用传输的数据更新客户端和服务器的状态。

了解每个部分的作用以及与之相关的困难非常重要。

传输协议


第一步是选择用于在服务器和客户端之间传输数据的协议。 为此,有两种Internet协议: TCPUDP 。 但是您可以基于其中之一创建自己的传输协议,也可以使用使用它们的库。

比较TCP和UDP


TCP和UDP都是基于IP的。 IP允许您将数据包从源传送到接收者,但不能保证发送的数据包迟早会到达接收者,它至少会到达一次,并且数据包的顺序会以正确的顺序到达。 此外,数据包可能仅包含由MTU值指定的有限数据大小。

UDP只是IP之上的一个薄层。 因此,它具有相同的局限性。 相反,TCP具有许多功能。 它通过错误检查在两个节点之间提供可靠,有序的连接。 因此,TCP非常方便,并且可用于许多其他协议,例如HTTPFTPSMTP 。 但是所有这些功能都是有代价的: delay

要了解为什么这些功能会导致延迟,您需要了解TCP的工作原理。 当发送节点将数据包转发到接收节点时,它期望接收到确认(ACK)。 如果在一定时间后他仍未收到(由于数据包或确认丢失,或由于其他原因),则他将重新发送数据包。 而且,TCP确保以正确的顺序接收数据包,因此,直到接收到丢失的数据包,所有其他数据包都无法进行处理,即使它们已经被接收节点接收到也是如此。

但是,您可能知道,多人游戏的延迟非常重要,尤其是在FPS这样的活跃类型中。 这就是为什么许多游戏将UDP与自己的协议一起使用的原因。

由于各种原因,基于本地UDP的协议可能比TCP更有效。 例如,它可能会将某些程序包标记为可信,而将其他程序包标记为不可信。 因此,他不在乎不受信任的数据包是否到达接收者。 或者,它可以处理多个数据流,以使在一个流中丢失的数据包不会减慢其余流的速度。 例如,可能存在用于玩家输入的流和用于聊天消息的另一流。 如果非紧急数据的聊天消息丢失,则不会减慢紧急输入的速度。 或者,专有协议可以实现与TCP不同的可靠性,以便在视频游戏中更加高效。

那么,如果TCP这么废话,那么我们将基于UDP创建自己的传输协议吗?

一切都比较复杂。 尽管TCP在游戏网络系统中几乎不是最佳选择,但它可以在您的游戏中正常工作并节省您的宝贵时间。 例如,对于基于回合的游戏或只能在LAN上玩的游戏,延迟可能不是问题,在LAN上,延迟和丢包率比Internet少得多。

许多成功的游戏,包括《魔兽世界》,《我的世界》和Terraria,都使用TCP。 但是,大多数FPS使用专有的基于UDP的协议,因此我们将在下面详细讨论它们。

如果决定使用TCP,请确保禁用Nagle算法 ,因为它会在发送前对数据包进行缓冲,这会增加延迟。

要了解有关多人游戏环境中UDP和TCP之间差异的更多信息,请阅读Glenn Fiedler的UDP vs. TCP协议

自己的协议


因此,您想创建自己的传输协议,但不知道从哪里开始? 您很幸运,因为Glenn Fiedler撰写了两篇有关此的惊人文章。 您会在其中发现许多聪明的想法。

第一篇文章《 面向游戏程序员的网络 2008》比第二篇文章《 构建游戏网络协议 2016 更简单。 我建议您从较早的版本开始。

请记住,Glenn Fiedler大力支持使用自己的UDP协议。 在阅读了他的文章之后,您一定会克服他的观点,即TCP在视频游戏中存在严重缺陷,并且您想实现自己的协议。

但是,如果您不熟悉网络,请帮自己一个忙,并使用TCP或库。 要成功实现自己的传输协议,您首先需要学习很多知识。

网络库


如果您需要比TCP更高效的功能,但是又不想麻烦自己实施协议并涉及许多细节,则可以使用网络库。 有很多:


我没有尝试过所有方法,但是我更喜欢ENet,因为它易于使用且可靠。 此外,她还有清晰的文档和针对初学者的教程。

传输协议:结论


总结一下:有两种主要的传输协议:TCP和UDP。 TCP具有许多有用的功能:可靠性,数据包排序,错误检测。 UDP没有所有这些,但是TCP本质上增加了某些游戏所不能接受的延迟。 也就是说,为确保低延迟,您可以创建自己的基于UDP的协议或使用实现UDP传输协议并适用于多人视频游戏的库。

TCP,UDP和库之间的选择取决于多个因素。 首先,从游戏的需求来看:它需要低延迟吗? 其次,从应用协议的要求来看:它是否需要可靠的协议? 正如我们将在下一部分中看到的那样,您可以创建一个应用程序协议,对于该协议来说,不可靠的协议非常适合。 最后,您还必须考虑网络引擎开发人员的经验。

我有两个提示:

  • 最大限度地提高应用程序其余部分的传输协议,以便可以轻松替换它,而无需重写整个代码。
  • 不要做过早的优化。 如果您不是网络专家,并且不确定是否需要自己的基于UDP的传输协议,则可以从TCP或提供可靠性的库开始,然后测试和衡量性能。 如果您有问题,并且确定原因在于传输协议,那么可能是时候创建自己的传输协议了。

在本部分的最后,我建议您阅读Brian Hook的“多人游戏编程简介” ,其中涵盖了此处讨论的许多主题。

应用协议


现在我们可以在客户端和服务器之间交换数据了,我们需要确定要传输的数据和格式。

经典方案是客户端将输入或动作发送到服务器,服务器将当前游戏状态发送到客户端。

服务器不会通过播放器旁边的实体发送完整但已过滤的状态。 他这样做有三个原因。 首先,总体状态对于高频传输而言可能太大。 其次,客户主要对视觉和音频数据感兴趣,因为大多数游戏逻辑是在游戏服务器上模拟的。 第三,在某些游戏中,玩家不需要知道某些数据,例如对手在地图另一端的位置,因为否则他可以嗅探数据包并确切知道要去哪里杀死他。

序列化


第一步是将我们要发送的数据(输入或游戏状态)转换为适合传输的格式。 此过程称为序列化

立即想到使用人类可读的格式(例如JSON或XML)的想法。 但这将是完全无效的,并且白白地占据了大部分渠道。

相反,建议您使用更紧凑的二进制格式。 也就是说,数据包将仅包含几个字节。 在这里,您需要考虑字节顺序的问题,这在不同的计算机上可能会有所不同。

您可以使用库来序列化数据,例如:


只需确保该库创建了可移植的归档文件并处理了字节顺序。

独立的解决方案可能是独立的实现,并不是特别复杂,特别是如果您在代码中使用面向数据的方法。 另外,它使您可以执行使用库时并非总是可能的优化。

Glenn Fiedler写了两篇有关序列化的文章: 读写数据包序列化策略

压缩方式


客户端和服务器之间传输的数据量受通道带宽限制。 数据压缩使您可以在每个快照中传输更多数据,提高刷新率,或简单地减少通道需求。

钻头包装


第一种技术是位打包。 它包括准确使用描述所需值所需的位数。 例如,如果您的枚举可以具有16个不同的值,则只能使用4位,而不是整个字节(8位)。

Glenn Fiedler在“ 读写数据包”文章的第二部分中说明了如何实现此目的。

比特打包在采样方面特别有效,这将是下一部分的主题。

离散化


离散化是一种有损压缩技术,仅使用可能值的子集来编码值。 实现离散化的最简单方法是四舍五入浮点数。

Glenn Fiedler(再次!)在他的快照压缩文章中展示了如何在实践中应用采样。

压缩算法


下一个技术将是无损压缩算法。

在我看来,您需要了解以下三个最有趣的算法:

  • 霍夫曼编码采用预先计算的代码,速度极快并且可以提供良好的结果。 它用于在Quake3网络引擎中压缩数据包。
  • zlib是一种通用压缩算法,永远不会增加数据量。 从这里可以看出,它已在许多应用程序中使用。 更新状态可能是多余的。 但是,如果您需要从服务器向客户端发送资产,长文本或浮雕,它会派上用场。
  • 复制序列的长度可能是最简单的压缩算法,但是它对于某些数据类型非常有效,并且可以用作zlib之前的预处理步骤。 它特别适用于压缩由瓦片或体素组成的地形,其中重复了许多相邻元素。

增量压缩


最新的压缩技术是增量压缩。 其原因在于,仅传输当前游戏状态和客户端接收到的最后状态之间的差异。

它首先在Quake3网络引擎中使用。 这里有两篇文章解释了如何使用它:


Glenn Fiedler在他的快照压缩文章的第二部分中也使用了它。

加密方式


另外,您可能需要对客户端和服务器之间的信息传输进行加密。 这有几个原因:

  • 隐私/机密性:邮件只能由收件人阅读,嗅探网络的其他任何人都不能阅读。
  • 身份验证:想要扮演玩家角色的人必须知道他的钥匙。
  • 防止作弊:恶意玩家创建自己的作弊程序包会更加困难,他们将不得不重现加密方案并找到密钥(随每个连接而变化)。

我强烈建议为此使用库。 我建议使用libsodium,因为它特别简单并且教程很棒。 特别有趣的是密钥交换教程,它使您可以在每个新连接中生成新密钥。

应用协议:结论


我们将以应用程序协议结束。 我认为压缩是完全可选的,使用压缩的决定仅取决于游戏和所需的带宽。 我认为加密是强制性的,但是在第一个原型中,您可以不用加密。

应用逻辑


现在我们可以更新客户端中的状态,但是可能会遇到延迟问题。 进入后,玩家需要等待服务器上游戏状态的更新,以查看他对世界的影响。

而且,在两次状态更新之间,世界是完全静态的。 如果状态的刷新率较低,则动作将非常颤抖。

有几种技术可以减少此问题的影响,在下一节中,我将讨论它们。

延迟平滑技术


Gabriel Gambetta的“ 快节奏多人游戏系列”详细讨论了本节中介绍的所有技术。 我强烈建议阅读此系列文章。 它还有一个交互式演示,让您了解这些技术在实践中如何工作。

第一种技术是直接应用输入,而不必等待服务器的响应。 这称为客户端预测 。 但是,当客户端从服务器收到更新时,他必须确保自己的预测正确。 如果不是这样,那么他只需要根据从服务器接收到的状态来更改其状态,因为服务器是专制的。 此技术最早在Quake中使用。 您可以在Fabien Sanglar撰写的《 Quake Engine代码审查》一书中阅读更多有关它的内容[ 翻译成Habré]。

第二组技术用于平滑两个状态更新之间其他实体的移动。 解决此问题的方法有两种:内插法和外推法。 在插值的情况下,采用最后两种状态,并显示从一种状态到另一种状态的转变。 它的缺点是,它会导致延迟的一小部分,因为客户端总是可以看到过去发生的事情。 外推法是根据客户端接收到的最后状态来预测实体现在应位于何处。 其缺点是,如果实体完全改变运动方向,则预测和实际位置之间将存在较大的误差。

仅在FPS中有用的最后一种最先进的技术是滞后补偿 。 , . , , - , - . , , , , , .

( !) 2004 Network Physics (2004) , . 2014 Networking Physics , .

Valve Wiki上还有两篇文章,《客户端/服务器游戏内协议设计和优化中的源多人网络延迟补偿方法》,讨论了延迟补偿。

防止作弊


防止作弊有两种主要技术。

第一:使作弊者发送恶意软件包变得复杂。如上所述,加密是实现它的好方法。

第二:专制服务器只能接收命令/输入/动作。客户端应该不能更改服务器上的状态,除非发送输入。然后,每次接收到输入时,服务器必须在应用之前检查其有效性。

应用逻辑:结论


, , . .


, , :

Source: https://habr.com/ru/post/zh-CN467025/


All Articles