首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >recv()函数太慢

recv()函数太慢
EN

Stack Overflow用户
提问于 2013-11-02 18:59:18
回答 3查看 12.5K关注 0票数 8

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

问题来了--我有两台电脑(一台是旧的英特尔凌动上网本,另一台是英特尔i5 NTB)。我想实现至少5FPS(上网本降低了NTB的速度,但不是很大,现在我大约有1,5FPS),但是在每台机器上调用recv()函数两次主循环总共需要大约0.5秒。wifi信号很强,路由器是300Mbit/s,它会发送大约500个字符的短字符串。如您所见,我使用time.clock()来测量时间。

下面是我通常在i5 NTB上运行的“服务器”代码的一部分:

代码语言:javascript
复制
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

下面是“客户端”游戏代码的一部分(刚刚颠倒了开头的发送/接收部分):

代码语言:javascript
复制
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上它有50FPS在地图的左上角和25FPS在右下角( 1000x1000像素的地图包含5x5像素的方块,我认为它更慢,因为更大的坐标,但我不敢相信这么多。顺便说一句,recv在地图右下角作为局域网游戏运行时需要大约。同时)在Atom上网本上,它有4-8 FPS。

所以你能告诉我为什么它这么慢吗?计算机是不同步的,一个更快,另一个更慢,但这不可能是他们在等待对方,最大延迟是0,17秒,对吧?另外,长时间的recv调用只会在速度更快的计算机上进行?我也不太清楚send/recv函数是如何工作的。奇怪的是,sendall几乎不需要时间,而接收只需要0,5秒。也许sendall正试图在后台发送,而程序的其余部分仍在继续。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-11-02 22:00:19

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

代码语言:javascript
复制
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

这可能会减少recv由于没有数据而休眠的时间。

正如维基百科所说:

TCP这个算法与

延迟确认的交互很糟糕,延迟确认是在20世纪80年代早期引入TCP的一个特性,但它是由不同的组引入的。在启用这两种算法的情况下,对TCP连接执行两次连续写入,然后在第二次写入的数据到达目的地后才完成读取的应用程序将经历高达500毫秒的恒定延迟,即“确认延迟”。因此,TCP实现通常为应用程序提供禁用Nagle算法的接口。这通常称为TCP_NODELAY选项。

你的基准测试中提到了0.5,所以这可能是一个原因。

票数 5
EN

Stack Overflow用户

发布于 2013-11-02 20:08:39

可以,send()或sendall()将在后台发生(除非连接现在已经饱和,即已经有太多数据等待发送)。相比之下,recv()只有在数据已经到达时才会立即获取数据,如果没有数据到达,它会等待。然后,它可能会返回其中的一小部分。(我假设c是TCP套接字,而不是UDP套接字。)请注意,您不应该假设recv(N)返回N个字节;您应该编写如下函数:

代码语言:javascript
复制
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
EN

Stack Overflow用户

发布于 2019-03-26 00:36:59

当我遇到同样的问题时,我发现python中的socket recv超级慢。对我来说(几天后)的解决办法是做一些大致的事情:

代码语言:javascript
复制
  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

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

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19741196

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档