shutil.copy()不能用于分片传输,因其仅支持本地文件操作,不支持网络传输、断点续传、并发控制和校验;跨服务器分片需手动管理偏移读取、http range头、连接复用及远端原子拼接。

分片传输为什么不能直接用 shutil.copy()?
因为 shutil.copy() 是本地文件操作函数,不处理网络、断点续传、并发或校验。跨服务器分片传输本质是「把一个大文件切开 → 分别上传 → 远端拼接」,必须自己控制读取偏移、HTTP/FTP协议行为、连接复用和失败重试。
如何用 requests 实现带偏移的分片上传?
核心是利用 HTTP 的 Range 请求头 + 服务端支持 Content-Range 响应。Python 客户端只需按块读取本地文件,用 requests.put() 或 requests.post() 发送每一片:
- 先用
os.path.getsize()获取总大小,确定分片大小(如 5MB) - 循环中用
file.seek(offset)定位,file.read(chunk_size)读取二进制数据 - 请求头里必须加
Content-Range: bytes {start}-{end}/{total}(服务端需识别该头) - 推荐用
requests.Session()复用连接,避免每次新建 TCP 开销
示例片段:
with open(local_path, "rb") as f:
for start in range(0, total_size, chunk_size):
end = min(start + chunk_size - 1, total_size - 1)
f.seek(start)
data = f.read(end - start + 1)
headers = {"Content-Range": f"bytes {start}-{end}/{total_size}"}
resp = session.put(url, data=data, headers=headers)
if resp.status_code not in (200, 206):
raise RuntimeError(f"Upload failed at {start}: {resp.text}")
远端拼接时容易忽略的权限和原子性问题
服务端收到分片后,不能简单 cat part_0 part_1 > final —— 这会引发竞态和权限丢失:
- 拼接前检查所有分片是否齐全(比如用
redis记录已上传的part_id) - 用
os.rename()替代直接写入目标路径,保证最终文件出现是原子的 - 拼接进程需有写入目标目录的权限,且目标文件父目录不能被其他进程清空
- 若用 NFS 或对象存储,注意其不支持传统文件锁,得靠外部协调(如 etcd 或数据库状态表)
要不要自己实现断点续传?
要,但别从零写。优先复用成熟协议:
- 如果服务端支持 S3 API,直接用
boto3的upload_fileobj(),它内置分片和断点续传逻辑 - 如果走 WebDAV,用
webdav4库,支持PUT分片和PROPFIND查询已上传段 - 自研时,客户端必须持久化记录每个分片的
offset和etag(或 MD5),重启后比对服务端已存分片再跳过
最常踩的坑是:本地记录分片状态用临时文件但没 fsync(),崩溃后状态丢失,导致重复上传或漏传。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











