recv() 函数太慢
Posted
技术标签:
【中文标题】recv() 函数太慢【英文标题】:recv() function too slow 【发布时间】:2013-11-13 12:08:27 【问题描述】:嗨,我是 Python 的新手。我使用 pygame 模块编写了一个简单的 LAN 游戏(对我来说并不简单)。
问题出在这里 - 我有两台电脑(一台旧英特尔 Atom 上网本,另一台英特尔 i5 NTB)。我想达到至少 5 FPS(上网本正在减慢 NTB,但不是那么多,现在我有大约 1.5 FPS),但是在主循环中调用 recv() 函数两次总共需要大约 0.5 秒机器。 wifi 信号很强,路由器是 300Mbit/s,它发送一个大约 500 个字符的短字符串。如您所见,我使用 time.clock() 来测量时间。
这是“服务器”代码的一部分,我通常在 i5 NTB 上运行:
while 1:
start = time.clock()
messagelen = c.recv(4) #length of the following message (fixed 4 character)
if " " in messagelen:
messagelen = messagelen.replace(" ","")
message = cPickle.loads(c.recv(int(messagelen))) #list of the arrows, other player position and changes in the game map
arrowsmod = message[0]
modtankposan = message[1]
removelistmod = message[2]
for i in removelistmod:
try:
randopos.remove(i)
except ValueError:
randopossv.remove(i)
print time.clock()-start
tosendlist=[]
if len(arrows) == 0: #if there are no arrows it appends only an empty list
tosendlist.append([])
else:
tosendlist.append(arrows)
tosendlist.append([zeltankpos, 360-angle])
if len(removelist) == 0: #if there are no changes of the map it appends only an empty list
tosendlist.append([])
else:
tosendlist.append(removelist)
removelist=[]
tosend=cPickle.dumps(tosendlist)
tosendlen = str(len(tosend))
while len(tosendlen)<4:
tosendlen+=" "
c.sendall(tosendlen) #sends the length to client
c.sendall(tosend) #sends the actual message(dumped list of lists) to client
...something else which takes <0,05 sec on the NTB
这是“客户端”游戏代码的一部分(只是将开头倒转 - 发送/接收部分):
while 1:
tosendlist=[]
if len(arrows) == 0: #if there are no arrows it appends only an empty list
tosendlist.append([])
else:
tosendlist.append(arrows)
tosendlist.append([zeltankpos, 360-angle])
if len(removelist) == 0: #if there are no changes of the map it appends only an empty list
tosendlist.append([])
else:
tosendlist.append(removelist)
removelist=[]
tosend=cPickle.dumps(tosendlist)
tosendlen = str(len(tosend))
while len(tosendlen)<4:
tosendlen+=" "
s.sendall(tosendlen) #sends the length to server
s.sendall(tosend) #sends the actual message(dumped list of lists) to server
start = time.clock()
messagelen = s.recv(4) #length of the following message (fixed 4 character)
if " " in messagelen:
messagelen = messagelen.replace(" ","")
message = cPickle.loads(s.recv(int(messagelen))) #list of the arrows, other player position and changes in the game map
arrowsmod = message[0]
modtankposan = message[1]
removelistmod = message[2]
for i in removelistmod:
try:
randopos.remove(i)
except ValueError:
randopossv.remove(i)
print time.clock()-start
... rest which takes on the old netbook <0,17 sec
当我在 i5 NTB 上的一台机器(没有插座模块)上运行单人游戏版本时,地图左上角有 50 FPS,右下角有 25 FPS( 1000x1000 像素地图包含 5x5 像素方块,我认为它会因为更大的坐标而变慢,但我不敢相信这么多。顺便说一句,在地图右下角作为 LAN 游戏运行时,recv 大约需要相同的时间) 在 Atom 上网本上它有 4-8 FPS。
那你能告诉我,为什么它这么慢吗?计算机不同步,一个更快,另一个更慢,但不可能是他们在等待对方,这将是最大 0,17 秒的延迟,对吧?再加上长时间的 recv 调用只会在速度更快的计算机上? 我也不完全知道发送/接收功能是如何工作的。奇怪的是 sendall 实际上不需要时间,而接收需要 0.5 秒。也许森多 正在尝试在后台发送,而程序的其余部分继续前进。
【问题讨论】:
【参考方案1】:当我遇到同样的问题时,我最终来到这里,python 中的 socket recv 看起来超级慢。对我来说(几天后)的解决方法是按照以下方式做一些事情:
recv_buffer = 2048 # ? guess & check
...
rx_buffer_temp = self._socket.recv(recv_buffer)
rx_buffer_temp_length = len(rx_buffer_temp)
recv_buffer = max(recv_buffer, rx_buffer_temp_length) # keep to the max needed/found
它的要点设置为尝试接收最接近实际预期的字节数。
【讨论】:
【参考方案2】:正如Armin Rigo 所说,recv
将在套接字接收到数据包后返回,但数据包不一定需要在调用send
后立即发送。虽然send
立即返回,但操作系统会在内部缓存数据,并且可能会等待一段时间以等待更多数据写入套接字,然后再实际传输它;这称为Nagle's algorithm 并避免通过网络发送大量小数据包。您可以禁用它并更快地将数据包推送到网络;尝试在 sending 套接字上启用TCP_NODELAY
选项(或者如果您的通信是双向的,则两者都启用),通过调用:
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
这可能会减少 recv
由于没有数据而休眠的时间。
正如***所说:
这个算法与 TCP 延迟确认的交互很糟糕,一个 早期大致同时引入 TCP 的特性 1980年代,但由不同的群体。启用这两种算法后, 对 TCP 连接进行两次连续写入的应用程序, 然后是在数据之后才会完成的读取 从第二次写到已经到达目的地,体验一次 高达 500 毫秒 的恒定延迟,即“ACK 延迟”。为了这 原因,TCP 实现通常为应用程序提供一个 用于禁用 Nagle 算法的接口。这通常称为 TCP_NODELAY 选项。
在您的基准测试中提到了 0.5 秒,所以这可能是一个原因。
【讨论】:
Nagle 的算法不会影响 send() 的时间。 send() 只是写入套接字发送缓冲区然后返回。所有对线路的实际写入、数据包合并等都与应用程序异步进行,不会由计时器测量。 @EJP 你是对的,我在这里的措辞不是很小心——send
函数本身没有延迟,但是与内核将数据推送到线路相关的延迟(以及这里是 Nagle 的算法)可以在recv
一侧看到,并且可能会影响那里的计时器。
另请注意,他在recv
周围有计时器,而不是send
。我不知道这是否是 Nagle 的错,但我认为几率足够高,可以试一试并围绕这个主题进行一些实验。
就是你说的!我设置了 NODELAY,recv 时间通常是
【参考方案3】:
是的,send() 或 sendall() 将在后台发生(除非连接现在已经饱和,即已经有太多数据等待发送)。相反,recv() 只有在数据已经到达时才会立即获取数据,但如果没有,它会等待。然后它可能返回它的一小部分。 (我假设 c 是 TCP 套接字,而不是 UDP 套接字。)请注意,您不应假设 recv(N) 返回 N 个字节;你应该写一个这样的函数:
def recvall(c, n):
data = []
while n > 0:
s = c.recv(n)
if not s: raise EOFError
data.append(s)
n -= len(s)
return ''.join(data)
不管怎样,说到点子上了。问题不在于 recv() 的速度。如果我理解正确的话,有四个操作:
服务器渲染(1/25 秒)
服务器在套接字上发送一些东西,由客户端接收;
客户租户(1/4 秒);
客户端在套接字上发回一些东西。
这几乎需要(0.3 + 2 * network_delay)
秒。没有什么是并行发生的。如果你想要更多的每秒帧数,你需要并行化这四个操作中的一些。例如,让我们合理地假设操作 3 是迄今为止最慢的。这是我们如何使 3 与其他三个操作并行运行的方法。您应该更改客户端,使其接收数据、处理数据并立即向服务器发送应答;只有这样它才会继续渲染它。在这种情况下,这应该足够了,因为执行此渲染需要 1/4 秒,这应该足以让答案到达服务器、服务器进行渲染以及再次发送下一个数据包。
【讨论】:
理想的方法是将一个线程用于网络,另一个用于主游戏循环? 不,不,我描述的 4 个操作发生在不同的物理位置:您不需要线程。 感谢 recvall 提示。您的意思是 1 字节的 buff 大小并不总是一个字符?但是现在它可以工作,否则会出现泡菜错误。另外我忘了提到,当我进行实验并将这些代码行与游戏分开时(+- 我发布的代码相同,但有一个示例发送列表)并且我运行它们时没有这样的延迟。两个 recv 调用需要 0,1 或 0,01 秒,我不完全记得,但可以肯定它远小于 0,5 - 也是@tomasz。那很奇怪。抱歉,我没有得到并行说明。我该怎么做? 现在通信是双向的,客户端和服务器几乎是同一个程序,但首先客户端发送数据,服务器接收,然后服务器发送数据,客户端接收。 (它可以被逆转,但这并不重要吗?)然后两个程序都“渲染”图形并进行游戏处理。然后它重复。 (每台机器的数据长度接收和数据接收总共需要 0.5 秒)所以你能再解释一下吗?我是个菜鸟 如果需要,画一张图:确实应该颠倒过来。大约 1 字节 == 1 个字符:阅读 socket.recv() 的文档以了解更多信息:recv(N) 可以返回最多 N 字节的随机数据量。以上是关于recv() 函数太慢的主要内容,如果未能解决你的问题,请参考以下文章