C++断点续传需手动构造Range请求头,用libcurl配置CURLOPT_RANGE;校验Content-Range响应头并解析总大小;文件写入须seekp偏移或系统调用pwrite/WriteFile;进度存独立文件并原子更新;恢复时校验文件大小与记录offset一致。

如何用 C++ 发起带 Range 头的 HTTP 下载请求
核心是手动构造并发送带 Range 请求头的 GET 请求,不能依赖默认全量下载逻辑。主流 HTTP 客户端库(如 libcurl)原生支持,但必须显式启用和配置,否则会忽略断点续传语义。
-
libcurl需调用curl_easy_setopt(handle, CURLOPT_RANGE, "bytes=1024-")设置范围,注意字符串格式必须为"bytes=start-end"或"bytes=start-"(后者表示从 start 到末尾) - 若服务端不支持
Range,响应状态码通常是200 OK而非206 Partial Content,此时需主动校验响应头中的Content-Range字段是否存在,否则可能误以为续传成功 - 避免在未检查服务端支持前硬设
Range:某些 CDN 或老旧服务器会直接返回416 Range Not Satisfiable,应先做一次 HEAD 请求探查Accept-Ranges: bytes响应头
如何安全地追加写入已存在的下载文件
断点续传的本质是「跳过已有字节、从指定偏移开始写入」,不是覆盖或重命名。C++ 标准文件流默认不提供原子偏移写入能力,必须绕过缓冲、直连系统调用或精细控制 seekp。
- 使用
std::ofstream时,务必以std::ios::binary | std::ios::in | std::ios::out模式打开,并在写入前调用file.seekp(offset);仅用std::ios::app会强制追加到末尾,完全失效 - 更可靠的做法是用底层
open()+pwrite()(Linux/macOS)或CreateFile()+SetFilePointerEx()+WriteFile()(Windows),规避 C 运行时缓冲干扰,尤其对 >2GB 文件更稳定 - 写入后必须调用
fsync()(或FlushFileBuffers())确保数据落盘,否则断电或崩溃会导致文件末尾出现零填充或截断
如何持久化记录下载进度并防重复/错乱
进度不能只存在内存里——进程退出、崩溃或用户中止后必须能精确恢复。关键不是存「百分比」,而是存「已写入的字节数」和「原始文件总大小(如果已知)」。
- 推荐将进度存为独立小文件(如
xxx.downloading),内容为纯文本两行:offset=123456789和total=987654321;避免与主文件同名加后缀(如.part),防止用户误删 - 写入进度文件前,先
rename()旧进度文件为临时名,再写新内容,最后rename()回原名——利用文件系统原子性保证状态一致性 - 恢复时,必须校验已存在文件的实际大小是否等于记录的
offset,不相等则说明上次写入异常中断,应删除文件重下,否则可能产生数据错位
为什么 Content-Length 不可靠而必须依赖 Content-Range
服务端对 Range 请求返回的 Content-Length 是本次响应体长度,不是整个文件长度;真正需要的是响应头里的 Content-Range: bytes 1024-999999/1000000 中的分母(即总大小)。
立即学习“C++免费学习笔记(深入)”;
- 解析
Content-Range时别手写正则:用std::string::find()找"/",再取其后子串转整数;注意分母可能是"*"(表示未知总长),此时无法预估进度,只能按字节累加 - 若服务端返回
206但没带Content-Range,属于协议违规,应拒绝接受该响应,避免后续逻辑错乱 - 不要假设所有分块大小一致:最后一块往往更小,
Content-Length会变化,进度计算必须基于累计写入 offset,而非块数 × 固定大小
最易被忽略的是并发写入冲突——多个线程/进程同时操作同一文件且未加锁,或者用不同句柄打开同一文件却没设共享标志,会导致部分写入丢失。断点续传必须是单写入流 + 显式偏移控制,没有捷径。



















