A stream socket exposes an ordered byte stream, so one recv call does not identify an application message boundary.
Python socket recv: assemble a bounded frame across partial reads
Operation contract
The local socket pair carries a two-byte big-endian length followed by a payload. read_exact keeps receiving until its requested count is assembled or EOF rejects truncation. It intentionally requests at most two bytes per call, making the loop exercise partial application reads even on the local fixture. read_frame checks the declared size before requesting the body. A second pair contains a length of five but only two body bytes, then closes its sending direction; the receiver detects that short frame.
Failure and ownership boundary
A timeout on each receive call is not an absolute operation deadline: a peer can keep delivering tiny chunks before each timeout. The fixture uses already sent local bytes, no remote host, and a one-second socket timeout. Python struct: a fixed-endian receipt record with exact byte length, Python asyncio timeout: cancellation and cleanup ownership and Python bounded gzip decoding: cap expanded output before publishing it cover independent layers.
Working program
import socket
def read_exact(stream, count):
chunks = bytearray()
while len(chunks) < count:
chunk = stream.recv(min(2, count - len(chunks)))
if not chunk:
raise EOFError("truncated frame")
chunks.extend(chunk)
return bytes(chunks)
def read_frame(stream):
length = int.from_bytes(read_exact(stream, 2), "big")
if length > 32:
raise ValueError("frame cap")
return read_exact(stream, length)
for wire in (b"\x00\x05R0041", b"\x00\x05R0", b"\x00\x21"):
sender, receiver = socket.socketpair()
with sender, receiver:
receiver.settimeout(1)
sender.sendall(wire)
sender.shutdown(socket.SHUT_WR)
try:
print("payload:", read_frame(receiver))
except (EOFError, ValueError) as failure:
print(type(failure).__name__)Output
payload: b'R0041'
EOFError
ValueErrorCosts and limits
The accepted body has at most 32 bytes, so this frame reader has bounded retained payload storage. Repeated frames still need a connection-wide count, byte budget and absolute deadline. A successful frame leaves following bytes for the next frame; it is not proof that the stream has no extra messages.
Common Mistakes
- recv may return fewer bytes than requested without reaching EOF.
- Check declared length before reading or allocating the body.
Connected lessons
Python struct: a fixed-endian receipt record with exact byte length, Python asyncio timeout: cancellation and cleanup ownership, Python JSON Lines: cap line bytes and validate the whole batch before returning it.
