recv()函数太慢了

时间:2013-11-02 10:59:18

标签: python sockets python-2.7 pygame

嗨,我是Python的新手。我正在使用pygame模块编写一个简单的局域网游戏(对我来说并不简单)。

这是问题 - 我有两台电脑(一台旧的英特尔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像素正方形,我认为它因坐标更大而速度较慢,但​​我无法相信这么多。在地图右下角作为局域网游戏运行时BTW recv大约需要同时进行) 在Atom上网本上它有4-8 FPS。

那么请你告诉我,为什么这么慢?计算机不同步,一个更快,另一个更慢,但它不能相互等待,它会延迟最多0.17秒,对吗?加上长时间的recv调用只会在更快的计算机上? 另外,我不知道send / recv函数是如何工作的。这很奇怪,sendall在接收到0.5秒时几乎没有时间。也许sendall 在节目的其余部分继续前进的同时,我正试图在后台发送。

3 个答案:

答案 0 :(得分:3)

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

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

由于没有数据,这可能会减少recv正在睡觉的时间。

正如维基百科所说:

  

此算法与TCP延迟确认非常相互作用,a   功能在早期大致同时引入TCP   20世纪80年代,但由一个不同的小组。启用两种算法后,   对TCP连接执行两次连续写入的应用程序,   然后是读数,直到数据之后才会满足   从第二次写到达目的地,体验一下   持续延迟高达<500>毫秒,即“ACK延迟”。为了这   原因,TCP实现通常为应用程序提供   接口禁用Nagle算法。这通常被称为   TCP_NODELAY选项。

您在基准测试中看到的是0.5s,所以这可能是一个原因。

答案 1 :(得分: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秒才能完成渲染,这应该足以让答案到达服务器,渲染服务器,再次发送下一个数据包。

答案 2 :(得分: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

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