用libcurl分段下载需设Range头、验206响应、用pwrite/WriteFile原子写入指定偏移,先HEAD查Accept-Ranges支持,并发时预划分互斥区间且独立操作文件。

如何用 libcurl 发起带 Range 头的 HTTP 请求
分段下载本质是让服务器只返回文件某一段字节,关键在于正确设置 Range HTTP 头。C++ 没有标准库支持,必须依赖第三方网络库,libcurl 是最常用且稳定的选择。
注意:不能直接用 std::fstream 或 fopen 打开 URL —— 那只是本地文件操作;必须走 HTTP 协议层。
- 初始化
curl_easy_init()后,必须调用curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers)添加"Range: bytes=0-1023"这类头 -
Range值格式严格:支持bytes=start-end(含首尾)、bytes=start-(从 start 到末尾)、bytes=-size(末尾 size 字节) - 务必检查服务器响应码是否为
206 Partial Content,不是200 OK;若返回200,说明服务器不支持分块或配置错误 - 调用
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback)自定义写入逻辑,避免数据全载入内存
如何安全地把分段数据写入同一文件的指定偏移位置
多个线程/请求并发写同一个文件时,不能简单用 std::ofstream 默认追加模式(std::ios::app),那会忽略偏移,所有写入都挤在末尾。
正确做法是:以 std::ios::in | std::ios::out | std::ios::binary 打开文件,用 seekp() 定位,再 write()。但要注意平台差异和线程安全。
立即学习“C++免费学习笔记(深入)”;
- Linux/macOS 下,用
open(..., O_RDWR | O_CREAT)+pwrite()更可靠,pwrite()是原子的“定位+写”,无需加锁 - Windows 下对应的是
CreateFile()+SetFilePointerEx()+WriteFile(),或直接用_lseeki64()+write() - 如果用 C++ fstream,每次写前必须调用
file.seekp(offset),且确保offset是std::streampos类型(不要用int或long long直接传) - 多线程写同一文件时,即使
pwrite原子,仍需同步 offset 计算逻辑,否则可能覆盖——分段任务应由调用方预先划分好区间,避免运行时竞争
如何判断服务器是否支持分段下载(Accept-Ranges)
不是所有 HTTP 服务器都支持 Range。强行发 Range 请求可能被忽略(返回完整文件)或直接 416(Requested Range Not Satisfiable)。
稳妥做法是:先发一次 HEAD 请求,检查响应头中是否有 Accept-Ranges: bytes。没有就只能整包下载。
- 用
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L)发 HEAD - 通过
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback)捕获响应头 - 注意有些服务器返回
Accept-Ranges: none或干脆不返回该头,都视为不支持 - 即使支持,也要验证:发一个
Range: bytes=0-0,看是否返回206且Content-Length: 1,而不是整个文件的长度
并发分段下载时,如何避免文件损坏或重复写入
并发本身不难,难点在于任务划分、状态跟踪与异常恢复。常见错误是多个线程写重叠区域,或某个段失败后没重试,导致文件中间缺一块。
- 任务划分必须互斥:比如总大小 10MB,分 4 段,每段 2.5MB,则区间应为
[0, 2599999],[2600000, 5199999]… 用uint64_t算,别用int防溢出 - 每个下载任务应独立打开文件(用
fd或FILE*),写完立刻 close,不要复用同一个句柄跨线程 - 写入前可先用
stat()或fstat()检查目标文件是否存在且大小足够,防止误写到错误路径 - 最易忽略的一点:HTTP 响应体可能包含 BOM、压缩传输(
Content-Encoding: gzip)或 chunked 编码——libcurl默认不自动解压,得手动关掉CURLOPT_ACCEPT_ENCODING或自己解压,否则写入的是压缩流


















