Получение сокета 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()
Если клиент отправляет многократные запросы по одному и тому же исходному порту, но сервер застревает, любая помощь будет очень оценена, спасибо!
Ответы
Ответ 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, некоторые хорошие методы для завершения сообщения.