Знаете ли вы, почему TCP не сохраняет границы сообщений?
Клиент отправляет hello и world двумя отдельными вызовами sendall(). На тестах кажется, что сервер сможет получить их двумя соответствующими вызовами recv(), но TCP такой связи не гарантирует. Он передает упорядоченный поток байтов, а каждый recv() возвращает доступную часть этого потока в пределах указанного размера.
import socket
server = socket.create_server(("127.0.0.1", 0))
client = socket.create_connection(server.getsockname())
connection, _ = server.accept()
client.sendall(b"hello")
client.sendall(b"world")
client.shutdown(socket.SHUT_WR)
data = b""
while chunk := connection.recv(1024):
data += chunk
print(data)
# b'helloworld'
Получатель видит десять байтов подряд. В потоке нет информации о том, были они отправлены как hello и world или разбиты между вызовами иначе. Кроме того, один большой send() может потребовать нескольких чтений, потому что recv(1024) возвращает максимум 1024 байта.
Границы добавляет протокол приложения. Например, перед сообщением можно передать его длину:
import socket
import struct
def receive_exactly(sock, size):
data = b""
while len(data) < size:
chunk = sock.recv(size - len(data))
if not chunk:
raise EOFError("connection closed")
data += chunk
return data
def receive_message(sock):
size = struct.unpack("!I", receive_exactly(sock, 4))[0]
return receive_exactly(sock, size)
server = socket.create_server(("127.0.0.1", 0))
client = socket.create_connection(server.getsockname())
connection, _ = server.accept()
for message in (b"hello", b"world"):
client.sendall(struct.pack("!I", len(message)) + message)
print(receive_message(connection))
print(receive_message(connection))
# b'hello'
# b'world'
Первые четыре байта задают длину следующего сообщения. receive_exactly() продолжает читать поток, пока не соберет нужное количество байтов, поэтому код работает при любом разбиении данных между вызовами recv().
TCP гарантирует порядок байтов, а границы сообщений определяет протокол приложения: длиной, разделителем или форматом с собственным фреймингом.
Код на салфетке x Кусочки кода
Клиент отправляет hello и world двумя отдельными вызовами sendall(). На тестах кажется, что сервер сможет получить их двумя соответствующими вызовами recv(), но TCP такой связи не гарантирует. Он передает упорядоченный поток байтов, а каждый recv() возвращает доступную часть этого потока в пределах указанного размера.
import socket
server = socket.create_server(("127.0.0.1", 0))
client = socket.create_connection(server.getsockname())
connection, _ = server.accept()
client.sendall(b"hello")
client.sendall(b"world")
client.shutdown(socket.SHUT_WR)
data = b""
while chunk := connection.recv(1024):
data += chunk
print(data)
# b'helloworld'
Получатель видит десять байтов подряд. В потоке нет информации о том, были они отправлены как hello и world или разбиты между вызовами иначе. Кроме того, один большой send() может потребовать нескольких чтений, потому что recv(1024) возвращает максимум 1024 байта.
Границы добавляет протокол приложения. Например, перед сообщением можно передать его длину:
import socket
import struct
def receive_exactly(sock, size):
data = b""
while len(data) < size:
chunk = sock.recv(size - len(data))
if not chunk:
raise EOFError("connection closed")
data += chunk
return data
def receive_message(sock):
size = struct.unpack("!I", receive_exactly(sock, 4))[0]
return receive_exactly(sock, size)
server = socket.create_server(("127.0.0.1", 0))
client = socket.create_connection(server.getsockname())
connection, _ = server.accept()
for message in (b"hello", b"world"):
client.sendall(struct.pack("!I", len(message)) + message)
print(receive_message(connection))
print(receive_message(connection))
# b'hello'
# b'world'
Первые четыре байта задают длину следующего сообщения. receive_exactly() продолжает читать поток, пока не соберет нужное количество байтов, поэтому код работает при любом разбиении данных между вызовами recv().
TCP гарантирует порядок байтов, а границы сообщений определяет протокол приложения: длиной, разделителем или форматом с собственным фреймингом.
Код на салфетке x Кусочки кода