C++如何构建支持断点续传的文件下载模块?(网络协议实现)

3次阅读

断点续传需手动构造range头并校验服务端响应,用fopen(“r+b”)+fseek精准写入,分段下载须独立临时文件合并,禁用自动重试并持久化断点状态。

C++如何构建支持断点续传的文件下载模块?(网络协议实现)

http Range 请求必须手动构造,curl 默认不启用断点续传

很多开发者以为用 curl_easy_setopt 设置了 CURLOPT_RESUME_FROM 就万事大吉,其实这只是起点。真正起作用的是 HTTP Range 头,而 CURLOPT_RESUME_FROM 只是让 libcurl 自动加这个头——前提是服务端支持且返回 206 Partial Content。如果服务端忽略 Range 或返回 200 OK,下载就从头开始。

实操建议:

立即学习C++免费学习笔记(深入)”;

  • 下载前先发一个 HEAD 请求,检查响应头是否含 Accept-Ranges: bytesContent-Range
  • 若文件已存在,用 stat() 获取本地文件大小,作为续传起点传给 CURLOPT_RESUME_FROM
  • 务必设置 CURLOPT_HTTPHEADER 手动追加 "Range: bytes=xxx-"(尤其在自定义 HTTP 客户端时)
  • 收到响应后,检查状态码:不是 206 就得清空本地文件重下,否则写入会错位

fseek()fwrite() 的偏移与追加模式要严格匹配

本地文件续传的本质是“跳过已有字节,从末尾继续写”。但 c++ 标准库的 std::ofstream 默认打开是覆盖模式,ios::app 又强制写到末尾、无法指定偏移——这和断点续传要求矛盾。

实操建议:

立即学习C++免费学习笔记(深入)”;

  • 用 C 风格 fopen(filename, "r+b") 打开文件(可读可写,不截断)
  • 调用 fseek(fp, offset, SEEK_SET) 定位到断点位置,再用 fwrite() 写入新数据
  • 避免用 std::ofstreamseekp() + write() 组合,某些平台(如 windows)下缓冲区行为不稳定
  • 写入后记得 fflush(fp),防止断电或崩溃导致最后几 KB 丢失

线程并发下载同一文件时,fseek/fwrite 不是原子操作

想靠分段下载加速?别直接多个线程往同一个文件句柄里写。即使每个线程都 fseek 到不同偏移,fwrite 仍可能因内核缓冲、文件系统缓存或线程调度顺序错乱,导致数据覆盖或空洞。

实操建议:

立即学习C++免费学习笔记(深入)”;

  • 分段下载必须为每段分配独立临时文件(如 part_001.tmp, part_002.tmp),完成后用 rename() 合并
  • 合并阶段才用单线程顺序读取各分段、fseek 定位写入目标文件
  • 不要依赖 lseek() + write() 的原子性——POSIX 不保证跨线程安全,linux 下也仅对小于 PIPE_BUF 的写入有原子性保障
  • 若必须单文件直写,至少用 flock() 加文件锁,但会显著降低并发收益

服务器返回 Connection: close 时,libcurl 可能静默重连并丢弃 Range

有些 CDN 或反向代理在长连接关闭后,libcurl 会自动重试请求,但默认不重发原始 Range 头——结果就是第二段请求变成全量下载,本地文件被重复写入开头,内容彻底损坏。

实操建议:

立即学习C++免费学习笔记(深入)”;

  • 显式禁用自动重试:curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L) + 捕获 CURLE_PARTIAL_FILE 错误
  • 手动处理连接中断:监听 CURLOPT_XFERINFOFUNCTION,若传输卡住超时,主动 cleanup 并重建 handle
  • 每次重试前重新设置 CURLOPT_RESUME_FROMCURLOPT_HTTPHEADER,不能依赖上一次配置残留
  • 记录已成功写入的字节数到外部状态文件(非内存变量),避免进程重启后丢失断点位置

断点续传真正的复杂点不在协议层,而在状态一致性:网络不可靠、磁盘 I/O 不可预测、服务端行为不统一。最容易被忽略的是“本地文件大小”和“服务端实际可续传位置”的校验——哪怕只差 1 字节,后续所有写入都会错位,且这种错误往往到合并或校验时才暴露。

text=ZqhQzanResources