ReadCallback 不能直接用于上传是因为 libcurl 将其视为静态数据读取回调,不管理流状态、不自动重试或分块,需手动维护偏移、正确设置 CURLOPT_INFILESIZE_LARGE、返回精确字节数且配合 CURLOPT_UPLOAD=1L 才生效。

ReadCallback 为什么不能直接用于上传
直接用 CURLOPT_READFUNCTION 做上传流,多数人会卡在数据发不出去或只发一次——因为 libcurl 默认把 READFUNCTION 当作「从内存/文件读取待上传内容」的回调,它不负责管理流状态,也不自动重试或分块;你得自己维护 size_t 偏移、处理 CURLOPT_INFILESIZE_LARGE 是否设为 -1(即未知长度)、还要主动告诉 curl 何时结束(返回 0)。
常见错误现象:CURLOPT_READFUNCTION 被调用一次后 curl 就断连,或者返回 CURLE_SEND_ERROR,其实是回调返回值没按规范:必须严格返回实际写入的字节数,不能少于请求长度,也不能超;返回 0 表示 EOF,但若提前返回 0,curl 就终止上传。
- 上传流必须配合
CURLOPT_UPLOAD= 1L,不是POST -
CURLOPT_READFUNCTION的参数size * nmemb是 curl 本次期望读取的总字节数,不是缓冲区大小 - 回调里不能阻塞太久,否则触发
CURLOPT_TIMEOUT或CURLOPT_LOW_SPEED_LIMIT - 如果流是网络来源(如 socket、pipe),需确保线程安全——libcurl 多数情况下单线程调用回调,但启用多 handle 时可能并发
如何正确实现 ReadCallback 上传动态流
核心是让回调每次按需填充缓冲区,并精确返回已填字节数。比如上传一个运行时生成的 JSON 流:
size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp) {
auto& stream = *static_cast<std::istream*>(userp);
size_t requested = size * nmemb;
if (requested == 0) return 0;
<pre class="brush:php;toolbar:false;">stream.read(static_cast<char*>(ptr), requested);
size_t actual = static_cast<size_t>(stream.gcount());
return actual; // 必须返回实际读取字节数,不能返回 -1 或超限}
立即学习“C++免费学习笔记(深入)”;
关键点:
-
userp必须指向有效生命周期长于 curl easy handle 的对象(比如new std::stringstream或成员变量),不能是栈变量地址 - 如果流提前 EOF,
gcount()为 0,返回 0 即可;但别在流未 EOF 时返回 0 - 若流有缓冲(如
std::stringstream),注意read()可能短读,要依赖gcount(),而非rdstate() - 对于无界流(如传感器实时数据),需配合
CURLOPT_INFILESIZE_LARGE= -1LL,并确保服务器支持 chunked encoding 或 expect-100-continue
上传流必须配哪些 CURLOPT 才生效
漏掉任意一项都可能导致 curl 静默失败或发空包:
-
CURLOPT_UPLOAD= 1L —— 强制进入 upload 模式,否则READFUNCTION被忽略 -
CURLOPT_READFUNCTION=read_callback—— 回调函数指针 -
CURLOPT_READDATA=userp—— 传给回调的上下文,必须非空 -
CURLOPT_INFILESIZE_LARGE=-1LL(未知长度)或具体字节数 —— 不设则 curl 可能等待 EOF 或报错CURLE_UPLOAD_FAILED -
CURLOPT_HTTPHEADER加"Transfer-Encoding: chunked"(仅当服务端支持且长度未知时)—— 否则部分服务器拒绝接收 -
CURLOPT_URL必须是 PUT 或 POST endpoint,且服务端路由要接受 raw body
注意:CURLOPT_POST 和 CURLOPT_POSTFIELDS 与 READFUNCTION 冲突,不能共存;上传流必须走 upload path,不是 form post。
调试 ReadCallback 的典型卡点
curl 不报详细错误,只返回 CURLE_SEND_ERROR 或 CURLE_PARTIAL_FILE,实际问题往往藏在回调细节里:
- 回调返回值为负数或大于
size * nmemb→ curl 直接 abort,日志里看不到原因 -
CURLOPT_READDATA传了 nullptr → 回调里解引用崩溃(debug 版本可能断言,release 版本 UB) - 流对象在回调执行中被析构(如 lambda 捕获局部对象)→ 读到垃圾内存,curl 发送乱码
- 没设
CURLOPT_UPLOAD→ curl 完全无视READFUNCTION,走默认 GET 流程 - 服务端返回 411 Length Required,但你设了
CURLOPT_INFILESIZE_LARGE= -1LL → 说明服务端不支持 chunked,得换 endpoint 或预估长度
建议加一句 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L) 看真实 HTTP 请求头和传输字节,比猜强得多。
流上传真正麻烦的不是回调写法,而是状态同步——比如从 pipe 读,得处理 EAGAIN;从网络 socket 读,得处理 partial read;这些都要在回调里做,而且不能抛异常。


















