Подтвердить что ты не робот

Получение сокета Python - входящие пакеты всегда имеют разный размер

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

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()

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

4b9b3361

Ответ 1

Сеть всегда непредсказуема. 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

Ответ 2

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

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

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

Подробнее см. документацию.

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

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

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

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

Используя обычные сокеты python, я обнаружил, что вы можете отправлять и получать информацию в пакетах с помощью 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)

Ответ 6

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

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

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

Обратите внимание, что для отправки пустого сообщения TCP существует no way. socket.send(b'') просто не будет работать.

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