断点续传的核心依据是服务端已接收的字节数。客户端需通过head或专用接口查询服务端offset作为续传起点,配合binary模式文件读取、content-range正确构造、本地状态持久化及服务端分片支持等机制协同实现。

断点续传的核心判断依据是文件偏移量
断点续传不是靠“记住文件名”或“猜上次传到哪”,而是必须依赖服务端已接收的字节数(即已写入目标文件的长度)。客户端每次上传前,先用 HEAD 或自定义接口查询该文件在服务端的当前大小,这个值就是续传起点。如果服务端没存这个信息,断点续传就无从谈起——本地文件 fseek 到某处没用,对方不认。
常见错误:只保存本地读取位置,但服务端从头覆盖写入;或用文件修改时间、MD5 做判断,这在分片上传场景下完全失效。
- 服务端必须提供可查询已接收字节数的接口(如
/upload/status?filename=xxx),返回{"offset": 123456} - 客户端用
std::ifstream打开文件时,需指定std::ios::binary模式,否则seekg在 Windows 下可能因换行符转换出错 - 用
file.seekg(offset, std::ios::beg)跳过已传部分,再逐块读取(建议块大小 8KB–64KB,太小增加系统调用开销,太大不利于中断响应)
HTTP 分块上传中 Range 和 Content-Range 的写法
C++ 本身不内置 HTTP 客户端,你大概率会用 libcurl。关键不是“怎么发请求”,而是请求头和 body 的构造是否符合 RFC 7233。服务端是否支持断点续传,取决于它是否解析并尊重 Content-Range,而不是看你有没有发 Range 头。
典型错误:只加 Range: bytes=1000-,却把整个文件内容全发过去——这其实是“范围下载”逻辑,不是续传。
- 续传必须用
POST或PUT,并在 body 前加上Content-Range: bytes 1000-1999/5000(注意空格和斜杠格式) -
libcurl中不能直接设Content-Range后自动截取 body,你需要手动用fread读指定区间,再通过CURLOPT_READFUNCTION提供数据 - 服务端返回
206 Partial Content表示接受分块,200 OK可能意味着它不支持续传、强制重传
本地临时状态文件容易被忽略的细节
网络中断后,你得知道“下次该从哪继续”,这个 offset 不能只存在内存里。但直接写入原文件同名的 .tmp 或 .offset 文件,会引入竞态和清理问题。
更稳妥的做法是把上传状态(文件路径、服务端返回的 upload_id、当前 offset)单独存为 JSON,且带校验字段(如本地文件 mtime + size),避免因文件被替换导致续传错位。
- 状态文件路径建议用
sha256(filename + server_url)哈希生成,避免特殊字符和路径冲突 - 写状态前先
write → fsync → rename,防止崩溃时写一半 - 每次开始上传前,检查状态文件中的本地文件 size 是否与磁盘一致,不一致则视为新上传,清空旧状态
多线程并发上传同一文件的风险
如果你试图用多个线程分别上传不同分片,并共享同一个 offset 状态,大概率会丢数据。C++ 没有原子文件写操作,fseek + fwrite 不是原子的,两个线程同时写同一文件的不同区域,在 ext4/xfs 上虽不损坏文件系统,但可能因缓存、重排序导致部分字节未落盘。
真正安全的并发续传,必须由服务端支持分片合并(如 upload_id + part_number),客户端每个线程只负责一个独立分片,最后由服务端拼接。此时本地不需要维护全局 offset,只需记录各分片是否完成。
- 不要在客户端用
std::ofstream多线程写同一个文件——即使加锁,seekp和write之间仍有窗口 - 若服务端不支持分片,那就老老实实单线程续传,别为了“快”引入不可测行为
- 上传完成后,务必校验服务端最终文件的
Content-MD5或ETag,而不仅是看 HTTP 状态码
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











