Функция recv() слишком медленная

Привет, я довольно новичок на Python. Я пишу простую игру в локальной сети (не просто для меня), используя модуль pygame.

Здесь проблема - у меня два компьютера (один старый нетбук Intel Atom, другой intel i5 NTB). Я хочу достичь не менее 5 FPS (нетбук замедляет NTB, но не так много, теперь у меня около 1,5 FPS), но при вызове функции recv() дважды основной цикл занимает около 0,5 секунды на каждом машина. Wi-Fi-сигнал силен, а маршрутизатор - 300 Мбит/с, и он отправляет короткую строку размером около 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 пытается отправить в фоновом режиме, пока остальная часть программы продолжается.

2 ответа

Как уже упоминалось Армином Риго, recv будет возвращаться после приема пакетов сокетом, но пакеты не обязательно должны быть переданы сразу после вызова send. В то время как send возвращается немедленно, ОС кэширует данные внутри и может подождать некоторое время для записи большего количества данных в сокет, прежде чем передавать его; это называется алгоритм Nagle и позволяет отправлять много мелких пакетов по сети. Вы можете отключить его и быстрее направить пакеты на провод; попробуйте включить параметры TCP_NODELAY в отправляющем сокете (или оба, если ваша связь двунаправлена), вызвав это:

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

Это может привести к сокращению времени recv спящего из-за отсутствия данных.

Как говорится в Википедии:

Этот алгоритм плохо взаимодействует с задержками с задержкой TCP, функция, введенная в TCP примерно в одно и то же время в начале 1980-е годы, но другой группой. Если оба алгоритма включены, приложения, которые выполняют две последовательные записи в TCP-соединение, за которым следует чтение, которое не будет выполнено до тех пор, пока данные от второй записи достигло места назначения, испытайте постоянная задержка до 500 миллисекунд, "Задержка ACK". Для этого причине, реализации TCP обычно предоставляют приложениям интерфейс для отключения алгоритма Nagle. Обычно это называется Параметр TCP_NODELAY.

Есть упоминание о 0,5 с, которое вы видите в своем тесте, поэтому это может быть причиной.


Да, 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 секунды, что должно быть достаточным для ответа на запрос сервера, сервера для рендеринга и последующего отправки следующего пакета.

licensed under cc by-sa 3.0 with attribution.