断点续传可靠性核心在于“确认写入后立刻记位置”,而非猜测进度;需先读状态再定起点,分块写入时依成功/失败/边界走三路分支,状态与数据强绑定,异常仅作安全兜底。

断点续传在不可靠网络下不靠“猜进度”,而靠“确认写入后立刻记位置”。分支结构在这里不是指 if-else 控制流本身,而是指根据当前状态是否存在、是否匹配、是否完整,走不同逻辑路径——这才是真正支撑断点续传可靠性的分支判断。
先读状态,再定起点:避免从0硬起
每次启动传输前,必须主动检查本地是否有进度记录,而不是默认从头开始:
- 尝试读取
.progress文件(如纯文本存一个数字)或数据库中该文件的最新last_offset - 若文件不存在或解析失败 → 视为首次上传,
start_offset = 0 - 若读取成功 → 用
os.path.getsize("target.file")校验实际文件大小;若二者不一致,以文件大小为准(磁盘更可信) - 最终确定
start_offset = max(recorded_offset, actual_file_size),防止“记录虚高”或“文件残缺”
分块写入时的三路分支:成功 / 失败 / 边界
每一块的处理不是线性推进,而是依据结果进入明确分支:
-
写入成功且落盘完成(
f.write(chunk)+os.fsync(f)返回无误)→ 立即更新.progress文件为start_offset + len(chunk),然后推进下一块 -
网络请求失败或超时(如
requests.exceptions.Timeout)→ 不更新进度,保留原.progress值,等待重试;重试时仍用原 offset 发起 Range 请求 -
读取末尾块时字节数不足(
remaining )→ 主动调整本次读取长度,不强行补零;写入后仍按实际字节数更新 offset
状态与数据强绑定:拒绝“假完成”分支
不能让“状态更新”和“数据写入”脱钩。常见错误分支要主动拦截:
- 禁止在
upload()调用前更新进度 → 防止请求未发就记“已传” - 禁止仅凭 HTTP 200 就认为块已落盘 → 服务端可能接收但未写入磁盘,客户端必须等
fsync或关闭流后再记 - 禁止用“临时文件存在”代替状态标记 → 中断可能留下损坏块,应以
.progress+ 实际文件大小双重验证 - 合并阶段打开目标文件时禁用
O_TRUNC→ 防止中断导致全清空;只用ab(追加)或r+b(读写)并显式seek
异常兜底不是主干,而是守门员
异常捕获不驱动流程,只做安全收口:
- 在循环外层包裹
try/except,捕获KeyboardInterrupt、SystemExit、IO 错误等强制中断 - 在
except块中,不做重试,只确保.progress未被污染(例如:不更新、或回滚到上一稳定值) - 退出前可选打印当前
start_offset,方便人工校验;不依赖异常来“触发续传”,它只防丢进度






