2009-11-10 4 views
27

Я использую модуль SocketServer для TCP-сервера. У меня возникает проблема с функцией recv(), потому что входящие пакеты всегда имеют разный размер, поэтому, если я укажу recv(1024) (я пробовал с большим значением и меньше), он застревает после 2 или 3 запросов, потому что длина пакета будет меньше (я думаю), а затем сервер застрянет до таймаута.Python socket receive - входящие пакеты всегда имеют разный размер

class Test(SocketServer.BaseRequestHandler): 

def handle(self): 

    print "From:", self.client_address 

    while True:  

    data = self.request.recv(1024) 
    if not data: break 

    if data[4] == "\x20":    
     self.request.sendall("hello") 
    if data[4] == "\x21": 
     self.request.sendall("bye") 
    else: 
     print "unknow packet" 
    self.request.close() 
    print "Disconnected", self.client_address 

launch = SocketServer.ThreadingTCPServer(('', int(sys.argv[1])),Test) 

launch.allow_reuse_address= True; 

launch.serve_forever() 

Если клиент посылает запросы кратные через тот же порт источника, но сервер застревает, любая помощь будет очень признателен, спасибо!

ответ

31

Сеть всегда непредсказуема. TCP делает много такого случайного поведения для вас. Одна замечательная вещь, которую выполняет TCP: она гарантирует, что байты будут поступать в том же порядке. Но! Это не гарантируют, что они будут изнашиваться таким же образом. Вы просто не можете предположить, что каждый send() с одного конца соединения приведет к тому, что ровно один recv() на дальнем конце с точно таким же количеством байтов.

Когда вы говорите socket.recv(x), вы говорите: «Не возвращайся, пока не прочитаешь x байтов из сокета». Это называется «блокировка ввода-вывода»: вы заблокируете (подождите), пока ваш запрос не будет заполнен. Если каждое сообщение в вашем протоколе было ровно 1024 байта, вызов socket.recv(1024) будет работать отлично. Но похоже, что это неправда. Если ваши сообщения являются фиксированным числом байтов, просто передайте это число в socket.recv(), и все готово.

Но что делать, если ваши сообщения могут иметь разную длину? Первое, что вам нужно сделать: прекратить вызов socket.recv() с явным номером. Изменение этого:

data = self.request.recv(1024) 

к этому:

data = self.request.recv() 

означает recv() всегда будет возвращаться всякий раз, когда он получает новые данные.

Но теперь у вас есть новая проблема: откуда вы знаете, когда отправитель отправил вам полное сообщение? Ответ таков: вы этого не делаете. Вам нужно будет сделать длину сообщения явной частью вашего протокола. Вот лучший способ: префикс каждого сообщения длиной, либо как целое число фиксированного размера (преобразованное в сетевой порядок байтов с использованием socket.ntohs() или socket.ntohl(), пожалуйста!), Либо как строка, за которой следует какой-то разделитель (например, «123:»). Этот второй подход часто менее эффективен, но в Python это проще.

Как только вы добавили это в свой протокол, вам нужно изменить свой код, чтобы обрабатывать recv(), возвращая произвольные объемы данных в любое время. Вот пример того, как это сделать. Я пробовал писать его как псевдокод или с комментариями, чтобы рассказать вам, что делать, но это было не очень понятно. Поэтому я написал это явно с использованием префикса длины как строки цифр, заканчивающихся двоеточием.Здесь вы идете:

length = None 
buffer = "" 
while True: 
    data += self.request.recv() 
    if not data: 
    break 
    buffer += data 
    while True: 
    if length is None: 
     if ':' not in buffer: 
     break 
     # remove the length bytes from the front of buffer 
     # leave any remaining bytes in the buffer! 
     length_str, ignored, buffer = buffer.partition(':') 
     length = int(length_str) 

    if len(buffer) < length: 
     break 
    # split off the full message from the remaining bytes 
    # leave any remaining bytes in the buffer! 
    message = buffer[:length] 
    buffer = buffer[length:] 
    length = None 
    # PROCESS MESSAGE HERE 
+22

Hans L в комментарии ниже, что в python request.recv() не является допустимым вызовом как bufsize, если обязательный параметр. В идеале этот ответ следует удалить или отредактировать. http://docs.python.org/library/socket.html – prashantsunkari

+0

«Это называется« блокирование ввода-вывода »:« Мне нравится разбивка этого на непрофессиональных условиях .... – repzero

2

Это характер TCP: протокол заполняет пакеты (нижний уровень - это IP-пакеты) и отправляет их. Вы можете иметь некоторый контроль над MTU (Maximum Transfer Unit).

Другими словами: вы должны разработать протокол, который будет проходить поверх TCP, где определено ваше определение «полезной нагрузки». Под «разделением полезной нагрузки» я подразумеваю способ извлечения единицы сообщения, поддерживаемого вашим протоколом. Это может быть так же просто, как «каждая строка с завершающим NULL».

113

Ответ Ларри Гастингс имеет много общих советов о гнездах, но есть несколько ошибок, как она относится к тому, как метод recv(bufsize) работает в модуле сокета Python.

Таким образом, чтобы уточнить, так как это может ввести в заблуждение тех, кто стремится это за помощью:

  1. BUFSIZE парам для метода recv(bufsize) не является факультативным. Вы получите сообщение об ошибке, если вы вызываете recv() (без параметра).
  2. Буфер в recv(bufsize) является максимум размер. Recv с радостью вернет меньше байтов, если их будет меньше.

Для получения более подробной информации см. the documentation.

Теперь, если вы получаете данные от клиента и хотите знать, когда вы получили все данные, вам, вероятно, придется добавить его в свой протокол, как предлагает Ларри. См. this recipe для стратегий определения конца сообщения.

Как указывается в этом рецепте, для некоторых протоколов клиент будет просто отключиться, когда будет сделан отправка данных. В этих случаях ваша петля while True должна работать нормально. Если клиент делает не отключить, вам нужно выяснить, каким образом сигнализировать длину вашего контента, разграничить ваши сообщения или реализовать тайм-аут.

Я был бы рад попытаться помочь вам, если бы вы могли опубликовать свой точный клиентский код и описание вашего тестового протокола.

+0

Лучший метод, который я нашел, для определения количества байтов в сообщении/файле/данных, затем отправьте длину сообщения/файла/данных перед сообщением в виде заголовка с разделителем типа ':'. 'recv', пока вы не получите длину сообщения, обнаружив': ', а затем' recv' то, что вам нужно явно на основе заголовка. Если это файл, тогда создайте цикл для 'recv' блоков файла за раз, гарантируя, что размер' recv' делится на 2 до последнего байта (если 'total bytes% 2! = 0'). Я использую этот метод для передачи больших файлов (стоимость GB), и он хорошо подходит для выполнения баров. – DuckPuncher

14

Вы также можете использовать recv(x_bytes, socket.MSG_WAITALL), который, похоже, работает только на Unix, и точно вернет x_bytes.

0

Я знаю, что это старый, но я надеюсь, что это поможет кому-то.

Использование регулярных питона сокетов я обнаружил, что вы можете отправлять и получать информацию в пакетах с использованием SendTo и recvfrom

# tcp_echo_server.py 
import socket 

ADDRESS = '' 
PORT = 54321 

connections = [] 
host = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
host.setblocking(0) 
host.bind((ADDRESS, PORT)) 
host.listen(10) # 10 is how many clients it accepts 

def close_socket(connection): 
    try: 
     connection.shutdown(socket.SHUT_RDWR) 
    except: 
     pass 
    try: 
     connection.close() 
    except: 
     pass 

def read(): 
    for i in reversed(range(len(connections))): 
     try: 
      data, sender = connections[i][0].recvfrom(1500) 
      return data 
     except (BlockingIOError, socket.timeout, OSError): 
      pass 
     except (ConnectionResetError, ConnectionAbortedError): 
      close_socket(connections[i][0]) 
      connections.pop(i) 
    return b'' # return empty if no data found 

def write(data): 
    for i in reversed(range(len(connections))): 
     try: 
      connections[i][0].sendto(data, connections[i][1]) 
     except (BlockingIOError, socket.timeout, OSError): 
      pass 
     except (ConnectionResetError, ConnectionAbortedError): 
      close_socket(connections[i][0]) 
      connections.pop(i) 

# Run the main loop 
while True: 
    try: 
     con, addr = host.accept() 
     connections.append((con, addr)) 
    except BlockingIOError: 
     pass 

    data = read() 
    if data != b'': 
     print(data) 
     write(b'ECHO: ' + data) 
     if data == b"exit": 
      break 

# Close the sockets 
for i in reversed(range(len(connections))): 
    close_socket(connections[i][0]) 
    connections.pop(i) 
close_socket(host) 

Клиент подобен

# tcp_client.py 
import socket 

ADDRESS = "localhost" 
PORT = 54321 

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.connect((ADDRESS, PORT)) 
s.setblocking(0) 

def close_socket(connection): 
    try: 
     connection.shutdown(socket.SHUT_RDWR) 
    except: 
     pass 
    try: 
     connection.close() 
    except: 
     pass 

def read(): 
    """Read data and return the read bytes.""" 
    try: 
     data, sender = s.recvfrom(1500) 
     return data 
    except (BlockingIOError, socket.timeout, AttributeError, OSError): 
     return b'' 
    except (ConnectionResetError, ConnectionAbortedError, AttributeError): 
     close_socket(s) 
     return b'' 

def write(data): 
    try: 
     s.sendto(data, (ADDRESS, PORT)) 
    except (ConnectionResetError, ConnectionAbortedError): 
     close_socket(s) 

while True: 
    msg = input("Enter a message: ") 
    write(msg.encode('utf-8')) 

    data = read() 
    if data != b"": 
     print("Message Received:", data) 

    if msg == "exit": 
     break 

close_socket(s) 
1

Обратите внимание, что точная причина почему ваш код заморожен не, потому что вы устанавливаете слишком высокий размер буфера request.recv(). Здесь объясняется What means buffer size in socket.recv(buffer_size)

Этот код будет работать до тех пор, пока не будет получать пустой TCP сообщение в (если вы хотите напечатать это пустое сообщение, он показал бы b''):

while True:  
    data = self.request.recv(1024) 
    if not data: break 

И заметьте, что есть никоим образом отправить пустые сообщения TCP. socket.send(b'') просто не сработает.

Почему? Поскольку пустое сообщение отправляется только при вводе socket.close(), ваш сценарий будет зацикливаться до тех пор, пока вы не закроете свое соединение. Как Hans L указал здесь good methods to end message.