recv()函数太慢了

Ada*_*dam 6 python sockets pygame python-2.7

嗨,我是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
Run Code Online (Sandbox Code Playgroud)

这是"客户端"游戏代码的一部分(只是颠倒了开头 - 发送/接收部分):

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
Run Code Online (Sandbox Code Playgroud)

当我在i5 NTB上运行一台机器(没有插座模块)的游戏的单人游戏版本时,它在地图的左上角有50 FPS,在右下角有25 FPS(1000x1000像素地图)包含5x5像素的正方形,我认为它因为更大的坐标而变慢,但是我不能相信这么多.在地图的右下角作为局域网游戏运行的BTW recv大约在同一时间) Atom上网本它有4-8 FPS.

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

tom*_*asz 7

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

sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
Run Code Online (Sandbox Code Playgroud)

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

正如维基百科所述:

该算法与 TCP 延迟确认的交互效果很差,TCP 延迟确认是在 20 世纪 80 年代初几乎同一时间引入 TCP 的一项功能,但由不同的团队引入。启用这两种算法后,如果应用程序对 TCP 连接进行两次连续写入,然后执行读取操作,直到第二次写入的数据到达目的地后才会完成读取操作,因此会经历高达500 毫秒的恒定延迟,即“ ACK 延迟”。因此,TCP 实现通常为应用程序提供禁用 Nagle 算法的接口。这通常称为 TCP_NODELAY 选项。

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