直接用socket写简易ftp易传错或卡死,因socket流式传输无消息边界,ftp依赖命令/响应分离,须严格分离控制通道(按行读取)和数据通道(按长度头收满字节)。

为什么直接用 socket 写“简易FTP”容易传错文件或卡死
因为 socket 默认是流式传输,没有内置消息边界——你 send() 两次 1KB 数据,接收端可能一次收到 2KB,也可能分三次收到。FTP 协议本身依赖命令/响应分离(比如 "STOR filename" 后紧接二进制数据),不加帧头或长度前缀,接收方根本不知道哪段是命令、哪段是文件内容。
常见现象:客户端发完 "GET test.txt" 就开始 recv(),结果收到的是混着命令的乱码,或者阻塞在等待永远不到的“文件结束”。
必须加长度头,且命令通道和数据通道要严格分离
真实 FTP 是双通道(控制连接 + 数据连接),简易版可简化,但逻辑不能混:
- 控制通道只收发纯文本指令(
"LIST"、"RETR filename"、"SIZE filename"),每次指令以结尾,接收方按行读取 - 数据通道专用于文件传输,且每次传输前,服务端先发 8 字节十六进制长度(如
b"00000400"表示 1024 字节),客户端再循环recv()直到收满 - 不要用
recv(1024)硬切——网络包大小不可控,必须按声明长度收
# 服务端发文件前
file_size = os.path.getsize(filepath)
conn.send(f"{file_size:08x}".encode()) # 发8字节长度头
with open(filepath, "rb") as f:
while (chunk := f.read(4096)):
conn.send(chunk)
客户端 recv() 必须处理 EAGAIN/EWOULDBLOCK 和不完整读取
局域网虽快,但 recv() 仍可能只返回部分数据,尤其在高并发或小包场景下。直接 recv(1024) 返回 32 字节就往下走,会导致后续解析全错。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
- 对控制通道:用
makefile("r")包装 socket,然后调readline(),它会自动缓冲直到遇到 - 对数据通道:写一个
recv_all(conn, n)辅助函数,循环recv()直到收满 n 字节,每次检查ret == 0(对方关闭)或errno in (EAGAIN, EWOULDBLOCK)(非阻塞时) - 千万别设
settimeout(0.1)做轮询——局域网延迟通常
Windows 下路径和换行符不处理会直接导致文件损坏
服务端用 os.listdir() 返回中文文件名,若没指定编码,Windows 默认 gbk,而 Python 3 socket 只认 bytes;客户端用 "
" 拼命令,但在 Windows 上若服务端用 "
" 解析,就会卡住。
- 所有字符串指令统一用
.encode("utf-8")发送,服务端用.decode("utf-8")解析(加errors="replace"防崩) - 文件操作路径一律用
pathlib.Path,避免os.path.join在不同系统拼出"dir\file.txt"或"dir/file.txt"导致找不到 - 控制指令结尾强制用
b" "(不是" ".encode(),避免多编码一层)
实际跑通的关键点就在这几处:长度头不可省、通道不混用、recv 必须收满、编码和路径不假设系统默认。局域网环境掩盖不了这些底层行为,反而因延迟低让问题更隐蔽——比如偶尔传对,多数时候错,最难排查。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










