拓冰建站拓冰建站
首页 / 资讯中心 / 正文

libcurl 通知机制详解:curl_multi_notify_enable 启用多句柄事件通知

libcurl 通知机制详解curl_multi_notify_enable 启用多句柄事件通知【免费下载链接】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/curlcurl_multi_notify_enable是 libcurl 在 8.17.0 版本引入的 multi 接口扩展用于按需开启 multi handle 上的事件通知类型配合CURLMOPT_NOTIFYFUNCTION回调让应用程序在传输状态发生变化时获得主动推送而不是反复轮询查询状态。本文基于当前仓库的官方文档 curl_multi_notify_enable.md结合 multi.c、multi_ntfy.c 与 multi.h 的源码实现完整讲解该函数的原型、通知类型、调用语义、底层实现原理与实战注意事项帮助读者在基于 libcurl 的多连接并发编程中正确使用这一事件驱动能力。函数概览一次调用开启一类通知curl_multi_notify_enable的函数原型定义在 include/curl/multi.h其声明如下#include curl/curl.h CURLMcode curl_multi_notify_enable(CURLM *multi_handle, unsigned int notification);函数接受两个参数multi_handle调用curl_multi_init()创建的多句柄notification要开启的通知类型取值为CURLMNOTIFY_INFO_READ或CURLMNOTIFY_EASY_DONE之一。函数的作用是在指定的 multi handle 上启用某一种通知类型的收集。当该类型的事件发生时通过CURLMOPT_NOTIFYFUNCTION安装的回调函数会被调用把事件推送给应用程序。适用性说明该函数不限定具体协议对所有 libcurl 支持的协议HTTP、FTP、SMTP、MQTT、WebSocket 等均适用官方文档的 Protocol 字段标记为 All。函数自 libcurl 8.17.0 起加入文档头部元信息Added-in: 8.17.0。使用前提回调与启用二者缺一不可文档明确强调了一个关键约束只有当通知回调已安装通过CURLMOPT_NOTIFYFUNCTION设置并且通知类型被启用时事件才会被收集并分发给回调。也就是说以下两个条件必须同时满足通过curl_multi_setopt(multi, CURLMOPT_NOTIFYFUNCTION, callback)安装回调通过curl_multi_notify_enable()启用至少一种通知类型。回调的原型定义同样位于 include/curl/multi.htypedef void (*curl_notify_callback)(CURLM *m, unsigned int notification, CURL *easy, void *user_data);四个参数的含义分别是m触发通知的 multi handlenotification通知类型即被启用的事件类型easy与事件相关的 easy handleuser_data通过CURLMOPT_NOTIFYDATA设置的私有数据指针。配套的选项文档 CURLMOPT_NOTIFYFUNCTION.md 指出默认情况下CURLMOPT_NOTIFYFUNCTION的取值为NULL即未安装回调。需要同步关注的是 CURLMOPT_NOTIFYDATA.md用于给回调传递自定义上下文。通知类型一览当前仓库中定义了两种通知类型见 include/curl/multi.h未来可能还会新增通知类型宏数值触发时机CURLMNOTIFY_INFO_READ0multi handle 的消息栈中新增了一条消息可通过curl_multi_info_read读取CURLMNOTIFY_EASY_DONE1某个 easy handle 的传输结束成功或失败都会触发CURLMNOTIFY_INFO_READ有消息可读启用该类型后每当 multi handle 向空消息栈添加新消息时会通知应用程序去调用curl_multi_info_read()处理这些消息。这里有一个值得注意的语义细节通知只在消息被加入空栈时触发一次之后继续追加消息不会重复触发。因此回调应一次性读取并清空消息栈这样下一次有新消息加入空栈时才会再次收到通知。该通知传入的easy参数是一个内部句柄internal handle并非应用程序直接创建的 easy handle。CURLMNOTIFY_EASY_DONE传输完成启用该类型后任意 easy handle 结束传输无论成功还是失败都会触发通知。回调中传入的easy参数指向结束传输的 easy handle——既可能是应用程序自己添加的句柄也可能在使用 DoH 或其他特性时是 libcurl 内部的句柄因此回调中不应假设它一定来自应用层。调用语义与行为细节官方文档给出了三点明确的调用语义支持多类型并存可以同时启用多种通知类型互不影响重复启用无害对已经启用的类型再次调用curl_multi_notify_enable不是错误幂等返回CURLM_OK可随时关闭通过配套函数curl_multi_notify_disable可以关闭某个通知类型接口声明见 curl_multi_notify_disable.md 与 include/curl/multi.h。完整示例代码官方文档提供了一个最小化示例展示如何在创建 multi handle 后立即启用通知#include curl/curl.h int main(void) { int rc; CURLM *multi curl_multi_init(); rc curl_multi_notify_enable(multi, CURLMNOTIFY_INFO_READ); /* 检查 rc非 CURLM_OK 表示出错 */ return 0; }结合 CURLMOPT_NOTIFYFUNCTION.md 中的示例一个更完整、可实际运行的用法是将回调安装、数据绑定与通知启用串联起来#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; (void)multi; (void)easy; printf(notification %u, my ptr: %p\n, notification, p-ours); /* 根据 notification 的值区分 CURLMNOTIFY_INFO_READ / CURLMNOTIFY_EASY_DONE 分别调用 curl_multi_info_read() 或处理传输结束后的清理工作 */ } int main(void) { struct priv setup; CURLM *multi curl_multi_init(); curl_multi_setopt(multi, CURLMOPT_NOTIFYFUNCTION, notify_cb); curl_multi_setopt(multi, CURLMOPT_NOTIFYDATA, setup); curl_multi_notify_enable(multi, CURLMNOTIFY_INFO_READ); curl_multi_notify_enable(multi, CURLMNOTIFY_EASY_DONE); /* ... 添加 easy handle、调用 curl_multi_perform 等驱动循环 ... */ return 0; }返回值与错误处理函数返回CURLMcode枚举值。CURLM_OK值为 0表示一切正常非零值表示发生了错误具体错误码参见 libcurl-errors(3) 文档。完整的CURLMcode枚举定义在 include/curl/multi.h与本函数相关的错误码包括CURLM_OK成功CURLM_BAD_HANDLE传入的 multi handle 无效CURLM_OUT_OF_MEMORY内存分配失败CURLM_UNKNOWN_OPTION传入的通知类型不被支持结合实现看即类型值超出当前已知范围CURLM_BAD_FUNCTION_ARGUMENT函数被以错误的参数调用。官方文档特别提醒返回码是针对整个 multi 栈whole multi stack的。即使这些函数返回了 OK个别传输仍可能发生了问题需要在回调或curl_multi_info_read()中进一步核对每个 easy handle 的结果。源码实现解析从 API 入口到事件分发的完整链路要真正理解curl_multi_notify_enable需要沿着仓库源码追踪它的实现链路。API 入口线程安全的守卫封装在 lib/multi.c 中curl_multi_notify_enable与curl_multi_notify_disable都通过CURL_MAPI_ENTER/CURL_MAPI_LEAVE宏进行守卫这保证了多线程环境下对 multi handle 的并发 API 调用安全随后委托给内部函数CURLMcode curl_multi_notify_enable(CURLM *m, unsigned int notification) { struct Curl_mapi_guard guard; CURLMcode mresult CURLM_OK; if(CURL_MAPI_ENTER(guard, m, multi_notify_enable, mresult)) { mresult Curl_mntfy_enable(m, notification); } CURL_MAPI_LEAVE(guard); return mresult; }启用/停用的核心逻辑位集合管理真正实现位于 lib/multi_ntfy.cCURLMcode Curl_mntfy_enable(struct Curl_multi *multi, unsigned int type) { if(type CURLMNOTIFY_EASY_DONE) return CURLM_UNKNOWN_OPTION; Curl_uint32_bset_add(multi-ntfy.enabled, type); return CURLM_OK; } CURLMcode Curl_mntfy_disable(struct Curl_multi *multi, unsigned int type) { if(type CURLMNOTIFY_EASY_DONE) return CURLM_UNKNOWN_OPTION; Curl_uint32_bset_remove(multi-ntfy.enabled, (uint32_t)type); return CURLM_OK; }从中可以看出两点实现事实类型合法性校验当type大于当前最大类型CURLMNOTIFY_EASY_DONE值为 1时直接返回CURLM_UNKNOWN_OPTION从源码层面印证了错误码语义启用集合multi handle 内部通过uint32_bset无符号 32 位位集合见 uint-bset.h记录所有已启用的通知类型因此重复启用不报错天然成立——向集合中添加已存在的元素本就是幂等操作。事件收集回调存在 类型启用 双重校验通知的收集入口是 lib/multi_ntfy.h 中的宏CURLM_NTFY和函数Curl_mntfy_add#define CURLM_NTFY(d, t) \ do { \ if((d) (d)-multi (d)-multi-ntfy.ntfy_cb) \ Curl_mntfy_add((d), (t)); \ } while(0)而 lib/multi_ntfy.c 中的Curl_mntfy_add会进一步校验通知类型是否已启用以及是否处于失败状态只有全部通过才会把事件追加到内部的 chunk 链表中排队等待分发。这正好对应文档中回调安装 类型启用二者缺一不可的约束。从数据结构上看lib/multi_ntfy.h每个 multi handle 维护一个struct curl_multi_ntfy包含回调指针ntfy_cb、用户数据ntfy_cb_data、启用集合enabled以及以 128 条为单位的mntfy_chunk事件队列。触发点事件从何而来在 lib/multi.c 中可以看到两类通知的实际触发位置CURLMNOTIFY_EASY_DONE在状态机迁移到MSTATE_DONE时触发lib/multi.c 的mstate_enter_done以及直接从非 DID 状态跳转到MSTATE_COMPLETED时补发lib/multi.cCURLMNOTIFY_INFO_READ在multi_addmsg()中仅当消息栈为空时触发lib/multi.c与文档中消息加入空栈才通知的描述完全一致。分发时机随驱动循环派发收集到的事件不会立即调用回调而是在合适的时机统一派发。Curl_mntfy_dispatch_all()lib/multi_ntfy.c实现了批量分发从队列头部逐条取出事件再次校验类型仍处于启用状态后调用ntfy_cb并特别处理了回调内部可能产生新通知的递归追加场景。该分发函数由 lib/multi.c 与 lib/multi.c 在 multi 驱动循环如curl_multi_perform路径中调用。值得一提的是这个新机制把持续轮询 multi handle 观察状态变化的模式转变成了由 libcurl 主动推送的事件驱动模式这正是 CURLMOPT_NOTIFYFUNCTION.md 描述的设计意图。回调的调用时机与限制与普通回调的不同CURLMOPT_NOTIFYFUNCTION.md 特别强调了该回调与 libcurl 其他回调的两点不同可调用更多 libcurl API除了curl_multi_perform、curl_multi_socket、curl_multi_socket_action、curl_multi_socket_all和curl_multi_cleanup之外回调内可以调用 multi 与 easy 句柄上的几乎所有其他方法包括向 multi handle 添加/移除 easy handle。这使得回调可以安全地在事件驱动模型中动态管理传输调用时机不可预期回调可能在应用程序与 libcurl 交互的任何时刻被调用甚至可能发生在所有传输结束之后也可能在curl_multi_cleanup()关闭缓存连接的过程中被调用。因此回调实现必须健壮不能依赖调用时机的假设且不要执行过于耗时或可能阻塞驱动循环的操作。测试与 ABI 保障仓库的 tests/data/test1135 测试用例通过test1135.pl校验CURL_EXTERN导出符号的顺序其中明确包含curl_multi_notify_enable与curl_multi_notify_disable见 tests/data/test1135验证了这两个函数在导出表中的正确位置——因为 VMS 与 OS/400 构建依赖该顺序破坏顺序会破坏二进制兼容性。此外lib/libcurl.defWindows 导出定义与 projects/OS400/curl.inc.in 中也同步声明了这两个函数确保跨平台导出一致。总结与最佳实践综合文档与源码使用curl_multi_notify_enable的推荐实践如下先安装回调再启用通知通过curl_multi_setopt设置CURLMOPT_NOTIFYFUNCTION与CURLMOPT_NOTIFYDATA随后调用curl_multi_notify_enable启用所需的通知类型按需启用可多类型并存同时启用CURLMNOTIFY_INFO_READ与CURLMNOTIFY_EASY_DONE是常见组合不再需要时用curl_multi_notify_disable关闭检查返回值调用后检查返回值是否为CURLM_OK尤其注意CURLM_UNKNOWN_OPTION类型越界与CURLM_OUT_OF_MEMORY内部队列分配失败两类错误回调内一次性消费消息收到CURLMNOTIFY_INFO_READ后应立即用curl_multi_info_read()清空消息栈否则下一次新消息加入空栈时不会再次通知回调保持轻量通知回调可能在驱动循环的关键路径上被调用避免在其中做耗时操作如需管理传输利用其可以添加/移除 easy handle的扩展权限进行区分内部句柄回调收到的easy参数可能是 libcurl 内部句柄如 DoH 场景不要假设其一定来自应用层也不要对其做超出文档允许的操作。相关配套文档可进一步阅读 curl_multi_notify_disable.md、CURLMOPT_NOTIFYFUNCTION.md 与 CURLMOPT_NOTIFYDATA.md源码可深入 lib/multi_ntfy.c 与 lib/multi_ntfy.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),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门