如何处理网码?


10

我对评估网络代码可以“融入”游戏引擎的不同方式感兴趣。我现在正在设计一个多人游戏,到目前为止,我已经确定(至少)需要一个单独的线程来处理网络套接字,这不同于处理图形循环和脚本的引擎其余部分。

我确实有一种使网络游戏完全为单线程的潜在方法,那就是在使用非阻塞套接字渲染每一帧后让它检查网络。但是,这显然不是最佳方法,因为渲染帧所花费的时间增加了网络延迟:通过网络到达的消息必须等待,直到当前帧渲染(和游戏逻辑)完成为止。但是,至少以这种方式,游戏玩法仍将或多或少保持平稳。

拥有独立的网络线程可以使游戏完全响应网络,例如,它可以在收到来自服务器的状态更新后立即发送回ACK数据包。但是我对在游戏代码和网络代码之间进行通信的最佳方式有些困惑。网络线程会将接收到的数据包推送到队列中,并且游戏线程将在其循环期间的适当时间从队列中读取数据,因此我们没有摆脱这种长达一帧的延迟。

另外,似乎我希望处理数据包发送的线程与检查管道中的数据包的线程是分开的,因为它不能在发送过程中将其发送出去。检查是否有传入消息。我在考虑select或类似功能。

我想我的问题是,设计具有最佳网络响应能力的最佳方法是什么?显然,客户端应该将用户输入尽快发送到服务器,这样我就可以在事件处理循环之后立即在游戏循环内部发送净发送代码。这有意义吗?

Answers:


13

忽略响应能力。在局域网上,ping无关紧要。在互联网上,往返60-100毫秒是一种祝福。向落后的上帝祈祷,您不会获得超过3K的峰值。您的软件必须以非常低的每秒更新数运行,这是一个问题。如果您每秒拍摄25次更新,那么在收到数据包并对其执行操作之前,您将有40ms的最大时间间隔。那就是单线程的情况...

设计系统的灵活性和正确性。这是我关于如何将网络子系统挂接到游戏代码中的想法:消息传递。解决许多问题的方法可以是“消息传递”。我认为消息传递可以治愈实验室老鼠的癌症。信息为我节省了200美元或更多的汽车保险。但是,认真的说,消息传递可能是将任何子系统连接到游戏代码的最佳方法,同时仍保持两个独立的子系统。

对于网络子系统和游戏引擎之间的任何通信,以及任何两个子系统之间的通信,请使用消息传递。子系统间消息传递可以很简单,就像使用std :: list的指针传递的数据块一样。

只需有一个传出消息队列和对网络子系统中游戏引擎的引用。游戏可以将想要发送的消息转储到传出队列中,并自动将它们发送出去,或者在调用“ flushMessages()”之类的函数时也可以。如果游戏引擎有一个较大的共享消息队列,那么所有需要发送消息的子系统(逻辑,AI,物理,网络等)都可以将消息转储到其中,然后主游戏循环可以读取所有消息然后采取相应行动。

我会说,在另一个线程上运行套接字是可以的,尽管不是必需的。这种设计的唯一问题是,它通常是异步的(您不确切地知道何时发送数据包),这会使调试变得困难,并使与时序相关的问题随机出现/消失。尽管如此,如果做得正确,这些都不是问题。

从更高的层次上讲,我想说的是独立于游戏引擎的网络。游戏引擎不在乎套接字或缓冲区,而在乎事件。事件包括诸如“玩家X开枪”,“在游戏T发生爆炸”之类的事情。这些可以由游戏引擎直接解释。它们从何处生成(脚本,客户的动作,AI播放器等)都无关紧要。

如果将网络子系统视为发送/接收事件的一种方式,那么与仅在套接字上调用recv()相比,您将获得许多好处。

您可以优化带宽,例如,采用50条小消息(长度为1-32字节),然后让网络子系统将它们打包为一个大数据包并发送。也许这可以在发送之前先压缩它们。另一方面,代码可以将大数据包再次解压缩/解包为50个离散事件,以供游戏引擎读取。这一切都可以透明地发生。

其他很酷的功能包括单人游戏模式,该模式通过在同一台计算机上运行纯客户端+纯服务器,通过共享内存空间中的消息传递进行通信,从而重用网络代码。然后,如果您的单人游戏正常运行,则远程客户端(即真正的多人游戏)也将运行。此外,它会迫使您提前考虑客户端需要哪些数据,因为您的单人游戏看起来要么正确,要么完全错误。混合搭配,运行服务器并在多人游戏中成为客户端-一切都一样容易。


您提到使用简单的std :: list或其他类似方式来传递消息。这可能是StackOverflow的主题,但是所有线程共享相同的地址空间是真的吗,并且只要我不让多个线程同时与属于我的队列的内存发生冲突,我应该还好吗?我可以像往常一样为堆上的队列分配数据,并在其中使用一些互斥锁?
史蒂文·卢

对,那是正确的。一个互斥体,保护对std :: list的所有调用。
PatrickB

感谢回复!到目前为止,我的线程例程已经取得了很大的进步。拥有自己的游戏引擎,真是太好了!
史蒂文·卢

4
这种感觉会消失。不过,您获得的大黄铜将与您同在。
克里斯(ChrisE)2011年

@StevenLu有点晚了,但是我想指出,防止线程同时与内存绑定可能非常困难,这取决于您尝试执行的方式和效率。今天是您要做的吗,我将向您介绍许多出色的开源并发队列实现之一,因此您无需重新发明一个复杂的轮子。
基金莫妮卡的诉讼'18

4

我需要(至少)有一个单独的线程来处理网络套接字

不,你不会。

我确实有一种使网络游戏完全为单线程的潜在方法,那就是在使用非阻塞套接字渲染每一帧后让它检查网络。但是,这显然不是最佳的,因为渲染帧所花费的时间被添加到网络延迟中:

不一定重要。您的逻辑何时更新?如果您还无法执行任何操作,那么从网络上获取数据毫无意义。同样,如果您无话可说,没有任何反应。

例如,它可以在收到来自服务器的状态更新后立即发送回ACK数据包。

如果您的游戏节奏如此之快,以至于等待下一帧的渲染是一个巨大的延迟,那么它将发送足够的数据,您无需发送单独的ACK数据包-只需在常规数据中包含ACK值有效载荷(如果您完全需要它们)。

对于大多数网络游戏,完全有可能出现如下游戏循环:

while 1:
    read_network_messages()
    read_local_input()
    update_world()
    send_network_updates()
    render_world()

您可以将更新与渲染分离开来,强烈建议这样做,但是除非您有特殊需要,否则其他所有内容都可以保持如此简单。您到底在玩哪种游戏?


7
不仅强烈建议将渲染与渲染解耦,而且如果您希望使用非垃圾引擎,则需要这样做。
AttackingHobo

不过,大多数人并没有制造引擎,而且大多数游戏可能仍无法将两者分开。在大多数情况下,将经过的时间值传递给更新函数的设置工作可以接受。
Kylotan

2

但是,这显然不是最佳方法,因为渲染帧所花费的时间增加了网络延迟:通过网络到达的消息必须等待,直到当前帧渲染(和游戏逻辑)完成为止。

那根本不是真的。接收方渲染当前帧,消息将通过网络传输。网络延迟被固定在客户端的整个帧数上。是的,但是如果客户的FPS太少了,那么这很重要,那么他们的问题就更大了。


0

网络通信应分批进行。您应该争取每个游戏滴答发送一个数据包(通常是在渲染帧时,但实际上应该是独立的)。

您的游戏实体与网络子系统(NSS)对话。NSS分批处理消息,ACK等,并发送一些(希望是一个)最佳大小的UDP数据包(通常约为1500个字节)。NSS仅发送单个UDP数据包时模拟数据包,通道,优先级,重发等。

通读灯光师对游戏的教程或只是使用ENET它实现了很多的格伦·菲德勒的想法。

或者,如果您的游戏不需要抽搐反应,也可以只使用TCP。然后所有的批处理,重新发送和ACK问题就消失了。但是,您仍然希望NSS管理带宽和通道。


0

不要完全“忽略响应”。向已经延迟的数据包再增加40ms的延迟几乎没有什么好处。如果要添加几帧(以60fps的速度),则表示延迟位置处理会更新另外几帧。最好快速接收并快速处理数据包,以提高仿真的准确性。

通过考虑表示屏幕上可见内容所需的最小状态信息,我在优化带宽方面取得了巨大成功。然后查看数据的每一位并为其选择模型。位置信息可以表示为一段时间内的增量值。您可以为此使用自己的统计模型并花很多时间调试它们,也可以使用库来帮助您。我更喜欢使用该库的浮点模型DataBlock_Predict_Float, 这使得优化用于游戏场景图的带宽确实非常容易。

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.