libcurl CURLMOPT_NOTIFYDATA 详解:为 multi 通知回调传递自定义上下文指针
【免费下载链接】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_NOTIFYDATA是 libcurl 多接口(multi interface)中的一个选项,用于向通过CURLMOPT_NOTIFYFUNCTION安装的通知回调传递一个自定义指针(clientp),让应用可以在回调中访问自己的上下文数据。本文以 CURLMOPT_NOTIFYDATA.md 为主线,结合仓库中的 multi.h、multi.c 与 multi_ntfy.c 源码,讲解该选项的原型、默认值、底层存储与分发路径、完整示例以及配合curl_multi_notify_enable的实战用法,帮助你写出事件驱动的 libcurl multi 应用。
选项一览
CURLMOPT_NOTIFYDATA是 libcurl 8.17.0 版本引入的 multi 句柄选项,适用于所有协议。它本身不做任何数据处理,仅承担"携带用户上下文"的角色:
| 属性 | 值 |
|---|---|
| 选项名 | CURLMOPT_NOTIFYDATA |
| 类型 | CURLOPTTYPE_OBJECTPOINT(对象指针) |
| 枚举编号 | 19(见 multi.h) |
| 默认值 | NULL |
| 返回值 | CURLM_OK |
| 引入版本 | 8.17.0 |
| 适用协议 | 全部 |
在 multi.h 中,该选项与回调函数选项成对声明:
/* This is the notify callback function pointer */ CURLOPT(CURLMOPT_NOTIFYFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 18), /* This is the argument passed to the notify callback */ CURLOPT(CURLMOPT_NOTIFYDATA, CURLOPTTYPE_OBJECTPOINT, 19),函数原型
#include <curl/curl.h> CURLMcode curl_multi_setopt(CURLM *handle, CURLMOPT_NOTIFYDATA, void *pointer);调用方式与所有curl_multi_setopt一致:第一个参数是curl_multi_init()返回的 multi 句柄,第二个参数是选项名,第三个参数是任意类型指针。libcurl 不会检查、复制或释放该指针指向的内容,它只是原样存储、原样传回。
语义:libcurl 不触碰的 clientp
根据官方文档的说明,CURLMOPT_NOTIFYDATA设置的指针不被 libcurl 触碰,仅作为通知回调的第四个参数clientp传入。
通知回调的原型定义在 multi.h:
typedef void (*curl_notify_callback)(CURLM *m, unsigned int notification, CURL *easy, void *user_data);四个参数的职责:
| 参数 | 含义 |
|---|---|
CURLM *m | 触发本次通知的 multi 句柄 |
unsigned int notification | 通知类型(见下文) |
CURL *easy | 与本次通知关联的 easy 句柄,可能是内部句柄 |
void *user_data | 即CURLMOPT_NOTIFYDATA设置的指针,原样回传 |
也就是说,CURLMOPT_NOTIFYDATA与CURLMOPT_NOTIFYFUNCTION的关系,等价于CURLMOPT_PUSHDATA与CURLMOPT_PUSHFUNCTION的关系:函数指针决定"回调做什么",数据指针决定"回调拿到什么上下文"。
源码实现:指针如何存储与回传
存储:multi 句柄的 ntfy 结构
在 multi_ntfy.h 中,multi 句柄内保存通知机制状态的结构如下:
struct curl_multi_ntfy { curl_notify_callback ntfy_cb; void *ntfy_cb_data; struct mntfy_chunk *head; struct mntfy_chunk *tail; uint32_t flags; CURLMcode failure; };其中ntfy_cb_data字段专门存放CURLMOPT_NOTIFYDATA传入的指针。
setopt:直接存入 ntfy_cb_data
multi.c 中两个选项的解析逻辑相邻:
case CURLMOPT_NOTIFYFUNCTION: multi->ntfy.ntfy_cb = va_arg(param, curl_notify_callback); break; case CURLMOPT_NOTIFYDATA: multi->ntfy.ntfy_cb_data = va_arg(param, void *); break;可以看到实现极其简单:va_arg取出指针后直接赋值,没有校验、没有拷贝。这印证了文档中"libcurl 不触碰该指针"的描述——指针生命周期完全由调用方管理。
回传:dispatch 时作为第四个参数
通知被触发后,libcurl 在合适的时机统一分发。分发逻辑在 multi_ntfy.c 的mntfy_chunk_dispatch_all中:
if(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); }注意这行关键代码:multi->ntfy.ntfy_cb_data就是CURLMOPT_NOTIFYDATA设置的指针,原封不动传给回调。从源码结构可以推断,libcurl 采用批量收集、统一分发的策略:通知条目按 128 条一个 chunk 缓存(见 multi_ntfy.c 的CURL_MNTFY_CHUNK_SIZE),在 multi 处理周期中通过Curl_mntfy_dispatch_all一次性派发,派发过程中新产生的通知会追加到队列尾部继续处理。
默认值
CURLMOPT_NOTIFYDATA的默认值为NULL。如果只设置回调而不设置数据指针,回调的notifyp/user_data参数将为NULL,访问前需要判空;因此建议总是成对设置回调与数据。
配套机制:通知类型与开关
数据指针本身没有含义,它的意义取决于回调收到的通知类型。当前版本支持两种通知类型(multi.h):
#define CURLMNOTIFY_INFO_READ 0 #define CURLMNOTIFY_EASY_DONE 1 #define CURLMNOTIFY_LAST 2 /* last, not used */CURLMNOTIFY_INFO_READ:当 multi 句柄的消息栈从空变为非空时触发,提示应用调用curl_multi_info_read读取消息。该通知只在"消息加入空栈"时触发一次,回调应把消息全部读空,后续新消息才会再次触发。CURLMNOTIFY_EASY_DONE:某个 easy 句柄传输结束(成功或失败)时触发。注意这里传入的easy在启用 DoH 等特性时可能是 libcurl 的内部句柄,而非应用自己的句柄。
通知的收集需要显式开启。在 curl_multi_notify_enable.md 中说明:只有同时满足"安装了回调函数"且"该通知类型被 enable"两个条件,通知才会被收集并派发。对应实现是 multi_ntfy.c 中通过位掩码multi->ntfy.flags记录启用的类型:
CURLMcode 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.c 中实现为curl_multi_notify_enable与curl_multi_notify_disable,两者对非法类型返回CURLM_UNKNOWN_OPTION。重复 enable 同一个类型不是错误,disable 同样通过位运算清除对应位。
通知的触发点散落在 multi 状态机中,例如 multi.c 在 easy 句柄进入 DONE 状态时触发:
static void mstate_enter_done(struct Curl_easy *data, CURLMstate from_state) { (void)from_state; CURLM_NTFY(data, CURLMNOTIFY_EASY_DONE); }CURLM_NTFY宏(multi_ntfy.h)会先检查回调是否已安装,再通过Curl_mntfy_add将通知条目追加到队列,避免在没有回调时产生任何开销。
完整可运行示例
下面把官方示例补全为可编译的完整程序,演示CURLMOPT_NOTIFYDATA的典型用法:把应用自定义结构体struct priv的指针交给回调,回调中读取并打印。
#include <stdio.h> #include <curl/curl.h> struct priv { void *ours; /* 应用自定义数据 */ int notify_count; /* 可扩展字段,用于统计通知次数 */ }; /* 通知回调:notifyp 即 CURLMOPT_NOTIFYDATA 传入的指针 */ 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); p->notify_count++; /* ... 在这里处理业务逻辑 ... */ } int main(void) { struct priv setup = {0}; 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 句柄并驱动 multi 循环 ... */ curl_multi_cleanup(multi); return 0; }运行要点:
- 成对设置:
CURLMOPT_NOTIFYFUNCTION与CURLMOPT_NOTIFYDATA应在驱动 multi 循环前设置好; - 显式开启:只设置回调还不够,必须用
curl_multi_notify_enable开启对应通知类型; - 指针生命周期:
&setup是栈上变量,其生命周期必须覆盖整个 multi 使用期间,直到curl_multi_cleanup完成。
回调内可调用的 API 边界
通知回调与其他 libcurl 回调不同,它拥有更宽的使用权限。根据 CURLMOPT_NOTIFYFUNCTION.md 的说明,除以下五个函数外,回调内可以调用 multi 与 easy 句柄上的几乎所有方法,包括向 multi 句柄添加或移除 easy 句柄:
curl_multi_performcurl_multi_socketcurl_multi_socket_actioncurl_multi_socket_allcurl_multi_cleanup
同时官方文档强调:该回调可能在任意时刻被调用,甚至可能在所有传输结束之后、或在curl_multi_cleanup关闭缓存连接的过程中被调用。因此:
- 回调内不要假设"此时没有传输在运行";
setup结构体的释放必须推迟到curl_multi_cleanup返回之后,否则可能在清理阶段触发悬垂指针访问;- 若回调中需要调用 libcurl API,可参考 api.c 中关于
allow_ntfy_cb的标记机制,了解 libcurl 如何对回调期间的 API 调用做防护。
类型检查与错误处理
在 GCC/Clang 环境下,curl_multi_setopt的变参会被 typecheck-gcc.h 中的宏检查,CURLMOPT_NOTIFYDATA要求传入指针类型,误传整型会在编译期告警。
curl_multi_setopt(multi, CURLMOPT_NOTIFYDATA, pointer)正常情况下返回CURLM_OK(值为 0);若传入的option无法识别则返回CURLM_UNKNOWN_OPTION(该选项始终被识别,不会走到 multi.c 的 default 分支)。建议对返回值做一次检查:
CURLMcode rc = curl_multi_setopt(multi, CURLMOPT_NOTIFYDATA, &setup); if(rc != CURLM_OK) { fprintf(stderr, "setopt failed: %d\n", rc); }典型应用场景
结合数据指针与通知回调,可以构建事件驱动的 multi 应用,避免轮询curl_multi_info_read或反复检查状态:
- 传输完成通知:开启
CURLMNOTIFY_EASY_DONE,在回调中通过curl_multi_info_read获取结果并立即添加新任务,实现流水线式任务队列; - 上下文关联:
easy句柄与回调之间没有直接的用户数据通道,CURLMOPT_NOTIFYDATA提供的结构体可以作为共享状态(如全局计数器、日志句柄、应用配置),弥补这一缺口; - 消息消费:开启
CURLMNOTIFY_INFO_READ,收到通知后一次性把消息栈读空,减少主循环中无谓的轮询开销。
相关选项与接口
- CURLMOPT_NOTIFYFUNCTION:与
CURLMOPT_NOTIFYDATA成对使用的回调安装选项; - curl_multi_notify_enable:开启指定通知类型;
- curl_multi_notify_disable:关闭指定通知类型;
- 相关头文件:multi.h;相关实现:multi.c、multi_ntfy.c、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),仅供参考