直接用socket传文件易卡住或丢数据,因TCP无消息边界,需自定义协议:先发8字节文件大小,再分块发送并校验;接收端先读取长度头,再循环recv_exact直至收满。

为什么直接用 socket 传文件容易卡住或丢数据?
因为 socket 默认是流式传输(TCP),没有内置消息边界——你 send() 两次 1KB,接收端可能一次收到 2KB,也可能分三次收到。文件传输必须自己定义协议,否则 recv(1024) 会提前返回、截断内容,尤其在大文件或网络波动时。
- 别依赖
recv()一次性收完:它只保证最多返回指定字节数,不保证收齐 - 发送端不能只调用一次
send():大文件需循环发送,且要检查返回值(实际发出字节数) - 接收端必须按长度循环
recv(),直到收满预期字节数,否则文件损坏
如何安全发送一个完整文件?
核心是「先发文件大小,再发文件内容」。接收端读完头 8 字节(struct.pack('Q', size)),就知道后面该收多少字节。
- 发送端用
struct.pack('Q', os.path.getsize(filepath))发 8 字节长度头 - 接着用
with open(filepath, 'rb') as f:分块读取(如每次 8192 字节),循环send()并检查返回值是否匹配 - 接收端先
recv(8)解包得总长度,再循环recv()累计到缓冲区,直到达到该长度
示例关键片段:
import struct
# 发送端
size = os.path.getsize('test.bin')
conn.send(struct.pack('Q', size))
with open('test.bin', 'rb') as f:
while (chunk := f.read(8192)):
sent = conn.send(chunk)
if sent != len(chunk): # 实际可能没发完
raise OSError(f'send truncated: {sent}/{len(chunk)}')接收端怎么避免 recv() 返回空或阻塞?
recv() 在连接关闭时返回空 bytes,在未超时前不会主动报错——所以必须靠长度控制,不能靠 while data: 这种逻辑。
立即学习“Python免费学习笔记(深入)”;
- 设置
socket.settimeout(30)防止无限等待(尤其对方异常断连) - 接收长度头时,必须用
recv_exact(conn, 8)辅助函数,内部循环调用recv()直到收满 - 文件内容接收同样用
recv_exact(),传入已知总长度 - 不要用
recv(0)或recv(1)做探测——无意义且低效
简易 recv_exact 写法:
def recv_exact(sock, n):
buf = b''
while len(buf) < n:
chunk = sock.recv(n - len(buf))
if not chunk:
raise ConnectionResetError("connection closed prematurely")
buf += chunk
return buf为什么不能直接传中文路径或带空格的文件名?
原始 socket 不处理编码,文件名属于应用层信息。如果想传名字,必须额外协商字段(比如在大小头前再加 256 字节文件名 UTF-8 编码 + \0 结尾),否则接收端根本不知道存成什么名。
- 最简方案:约定固定文件名(如
'received.bin'),跳过命名问题 - 若需传名,发送端先
name.encode('utf-8').ljust(256, b'\0'),接收端name_bytes.rstrip(b'\0').decode('utf-8') - 注意:Windows 路径含
\,跨平台建议统一用/或os.path.normpath()处理
真正麻烦的不是传输,而是两端对协议的理解是否一致——少一个字节头、多一次 close,文件就废了。



















