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

关于初学者游戏中的网络模型
在过去的两周里,我一直在为我的游戏开发在线引擎。 在此之前,我对游戏中的网络一无所知,因此我阅读了很多文章并做了很多实验来理解所有概念并能够编写自己的网络引擎。

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

一般来说,网络架构主要有两种类型:对等网络和客户端-服务器网络。 在点对点 (p2p) 架构中,数据在任意连接的玩家对之间传输,而在客户端-服务器架构中,数据仅在玩家和服务器之间传输。

尽管一些游戏中仍然使用点对点架构,但客户端-服务器已成为标准:它更容易实现,需要更小的通道宽度,并且更容易防止作弊。 因此,在本教程中,我们将重点关注客户端-服务器架构。

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

网络游戏系统具有三个主要组成部分:

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

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

传输协议

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

TCP和UDP的比较

TCP和UDP都是基于 IP。 IP 允许数据包从源传输到接收者,但不保证发送的数据包迟早会到达接收者、至少会到达一次、以及数据包的顺序会以正确的方式到达命令。 此外,数据包只能包含有限数量的数据,由值给出 MTU.

UDP 只是 IP 之上的薄层。 因此,它也有同样的局限性。 相比之下,TCP有很多特点。 它通过错误检查在两个节点之间提供可靠、有序的连接。 因此,TCP 非常方便并且被用于许多其他协议中,例如 HTTP, 则fTP и SMTP。 但所有这些功能都是有代价的: 延时.

要理解为什么这些函数会导致延迟,我们需要了解 TCP 的工作原理。 当发送节点将数据包发送到接收节点时,它期望收到确认(ACK)。 如果在一段时间后仍未收到数据包(因为数据包或确认丢失,或由于某些其他原因),则它会重新发送数据包。 此外,TCP 保证数据包按正确的顺序接收,因此在接收到丢失的数据包之前,所有其他数据包都无法得到处理,即使它们已经被接收主机接收。

但正如您可能想象的那样,多人游戏中的延迟非常重要,尤其是在 FPS 等充满动作的游戏类型中。 这就是为什么许多游戏都使用 UDP 及其自己的协议。

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

那么,如果 TCP 如此糟糕,那么我们将基于 UDP 创建自己的传输协议吗?

情况有点复杂。 尽管 TCP 对于游戏网络系统来说几乎不是最佳选择,但它可以很好地适合您的特定游戏并节省您宝贵的时间。 例如,对于回合制游戏或只能在 LAN 网络上玩的游戏来说,延迟可能不是问题,因为 LAN 网络上的延迟和丢包率比 Internet 低得多。

许多成功的游戏,包括《魔兽世界》、《我的世界》和《泰拉瑞亚》,都使用 TCP。 然而,大多数 FPS 使用自己的基于 UDP 的协议,因此我们将在下面详细讨论它们。

如果您决定使用 TCP,请确保将其禁用 内格尔算法,因为它在发送之前缓冲数据包,这意味着它会增加延迟。

要了解有关多人游戏中 UDP 和 TCP 之间的差异的更多信息,您可以阅读 Glenn Fiedler 的文章 UDP 对比传输控制协议.

自己的协议

您想创建自己的传输协议,但不知道从哪里开始? 你很幸运,因为 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 个不同值的枚举,那么您可以只使用 8 位,而不是整个字节(4 位)。

Glenn Fiedler 在文章的第二部分解释了如何实现这一点 读写数据包.

位打包对于采样特别有效,这将是下一节的主题。

采样

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

Glenn Fiedler(再次!)在他的文章中展示了如何将采样付诸实践 快照压缩.

压缩算法

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

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

  • 霍夫曼编码 使用预先计算的代码,速度非常快并且可以产生良好的结果。 它用于压缩 Quake3 网络引擎中的数据包。
  • ZLIB 是一种通用压缩算法,不会增加数据量。 你怎么能看到 这里,它已被用于各种应用中。 对于更新状态来说可能是多余的。 但如果您需要从服务器向客户端发送资产、长文本或地形,它会很有用。
  • 复制运行长度 - 这可能是最简单的压缩算法,但它对于某些类型的数据非常有效,并且可以用作 zlib 之前的预处理步骤。 它特别适合压缩由重复许多相邻元素的图块或体素组成的地形。

增量压缩

最后一种压缩技术是增量压缩。 它的事实在于,仅传输当前游戏状态与客户端接收到的最后状态之间的差异。

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

格伦·费德勒(Glenn Fiedler)也在他的文章的第二部分中使用了它 快照压缩.

加密

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

  • 隐私/保密:消息只能由收件人阅读,嗅探网络的其他人无法阅读它们。
  • 身份验证:想要扮演玩家角色的人必须知道他的密钥。
  • 作弊预防:恶意玩家创建自己的作弊包将更加困难,他们必须重现加密方案并找到密钥(每次连接都会改变)。

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

应用协议:结论

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

应用逻辑

我们现在可以更新客户端中的状态,但可能会遇到延迟问题。 玩家完成输入后,需要等待服务器更新游戏状态,看看它对世界产生了什么影响。

此外,在两次状态更新之间,世界是完全静态的。 如果状态更新率较低,则运动会非常不稳定。

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

延迟平滑技术

本节中描述的所有技术都将在本系列中详细讨论 快节奏多人游戏 加布里埃尔·甘贝塔。 我强烈推荐阅读这个优秀的系列文章。 它还包括一个交互式演示,让您了解这些技术如何在实践中发挥作用。

第一种技术是直接应用输入结果,而不等待服务器的响应。 它被称为 客户端预测。 但是,当客户端收到来自服务器的更新时,它必须验证其预测是否正确。 如果不是这种情况,那么他只需要根据从服务器收到的信息来改变他的状态,因为服务器是专制的。 这项技术首先在《Quake》中使用。 您可以在文章中阅读更多相关信息 雷神之锤引擎代码审查 法比安·桑格拉斯 [翻译 关于哈布雷]。

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

仅在 FPS 中有用的最新、最先进的技术是 滞后补偿。 使用延迟补偿时,服务器会考虑客户端射击目标时的延迟。 例如,如果玩家在屏幕上进行了爆头,但实际上由于延迟而导致目标位于不同的位置,那么由于延迟而剥夺玩家杀人的权利是不公平的。 因此,服务器会将时间倒回到玩家开枪的那一刻,以模拟玩家在屏幕上看到的内容并检查射击与目标之间的碰撞。

Glenn Fiedler(一如既往!)在 2004 年写了一篇文章 网络物理(2004),其中他为服务器和客户端之间的物理模拟同步奠定了基础。 2014年他写了一系列新文章 网络物理,其中描述了同步物理模拟的其他技术。

Valve wiki 上还有两篇文章, 源多人网络 и 客户端/服务器游戏内协议设计与优化中的延迟补偿方法 其中考虑了延误补偿。

防止作弊

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

第一:让作弊者更难发送恶意数据包。 如上所述,实现这一点的一个好方法是加密。

第二:独裁服务器应该只接收命令/输入/操作。 除了发送输入之外,客户端不应该能够更改服务器上的状态。 然后,服务器每次接收输入时,都必须在使用之前检查它是否有效。

应用逻辑:结论

我建议您实施一种模拟高延迟和低刷新率的方法,以便您可以在恶劣条件下测试游戏的行为,即使客户端和服务器在同一台计算机上运行也是如此。 这将大大简化延迟平滑技术的实现。

其他有用的资源

如果您想探索有关网络模型的其他资源,可以在这里找到它们:

来源: habr.com

添加评论