TCP 一次 recv 读到半条消息:用长度前缀恢复消息边界
TCP 应用接收到的是字节流。一次 recv() 可能只给出消息的一部分,也可能读到多条消息的字节。协议需要约定边界。这里用 4 字节无符号大端长度加正文,读取方先拼齐头部,再按声明的长度读取正文。
1. 本机复现
保存为 frame_lab.py,运行 python frame_lab.py。脚本使用本机套接字对,并让每次读取最多取两个字节,稳定覆盖多次读取才能拼齐的分支。
from socket import socketpair
from struct import pack, unpack
MAX_FRAME = 1024
def read_exact(sock, size, *, allow_eof=False):
data = bytearray()
while len(data) < size:
chunk = sock.recv(min(2, size - len(data)))
if not chunk:
if allow_eof and not data:
return None
raise EOFError(f"expected {size} bytes, got {len(data)}")
data.extend(chunk)
return bytes(data)
def read_frame(sock):
header = read_exact(sock, 4, allow_eof=True)
if header is None:
return None
(size,) = unpack("!I", header)
if size > MAX_FRAME:
raise ValueError(f"frame too large: {size}")
return read_exact(sock, size)
def encode(body):
return pack("!I", len(body)) + body
def run(wire):
sender, receiver = socketpair()
with sender, receiver:
sender.settimeout(2)
receiver.settimeout(2)
sender.sendall(wire)
sender.close()
frames = []
while (body := read_frame(receiver)) is not None:
frames.append(body)
return frames
frames = run(encode(b"hello") + encode(b"") + encode(b"bye"))
assert frames == [b"hello", b"", b"bye"]
print("frames:", frames)
for wire, expected in (
(b"\x00\x00", "expected 4 bytes, got 2"),
(pack("!I", 5) + b"ab", "expected 5 bytes, got 2"),
(pack("!I", MAX_FRAME + 1), "frame too large: 1025"),
):
try:
run(wire)
except (EOFError, ValueError) as error:
assert str(error) == expected
print(error)
else:
raise AssertionError("accepted an invalid frame")
本地实测输出如下。
frames: [b'hello', b'', b'bye']
expected 4 bytes, got 2
expected 5 bytes, got 2
frame too large: 1025
2. 区分正常关闭、消息截断和长度错误
| 读取状态 | 处理 |
|---|---|
| 还没读到下一个头的任何字节,就收到 EOF | 流正常结束 |
| 头或正文已经开始读取,随后收到 EOF | 当前消息截断,不能交给业务处理 |
| 长度超过协议上限 | 在分配正文缓冲区前拒绝,并关闭连接 |
| 长度为零 | 按协议处理空正文。本例允许空消息 |
示例里的两字节读取上限是为了覆盖分支。生产中可根据剩余字节数和缓冲区容量决定读取量。如果需要限制慢速连接,给整条消息设置截止时间。仅给每次 recv() 单独设置超时,无法限制持续少量发送的对端把消息拖多久。
小结
为字节流定义明确的长度格式和上限。仅将读取完整的帧交给业务,并区分帧边界上的 EOF 和帧内截断。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| RFC 9293 第 2.2 节 | https://www.rfc-editor.org/rfc/rfc9293.html#section-2.2 | TCP 提供可靠、有序的字节流 |
| Python Socket Programming HOWTO | https://docs.python.org/3.11/howto/sockets.html#using-a-socket | 读写循环、EOF 与消息分帧 |
核验说明
- Windows、Python 3.11.9 用 socketpair 实测连续三帧、空消息、头和正文截断及超长声明。读取上限由示例控制,没有测真实网络的分段和吞吐。