8

嗨,我是 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 秒。也许 sendall 正试图在后台发送,而程序的其余部分继续前进。

4

3 回答 3

5

正如 Armin Rigo 所说,recvsocket 接收到数据包后会返回,但调用后不一定需要立即传输数据包send。在send立即返回时,操作系统会在内部缓存数据,并且可能会等待一段时间,以便在实际传输之前将更多数据写入套接字;这称为Nagle 算法,可避免通过网络发送大量小数据包。您可以禁用它并更快地将数据包推送到网络;尝试启用发送TCP_NODELAY套接字上的选项(如果您的通信是双向的,则两者都启用),通过调用:

sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

recv由于没有数据,这可能会减少睡眠时间。

正如维基百科所说:

该算法与 TCP 延迟确认的交互很糟糕,TCP 延迟确认是在 1980 年代初大致同时引入 TCP 的一个功能,但由不同的团队引入。启用这两种算法后,对 TCP 连接执行两次连续写入,然后在第二次写入的数据到达目的地后才会完成读取的应用程序会经历长达500 毫秒的持续延迟,“确认延迟”。出于这个原因,TCP 实现通常为应用程序提供一个接口来禁用 Nagle 算法。这通常称为 TCP_NODELAY 选项。

您在基准测试中看到了 0.5 秒,因此这可能是一个原因。

于 2013-11-02T14:00:18.977 回答
2

是的,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 秒,这应该有足够的时间让答案到达服务器、服务器渲染以及再次发送下一个数据包。

于 2013-11-02T12:08:39.323 回答
0

当我遇到同样的问题时,我最终来到这里,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

它的要点设置为尝试接收最接近实际预期的字节数。

于 2019-03-25T16:36:59.240 回答