资讯动态

libcurl 多接口通知回调 CURLMOPT_NOTIFYFUNCTION 深入解析:事件驱动式多传输状态监测实战

发布时间:2026/9/10 7:46:42 来源:尧图企业网站定制
libcurl 多接口通知回调 CURLMOPT_NOTIFYFUNCTION 深入解析事件驱动式多传输状态监测实战【免费下载链接】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导读CURLMOPT_NOTIFYFUNCTION是 libcurl 多接口multi interface自 8.17.0 起引入的通知机制它允许应用程序注册一个回调函数在 multi handle 内部发生关键状态变化如某个 easy transfer 完成、有新的结果消息可供读取时被主动告知从而摆脱传统模式下不断轮询 multi handle才能感知变化的方式。本文以该选项的官方文档为骨架结合当前 curl 仓库中 include/curl/multi.h、lib/multi_ntfy.c、lib/multi_ntfy.h 与 lib/multi.c 的源码实现为你讲解回调原型、两种通知类型、启用/停用 API并给出可直接编译运行的完整示例。一、为什么需要通知回调在使用 libcurl 多接口时传统的事件循环通常由以下步骤组成调用curl_multi_socket_action()或curl_multi_perform()驱动传输调用curl_multi_info_read()主动询问是否有 transfer 完成、是否有新消息根据返回值决定下一步动作。这种主动询问模式要求应用在每个事件循环迭代里都去轮询 multi handle 的内部状态即使多数情况下根本没有变化。通知回调改变了这一交互方式当 multi handle 处理传输过程中发生变化时libcurl 会主动收集这些变化并在合适的时机把它们分发给应用注册的回调函数应用无需持续查询即可感知并做出响应。官方文档的原话是This can eliminate the need to constantly interrogate the multi handle to observe such changes to act on them.这可以消除为了观察并响应这些变化而不断询问 multi handle 的需要。二、回调原型与选项安装2.1 函数原型#include curl/curl.h void notify_callback(CURLM *multi, /* multi handle */ unsigned int notification, /* notification type */ CURL *easy, /* easy handle */ void *notifyp); /* private notify pointer */ CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_NOTIFYFUNCTION, notify_callback);在 include/curl/multi.h 中这个回调被正式定义为curl_notify_callback类型typedef void (*curl_notify_callback)(CURLM *m, unsigned int notification, CURL *easy, void *user_data);2.2 回调参数说明参数含义multi触发该通知的 multi handlenotification通知类型即发生了什么当前可用取值见下文未来可能新增更多类型easy与本次通知相关的 easy handle可能是应用自己的 easy handle也可能是 libcurl 内部创建的句柄notifyp应用自定义指针通过CURLMOPT_NOTIFYDATA设置与CURLMOPT_NOTIFYFUNCTION配套使用见 CURLMOPT_NOTIFYDATA 文档2.3 与普通回调的关键区别官方文档特别强调notify 回调与其他回调不同它可以在回调内部调用更多 libcurl API 函数。除了以下 5 个函数外它几乎可以调用 multi 与 easy handle 上的所有其他方法curl_multi_perform(3)curl_multi_socket(3)curl_multi_socket_action(3)curl_multi_socket_all(3)curl_multi_cleanup(3)这意味着你可以在 notify 回调里向 multi handle 添加或移除 easy handlecurl_multi_add_handle/curl_multi_remove_handle实现真正的响应式调度——某个传输完成的通知到来时立即在回调内安排下一个任务。2.4 调用时机任意时刻都可能被触发这是一个需要特别小心的特性该回调可能在应用与 libcurl 交互的任何时刻被调用。官方文档明确指出可能在所有传输都结束后才被触发甚至可能发生在curl_multi_cleanup()调用期间——当缓存的连接被关闭时curl_multi_cleanup会被动关闭空闲连接进而触发状态变化。因此回调实现必须足够轻量且自洽不要在回调内假设某个传输仍然存在也不要依赖当前调用栈之外的生命周期状态。三、通知类型详解当前可用的通知类型定义在 include/curl/multi.h#define CURLMNOTIFY_INFO_READ 0 #define CURLMNOTIFY_EASY_DONE 1 #define CURLMNOTIFY_LAST 2 /* last, not used */CURLMNOTIFY_LAST是哨兵值表示最后一个不使用同时充当类型数量的上限。在 lib/multi_ntfy.c 中有一个编译期约束CURLMNOTIFY_LAST必须小于等于 31以保证每种通知类型可以用一个 32 位标志位(uint32_t)1 type表示。3.1 CURLMNOTIFY_INFO_READ有新消息可读当通过curl_multi_notify_enable()启用后每当 multi handle 内部的消息栈从空变为非空时此通知会告诉应用现在可以调用curl_multi_info_read()来读取新消息了。关键语义官方文档原文此通知只在消息被添加到空消息栈时触发一次后续继续追加消息不会再次触发。应用应当在收到通知后把栈中所有可读消息全部取走、清空栈这样下一次有新消息加入时才会再次触发通知。如果应用每次只读一条消息那么后续消息就不会再触发通知可能造成消息滞留。实现上的对应关系在 lib/multi.c 的multi_addmsg()函数中static void multi_addmsg(struct Curl_multi *multi, struct Curl_easy *data) { if(Curl_uint32_bset_empty(multi-msgsent)) CURLM_NTFY(multi-admin, CURLMNOTIFY_INFO_READ); Curl_uint32_bset_add(multi-msgsent,>static void mstate_enter_done(struct Curl_easy *data, CURLMstate from_state) { (void)from_state; CURLM_NTFY(data, CURLMNOTIFY_EASY_DONE); }static void mstate_enter_completed(struct Curl_easy *data, CURLMstate from_state) { ... if(from_state MSTATE_DONE) CURLM_NTFY(data, CURLMNOTIFY_EASY_DONE); ... }从 lib/multi.c 的状态机跳转表可以看到mstate_enter_done挂在DONE状态、mstate_enter_completed挂在COMPLETED状态上正常流程进入DONE时触发一次通知若某些场景直接从更早的状态跳入COMPLETEDfrom_state MSTATE_DONE也会补发一次CURLMNOTIFY_EASY_DONE保证完成事件绝不遗漏。四、启用与停用curl_multi_notify_enable / disable设置回调本身CURLMOPT_NOTIFYFUNCTION只是登记具体哪种通知类型生效还需要显式启用。libcurl 提供了一对配套 API声明见 include/curl/multi.h实现在 lib/multi.cCURLMcode curl_multi_notify_enable(CURLM *m, unsigned int notification); CURLMcode curl_multi_notify_disable(CURLM *m, unsigned int notification);两个函数都返回CURLMcode传入不存在的类型notification CURLMNOTIFY_LAST时返回CURLM_UNKNOWN_OPTION见 lib/multi_ntfy.cCURLMcode Curl_mntfy_enable(struct Curl_multi *multi, unsigned int type) { if(type CURLMNOTIFY_LAST) return CURLM_UNKNOWN_OPTION; multi-ntfy.flags | CURL_MNTFY_TYPE_FLAG(type); return CURLM_OK; }内部实现是对multi-ntfy.flags这一 32 位位图做置位/清位每种类型对应一个独立位因此可以同时启用多种通知例如同时启用CURLMNOTIFY_INFO_READ和CURLMNOTIFY_EASY_DONE。在导出符号表 lib/libcurl.def 中也能看到这两个新 API 已被正式导出。五、通知的收集与分发源码级工作流程了解回调背后的机制有助于写出正确的回调。整个通知系统由 lib/multi_ntfy.c 与 lib/multi_ntfy.h 实现核心数据结构为struct curl_multi_ntfy见 lib/multi_ntfy.hstruct curl_multi_ntfy { curl_notify_callback ntfy_cb; /* 应用注册的回调 */ void *ntfy_cb_data; /* CURLMOPT_NOTIFYDATA 设置的指针 */ struct mntfy_chunk *head; /* 待分发通知队列头部 */ struct mntfy_chunk *tail; /* 待分发通知队列尾部 */ uint32_t flags; /* 启用类型位图 是否有待处理条目标志 */ CURLMcode failure; /* 队列分配失败等错误记录 */ };工作流程分三步第一步触发登记Curl_mntfy_add。传输状态变化时代码通过CURLM_NTFY宏lib/multi_ntfy.h把事件记录下来#define CURLM_NTFY(d, t) \ do { \ if((d) (d)-multi (d)-multi-ntfy.ntfy_cb) \ Curl_mntfy_add((d), (t)); \ } while(0)Curl_mntfy_add()lib/multi_ntfy.c会检查回调已注册、无失败状态、类型合法且已启用然后以struct mntfy_entry { uint32_t mid; uint32_t type; }为单位把通知追加到分块队列每块固定 128 条见CURL_MNTFY_CHUNK_SIZE中。注意此时并不会立即调用应用回调只是排队。第二步队列化管理。队列以 128 条一组的 chunk 链表形式组织lib/multi_ntfy.c避免每次通知都动态分配小块内存队列满时自动追加新 chunk。若内存分配失败会记录CURLM_OUT_OF_MEMORY到failure字段稍后返回给应用。第三步统一分发Curl_mntfy_dispatch_all。在合适的时机multi handle 处理完一批事件后例如 lib/multi.c 与 lib/multi.c 调用Curl_mntfy_dispatch_all(multi)libcurl 依次取出队列中的条目并调用应用回调lib/multi_ntfy.cif(data (multi-ntfy.flags CURL_MNTFY_TYPE_FLAG(e-type))) { /* this may cause new notifications to be added! */ CURL_TRC_M(multi-admin, [NTFY] dispatch %u to xfer %u, e-type, e-mid); multi-ntfy.ntfy_cb(multi, e-type, data, multi-ntfy.ntfy_cb_data); }分发过程中会做两件重要的事情回调用内添加新通知是允许的——源码注释明确写着 this may cause new notifications to be added!这可能导致新的通知被添加进来分发循环会持续处理到队列清空为止因此你在回调里调用curl_multi_add_handle()等操作是安全的分发时会再次检查该类型的启用位——即使事件已入队如果在分发前应用调用了curl_multi_notify_disable()关闭了该类型该条通知会被跳过。六、完整示例以下代码综合了官方文档示例并补全了启用通知、配套数据指针等关键步骤原型参照 include/curl/multi.h#include stdio.h #include curl/curl.h struct priv { void *ours; }; static void notify_cb(CURLM *multi, unsigned int notification, CURL *easy, void *notifyp) { struct priv *p notifyp; printf(notification%u, my ptr: %p\n, notification, p-ours); switch(notification) { case CURLMNOTIFY_INFO_READ: { /* 新消息可读一次性读空消息栈确保后续消息能再次触发通知 */ CURLMsg *msg; int msgs_left 0; while((msg curl_multi_info_read(multi, msgs_left))) { printf(msg: %s (%u remaining)\n, curl_easy_strerror(msg-data.result), msgs_left); } break; } case CURLMNOTIFY_EASY_DONE: /* 某个传输完成成功或失败都会触发。此处可安全调用 curl_multi_remove_handle / curl_multi_add_handle 安排下一个任务 */ printf(easy handle %p done\n, (void *)easy); break; default: break; } } int main(void) { struct priv setup; CURLM *multi curl_multi_init(); /* ... use socket callback and custom pointer ... */ /* 1. 注册通知回调 */ curl_multi_setopt(multi, CURLMOPT_NOTIFYFUNCTION, notify_cb); /* 2. 配套设置自定义指针回调的 notifyp 参数即取自这里 */ curl_multi_setopt(multi, CURLMOPT_NOTIFYDATA, setup); /* 3. 显式启用想要的通知类型可同时启用多种 */ curl_multi_notify_enable(multi, CURLMNOTIFY_INFO_READ); curl_multi_notify_enable(multi, CURLMNOTIFY_EASY_DONE); /* ... 添加 easy handle、驱动 multi 事件循环 ... */ curl_multi_cleanup(multi); return 0; }要点回顾CURLMOPT_NOTIFYDATA设置的指针会原样出现在回调的notifyp参数中与CURLMOPT_NOTIFYFUNCTION配合使用只用curl_multi_setopt注册回调还不够必须用curl_multi_notify_enable()启用具体类型后通知才会真正派发处理CURLMNOTIFY_INFO_READ时务必用循环curl_multi_info_read()读空消息栈。七、默认值与返回值DEFAULTNULL即默认不注册任何通知回调RETURN VALUEcurl_multi_setopt(multi, CURLMOPT_NOTIFYFUNCTION, ...)返回CURLM_OK表示设置成功CURLMcode 枚举定义于 include/curl/multi.h。八、可用性与注意事项该选项自libcurl 8.17.0起加入文档 front-matter 中的Added-in: 8.17.0适用于所有协议front-matter 中Protocol: All与传输协议无关该特性属于实验性新 API依赖 8.17.0 之后的 libcurl 版本编译前请确认链接的 libcurl 版本号curl_version_info()或curl-config --version回调可能在curl_multi_cleanup()期间因缓存连接关闭而被调用回调内避免访问已释放的资源回调携带的easy可能是内部句柄DoH 等场景不要假定它一定来自应用在回调内可以安全使用除curl_multi_perform(3)、curl_multi_socket(3)、curl_multi_socket_action(3)、curl_multi_socket_all(3)、curl_multi_cleanup(3)之外的所有 multi/easy API包括添加与移除 easy handle。相关文档CURLMOPT_NOTIFYDATA为通知回调设置自定义指针curl_multi_socket_actionmulti 接口事件驱动核心函数curl_multi_info_read读取完成消息栈curl_multi_performmulti 接口驱动函数multi interface 总览multi 接口编程模型通知机制核心实现lib/multi_ntfy.c、lib/multi_ntfy.h通知触发与启用/停用 APIlib/multi.c、include/curl/multi.h【免费下载链接】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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价