
libcurl 进度回调指南CURLOPT_PROGRESSFUNCTION 详解与迁移至 CURLOPT_XFERINFOFUNCTION【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl本文围绕 libcurlcurl 项目中的进度回调选项CURLOPT_PROGRESSFUNCTION展开讲解其回调原型、触发时机、返回值语义、与CURLOPT_NOPROGRESS及CURLOPT_PROGRESSDATA的配合方式并结合本仓库lib/progress.c、lib/setopt.c、include/curl/curl.h的源码实现剖析其底层调用链。由于该选项自 7.32.0 起被弃用本文还会给出迁移到CURLOPT_XFERINFOFUNCTION的完整方案与可运行的代码示例帮助读者在下载/上传场景中正确实现进度上报、超时中断等能力。概述这个选项解决什么问题CURLOPT_PROGRESSFUNCTION用于为一次 easy 传输注册一个进度回调函数。libcurl 在传输过程中会周期性调用它把将要传输的总字节数和当前已传输的字节数下载与上传各一组告诉应用程序应用程序据此可以更新 UI 进度条、打印日志、或者在满足特定条件时主动中止传输。它适用于所有协议文档标注 Protocol: All在 curl 7.1 中引入。注意该选项自 7.32.0 起已被弃用文档 CURLOPT_PROGRESSFUNCTION.md 的 DEPRECATED 一节明确说明官方建议新代码优先使用 CURLOPT_XFERINFOFUNCTION后者使用整数类型的curl_off_t而非double精度更高。不过了解旧回调的语义仍很有价值——它可以帮助理解 libcurl 进度机制的整体设计且存量代码中仍大量存在。回调原型与参数语义选项的声明与回调原型如下摘自 CURLOPT_PROGRESSFUNCTION.md 的 SYNOPSIS#include curl/curl.h int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow); CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PROGRESSFUNCTION, progress_callback);在include/curl/curl.h中对应的类型定义如下L240-L244typedef int (*curl_progress_callback)(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow);回调的五个参数含义参数含义clientp应用自定义指针由CURLOPT_PROGRESSDATA传入libcurl 不解析它只是原样透传给回调dltotal本次传输 libcurl 预期下载的总字节数dlnow到目前为止已下载的字节数ultotal本次传输 libcurl 预期上传的总字节数ulnow到目前为止已上传的字节数需要特别注意两点文档 DESCRIPTION 明确强调未知或未使用的参数会被置 0。例如纯下载场景上传相关的ultotal/ulnow始终保持 0反过来纯上传场景下载参数为 0。回调在数据量未知之前就会先被调用若干次。程序必须容忍总大小还是 0的早期调用不能一上来就做除以 0 之类的运算。回调的触发时机与调用频率libcurl 在传输期间以频繁的间隔调用该回调取代其内部的默认进度函数在传输缓慢甚至无数据传输的空闲期调用频率会降低到大约每秒一次。从源码看这一节流逻辑实现在 lib/progress.c 的progress_calc()中L458 起libcurl 维护速度采样记录与上次显示时间delta.lastshow_us当距上次回调不足 1 秒(elapsed_us - p-delta.lastshow_us) (1000 * 1000)见 L529-L533且传输未结束时直接返回FALSE从而保证回调不会被高频刷屏。此外使用 multi 接口时空闲期间回调不会被调用除非你调用执行传输的对应 libcurl 函数如curl_multi_perform等。这意味着基于 multi 接口的进度刷新依赖你的事件循环主动驱动。返回值语义继续、中止还是恢复默认回调的返回值有三种情况直接影响传输走向返回值行为0一切正常libcurl 继续传输CURL_PROGRESSFUNC_CONTINUE让 libcurl继续执行默认的进度函数即内部进度条输出1或其他非 0 值让 libcurl中止本次传输easy 接口返回CURLE_ABORTED_BY_CALLBACKCURL_PROGRESSFUNC_CONTINUE在 include/curl/curl.h L236 定义#define CURL_PROGRESSFUNC_CONTINUE 0x10000001底层判断逻辑在 lib/progress.c 的pgrsupdate()L658-L704rc >#include stdio.h #include curl/curl.h struct progress { char *private_data; size_t size; }; static int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) { struct progress *memory clientp; printf(private: %p\n, (void *)memory-private_data); if(dltotal 0) { /* 避免早期 dltotal0 的调用导致除零 */ int percent (int)(dlnow * 100.0 / dltotal); printf(download: %d%% (%0.0f / %0.0f bytes)\n, percent, dlnow, dltotal); } /* 业务中止示例下载超过 1MB 就停 */ if(dlnow 1024 * 1024) return 1; return 0; /* all is good */ } int main(void) { struct progress data { my-private-data, 0 }; CURL *curl curl_easy_init(); if(curl) { CURLcode result; /* pass struct to callback */ curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, data); curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progress_callback); /* 必须显式关闭 NOPROGRESS 才会触发回调 */ curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); curl_easy_setopt(curl, CURLOPT_URL, https://example.com/file.bin); result curl_easy_perform(curl); if(result CURLE_ABORTED_BY_CALLBACK) fprintf(stderr, transfer aborted by callback\n); curl_easy_cleanup(curl); } return 0; }要点回顾CURLOPT_PROGRESSDATA传入的结构体指针会成为回调的clientp用于在回调与应用主逻辑之间共享状态回调里必须对dltotal 0的早期调用做保护返回1触发CURLE_ABORTED_BY_CALLBACK中止别忘了CURLOPT_NOPROGRESS置 0。源码级原理回调在传输管线中的位置整个进度机制由 lib/progress.c 驱动核心函数是Curl_pgrsUpdate()L713与内部pgrs_update()L706最终汇聚到pgrsupdate()L658。其职责顺序为调用progress_calc()计算当前速度、判断是否到展示时间点节流 1 秒若设置了fxferinfo新回调优先调用之否则若设置了fprogress本选项对应的旧回调调用之L662-L697依据返回值决定CURL_PROGRESSFUNC_CONTINUE→ 继续执行内部progress_meter()输出默认进度条L699-L700非 0 → 返回CURLE_ABORTED_BY_CALLBACK0 → 继续传输。选项解析侧CURLOPT_PROGRESSFUNCTION在 lib/setopt.c L2538-L2547 被处理它把函数指针存入s-fprogress并同步设置data-progress.callback TRUE表示不再使用内部进度显示若传入NULL则恢复内部默认行为。另外一次传输结束Curl_pgrsDone()lib/progress.c L188或重试时总大小会被重置如 L209、L218 的Curl_pgrsSetDownloadSize(data, -1)这与回调早期传入的总大小可能为 0的行为一致——进度数据是动态更新而非一次性确定的。弃用说明与迁移到 CURLOPT_XFERINFOFUNCTIONCURLOPT_PROGRESSFUNCTION自7.32.0起被标记为弃用在 include/curl/curl.h L1345-L1348 中有正式声明CURLOPTDEPRECATED(CURLOPT_PROGRESSFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 56, 7.32.0, Use CURLOPT_XFERINFOFUNCTION),替代选项CURLOPT_XFERINFOFUNCTION自 7.32.0 引入两者对比如下对比项CURLOPT_PROGRESSFUNCTION旧CURLOPT_XFERINFOFUNCTION新参数类型doublecurl_off_t64 位整数数据精度浮点可能损失大文件精度精确到字节clientp 配套选项CURLOPT_PROGRESSDATACURLOPT_XFERINFODATA与前者同值状态已弃用推荐使用调用时机、节流、中止语义与新版一致与旧版一致迁移只需三步回调参数类型从double改为curl_off_t选项名改为CURLOPT_XFERINFOFUNCTION数据指针选项名改为CURLOPT_XFERINFODATA注意 include/curl/curl.h L1353 中两者是同一个值因此迁移后旧代码若仍用CURLOPT_PROGRESSDATA也能工作但建议统一为新名。迁移示例语义与上文完全等价示例原型出自 CURLOPT_XFERINFOFUNCTION.md#include stdio.h #include curl/curl.h struct progress { char *private_data; size_t size; }; static int xferinfo_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { struct progress *memory clientp; printf(my ptr: %p\n, (void *)memory-private_data); if(dltotal 0) { int percent (int)(dlnow * 100 / dltotal); printf(download: %d%% (%lld / %lld bytes)\n, percent, (long long)dlnow, (long long)dltotal); } return 0; /* all is good */ } int main(void) { CURL *curl curl_easy_init(); if(curl) { struct progress data { my-private-data, 0 }; /* pass struct to callback */ curl_easy_setopt(curl, CURLOPT_XFERINFODATA, data); /* enable progress callback getting called */ curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferinfo_callback); curl_easy_setopt(curl, CURLOPT_URL, https://example.com/file.bin); curl_easy_perform(curl); curl_easy_cleanup(curl); } return 0; }源码层面新老回调在 lib/progress.c 的pgrsupdate()中共享同一套调用框架L662-L697且共用progress_client作为 clientp——这也是迁移成本如此之低的原因。返回值与错误处理curl_easy_setopt()对该选项的调用返回CURLcodeCURLE_OK (0)表示设置成功非 0 表示出错具体错误码参见 libcurl-errors对应手册页libcurl-errors(3)。注意设置回调本身几乎不会失败真正的运行时错误——即回调返回非 0 导致的中止——会通过curl_easy_perform()的返回值以CURLE_ABORTED_BY_CALLBACK体现需要在使用侧捕获处理。相关选项CURLOPT_NOPROGRESS置 0 才会触发进度回调默认关闭CURLOPT_XFERINFOFUNCTION本选项的官方推荐替代7.32.0 起可用CURLOPT_VERBOSE开启详细输出便于配合调试进度行为。小结CURLOPT_PROGRESSFUNCTION是 libcurl 最经典的进度回调接口通过double形式的下载/上传总量与已传输量配合clientp上下文指针应用可以自由实现进度条、日志与主动中止逻辑其1 秒节流、早期总大小未知、multi 接口需主动驱动等行为特征至今沿用。虽然该选项已被弃用但它与CURLOPT_XFERINFOFUNCTION共用同一套实现框架s-fprogress/s-fxferinfoprogress_clientpgrsupdate()理解旧接口即理解了新接口。新项目请直接使用CURLOPT_XFERINFOFUNCTION以获得整数精度旧代码也可按上文三步低成本平滑迁移。【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考