ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

curl_multi_poll 完全指南:libcurl multi 接口的高效事件等待与超时控制

curl_multi_poll 完全指南:libcurl multi 接口的高效事件等待与超时控制 curl_multi_poll 完全指南libcurl 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导读curl_multi_poll()是 libcurl multi 接口异步、非阻塞、可并发的传输接口中用于等待事件的核心函数它负责阻塞当前线程直到 multi handle 管理的任意 easy handle 出现可读写事件、内部定时器到期或调用方传入的超时时间到达。本文以官方文档 docs/libcurl/curl_multi_poll.md 为主体结合 lib/multi.c 中的真实实现与 docs/examples/multi-app.c 等示例完整讲解其函数签名、curl_waitfd结构、超时语义、与curl_multi_wait()的本质区别、多线程唤醒机制以及底层实现原理。读完本文你将能正确使用curl_multi_poll()编写高效、无忙轮询、可被其他线程随时唤醒的并发传输循环。一、为什么需要 curl_multi_polllibcurl 的 multi 接口允许应用程序在同一时刻管理多个并发的 easy handle 传输。典型的事件驱动循环如下调用curl_multi_perform()执行所有需要处理的传输非阻塞做现在能做的事等待网络活动或超时回到第 1 步直到running_handles变为 0。第 2 步等待的质量直接决定了整个应用的 CPU 占用与响应速度。传统的做法是使用curl_multi_fdset()取得 fd_set再用select(2)等待但这有两个明显缺陷select(2)受FD_SETSIZE常见为 1024限制无法处理大量并发连接需要手动拼接 fd_set、管理max_fd代码繁琐易错。curl_multi_poll()正是为了解决这些问题而设计的它基于poll(2)语义从 7.66.0 版本开始提供见文档 frontmatter 中的Added-in: 7.66.0内部自行收集所有 easy handle 的文件描述符支持传递额外的应用自有描述符并且没有 1024 个描述符的上限问题。文档原文明确鼓励使用它替代select(3)This function is encouraged to be used instead of select(3) when using the multi interface to allow applications to easier circumvent the common problem with 1024 maximum file descriptors.二、函数签名与参数详解#include curl/curl.h CURLMcode curl_multi_poll(CURLM *multi_handle, struct curl_waitfd extra_fds[], unsigned int extra_nfds, int timeout_ms, int *numfds);参数类型含义multi_handleCURLM *由curl_multi_init()创建、并已通过curl_multi_add_handle()加入若干 easy handle 的 multi 句柄extra_fdsstruct curl_waitfd[]调用方希望与 libcurl 内部描述符一起等待的额外文件描述符数组可为NULLextra_nfdsunsigned intextra_fds数组的元素个数无额外描述符时传0timeout_msint最长等待毫秒数若传0则立即返回仅做一次非阻塞检查numfdsint *输出参数非NULL时返回发生了感兴趣事件的文件描述符总数含 libcurl 内部描述符与extra_fds中的描述符返回值类型为CURLMcodeCURLM_OK (0)表示一切正常非零值表示出错错误码含义参见 docs/libcurl/libcurl-errors.md。函数声明位于 include/curl/multi.h与curl_multi_wait()的声明相邻二者签名完全一致仅在行为细节上有差异见下文第四节。三、curl_waitfd 结构与事件标志curl_waitfd是仿照poll(2)的pollfd结构设计的定义于 include/curl/multi.hstruct curl_waitfd { curl_socket_t fd; /* 要等待的文件描述符/套接字 */ short events; /* 调用前设置感兴趣的事件掩码 */ short revents; /* 返回后由 libcurl 填充实际发生的事件掩码 */ };三个事件标志位定义于 include/curl/multi.h标志值含义CURL_WAIT_POLLIN0x0001等待读事件例如有新数据到达CURL_WAIT_POLLPRI0x0002等待高优先级读事件例如带外out-of-band数据CURL_WAIT_POLLOUT0x0004等待写事件例如套接字已可无阻塞写入需要注意CURL_WAIT_POLL*是 libcurl 自定义的常量不保证与平台原生POLLIN/POLLOUT/POLLPRI数值相同。libcurl 之所以不用原生pollfd是为了覆盖没有poll()的平台Windows 等参见头文件注释/* Based on poll(2) structure and values. * We do not use pollfd and POLL* constants explicitly * to cover platforms without poll(). */这一点在源码中也有印证lib/multi.c 将extra_fds[i].events中的CURL_WAIT_POLLIN/PRI/OUT翻译为原生POLLIN/POLLPRI/POLLOUT后再加入轮询集轮询结束后lib/multi.c又把原生revents的位反过来映射回CURL_WAIT_POLL*写入extra_fds[i].revents。因此应用程序只需与 libcurl 的抽象位打交道无需关心平台差异。四、等待语义与超时控制curl_multi_poll()的完整等待规则如下阻塞等待轮询 multi handle 内所有 easy handle 所使用的文件描述符阻塞到至少一个描述符出现活动或timeout_ms毫秒到期。内部超时优先如果 multi handle 存在一个比timeout_ms更早到期的内部超时例如传输自身的超时定时器则改用这个更短的时间以保证超时精度。源码实现位于 lib/multi.c在收集完所有套接字之后调用multi_timeout()查询内部定时器取二者较小值/* Use the shorter one of the internal and the caller requested timeout. */ multi_timeout(multi, NULL, timeout_internal); if((timeout_internal 0) (timeout_internal timeout_ms)) timeout_ms timeout_internal;注释还解释了为何要在收集完所有套接字之后才查询内部超时收集套接字的过程可能由协议层和连接过滤器安装新的定时器。无描述符也可等待如果调用方没有提供任何额外描述符且 libcurl 内部也没有任何可等待的描述符此时函数不会立即返回而是老老实实等待timeout_ms毫秒或按内部定时器缩短。这正是它与curl_multi_wait()最关键的差异之一。与 curl_multi_wait() 的两点本质区别curl_multi_wait()文档见 docs/libcurl/curl_multi_wait.md7.28.0 加入与curl_multi_poll()签名相同但存在两处不同不可被唤醒curl_multi_wait()无法被curl_multi_wakeup()唤醒而curl_multi_poll()可以空描述符集时的行为不同当没有额外描述符、libcurl 也没有描述符可等时curl_multi_wait()会立即返回可能造成忙轮询而curl_multi_poll()会按timeout_ms正常等待。curl_multi_wait的文档甚至专门提示读者改用curl_multi_poll()来规避这一行为If no extra file descriptors are provided and libcurl has no file descriptor to offer to wait for, this function returns immediately. (Consider using curl_multi_poll(3) to avoid this behavior.)从实现上可以直观看到这一点curl_multi_poll与curl_multi_wait都调用同一个内部函数multi_wait()仅通过最后一个布尔参数extrawait区分lib/multi.cCURLMcode curl_multi_wait(CURLM *m, ...) { ... mresult multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, FALSE); ... } CURLMcode curl_multi_poll(CURLM *m, ...) { ... mresult multi_wait(m, extra_fds, extra_nfds, timeout_ms, ret, TRUE); ... }而extrawait的作用点在于 lib/multi.cPOSIX 分支下若没有任何 fd 可轮询extrawait TRUE时调用curlx_wait_ms(timeout_ms)真正睡满超时FALSE时则直接跳过等待立即返回else if(extrawait) { /* No fds to poll, but asked to obey timeout_ms anyway. We cannot * use Curl_poll() as it, on some platforms, returns immediately * without fds. */ curlx_wait_ms(timeout_ms); }因此在编写通用事件循环时curl_multi_poll()是比curl_multi_wait()更稳妥的选择。五、通过 curl_multi_wakeup 实现多线程唤醒curl_multi_poll()的一个独特价值在于它可以被curl_multi_wakeup()从任意其他线程安全地唤醒curl_multi_wakeup于 7.68.0 加入文档见 docs/libcurl/curl_multi_wakeup.md若调用时正处于curl_multi_poll()等待中则立即返回若调用时没有正在进行的curl_multi_poll()则会使下一次调用立即返回多次调用可能唤醒同一次等待操作只保证唤醒当前或下一次不保证一一对应对curl_multi_wait()没有任何效果。这是实现优雅停机的经典手段工作线程阻塞在curl_multi_poll()上控制线程或信号处理线程需要结束循环时调用curl_multi_wakeup()将其唤醒。官方文档给出的双线程示例完整代码见 docs/libcurl/curl_multi_wakeup.md核心逻辑如下/* 线程 1传输循环 */ do { CURLMcode mresult; int numfds; mresult curl_multi_perform(multi, still_running); if(mresult CURLM_OK) { /* wait for activity, timeout or wakeup */ mresult curl_multi_poll(multi, NULL, 0, 10000, numfds); } if(time_to_die()) return 1; } while(still_running);/* 线程 2请求退出 */ if(decide_to_stop_thread1()) { set_something_to_signal_thread_1_to_exit(); curl_multi_wakeup(multi); }从实现层面看唤醒机制依赖 libcurl 内部的wakeup_pair套接字对在 lib/multi.c当存在需要遵守timeout_ms的等待条件时libcurl 会把multi-wakeup_pair[0]一并加入轮询集等待结束后若检测到该套接字上有POLLIN则消耗唤醒信号并从事件计数中扣除lib/multi.c#ifdef ENABLE_WAKEUP if(nevents (wakeup_idx 0)) { if(cpfds.pfds[wakeup_idx].revents POLLIN) { (void)Curl_wakeup_consume(multi-wakeup_pair, TRUE); /* do not count the wakeup socket into the returned value */ nevents--; } } #endif而curl_multi_wakeup()本身lib/multi.c只做两件事向wakeup_pair写入信号或Windows 下触发WSASetEvent()因此它只访问 multi 句柄中在初始化后保持不变的字段可以被安全地跨线程调用。六、完整示例等待 libcurl 与自有描述符下面是官方文档 docs/libcurl/curl_multi_poll.md 提供的完整示例在等待 libcurl 内部活动的同时额外监听应用自己的一个文件描述符示例中为fd 2即标准错误输出。extern void handle_fd(int); int main(void) { CURL *easy_handle; CURLM *multi_handle; int still_running 0; int myfd 2; /* this is our own file descriptor */ multi_handle curl_multi_init(); easy_handle curl_easy_init(); /* add the individual easy handle */ curl_multi_add_handle(multi_handle, easy_handle); do { CURLMcode mresult; int numfds; mresult curl_multi_perform(multi_handle, still_running); if(mresult CURLM_OK) { struct curl_waitfd myown; myown.fd myfd; myown.events CURL_WAIT_POLLIN; /* wait for input */ myown.revents 0; /* clear it */ /* wait for activity on curls descriptors or on our own, or timeout */ mresult curl_multi_poll(multi_handle, myown, 1, 1000, numfds); if(myown.revents) { /* did our descriptor receive an event? */ handle_fd(myfd); } } if(mresult ! CURLM_OK) { fprintf(stderr, curl_multi failed, code %d.\n, mresult); break; } } while(still_running); curl_multi_remove_handle(multi_handle, easy_handle); }关键点拆解先 perform 再 poll每次循环先调用curl_multi_perform()驱动传输它会返回仍在运行的手柄数still_running然后才阻塞等待当still_running为 0 时循环结束。myown.revents 0必须在调用前清空revents由 libcurl 在返回时填充清空后可通过myown.revents判断自有描述符是否真的发生了事件示例中据此调用handle_fd(myfd)。超时 1000ms即使网络一直无活动循环也会每 1 秒被唤醒一次重新 perform保证定时器类传输如连接超时、DNS 解析超时的精度。文档还提示这里的numfds会包含发生感兴趣事件的 libcurl 内部描述符与额外描述符的总数。对于只关心 libcurl 自身、不掺入自有 fd的通用场景可以直接传NULL, 0。仓库中 docs/examples/multi-app.c同时并行一个 HTTP 下载和一个 FTP 上传即采用此写法while(still_running) { CURLMcode mresult curl_multi_perform(multi, still_running); if(still_running) /* wait for activity, timeout or nothing */ mresult curl_multi_poll(multi, NULL, 0, 1000, NULL); if(mresult) break; }注意这里numfds参数直接传了NULL——它是可选的输出参数不关心事件数量时允许省略。同类的多句柄示例还可见 docs/examples/multi-double.c、docs/examples/http2-download.c、docs/examples/http2-upload.c 等。七、底层实现一次 curl_multi_poll 调用内部发生了什么综合 lib/multi.c 中multi_wait()的实现一次curl_multi_poll()调用的完整流程如下收集内部描述符遍历 multi handle 的 process 集合对每个 easy handle 调用Curl_multi_pollset()收集其当前使用的套接字lib/multi.c并追加关闭队列cshutdn相关的描述符注册唤醒套接字ENABLE_WAKEUP时若需要遵守timeout_ms把wakeup_pair[0]加入轮询集lib/multi.c追加外部描述符将extra_fds[]逐个翻译成原生 poll 事件后加入轮询集lib/multi.c确定超时调用multi_timeout()取得内部定时器与timeout_ms取较小值lib/multi.c执行等待POSIX 平台走multi_posix_poll()内部调用Curl_poll()包装的poll(2)无 fd 且extrawait时退化为curlx_wait_ms()见 lib/multi.cWindows 平台走multi_winsock_select()翻译事件回填把原生revents位映射回CURL_WAIT_POLL*写入每个extra_fds[i].revents统计发生事件的描述符数量处理唤醒若唤醒套接字被触发消耗信号并从事件计数中扣除不把唤醒套接字计入返回的numfds。numfds的语义也因此明确它是发生感兴趣事件的描述符计数可能同时包含 libcurl 内部描述符和调用方传入的额外描述符并非已就绪且必须处理的数量——循环体依然要以curl_multi_perform()为驱动核心numfds更多用于判断是否需要立刻 perform例如配合curl_multi_timeout()精细控制。仓库测试 tests/libtest/lib1564.c 对该 API 的时序做了专门验证其断言包括 curl_multi_poll returned too early返回过早与 curl_multi_poll returned too late返回过晚两类检查从测试层面保证了在无事件、有内部定时器、有额外 fd 等不同场景下等待时长均符合预期。相关接口的配套说明还可参考 docs/libcurl/curl_multi_waitfds.mdcurl_multi_waitfds()用于获取可等待的描述符数组与 docs/libcurl/curl_multi_perform.mdcurl_multi_perform()的完整语义。八、返回值与错误处理curl_multi_poll()返回CURLMcodeCURLM_OK (0)一切正常注意即便超时或仅有唤醒事件通常也返回 OK事件计数体现在numfds中非零值发生错误具体错误码见 docs/libcurl/libcurl-errors.md。实现中可能产生的错误码包括CURLM_BAD_FUNCTION_ARGUMENTtimeout_ms为负值见 lib/multi.c、CURLM_OUT_OF_MEMORY轮询集扩容失败、CURLM_UNRECOVERABLE_POLL底层poll()调用失败见 lib/multi.c以及CURLM_BAD_HANDLEmulti 句柄非法。一个实用的编码约定与官方示例一致把curl_multi_perform()与curl_multi_poll()的返回码放在同一处检查任何非CURLM_OK都立即跳出循环并做清理避免在错误状态下继续轮询。九、小结何时使用 curl_multi_poll场景推荐通用 multi 事件循环希望避免忙轮询与 select 的 1024 限制curl_multi_poll()需要从其他线程信号处理、控制线程唤醒等待curl_multi_poll()curl_multi_wakeup()需要同时等待应用自有的 socket / 管道 / 定时器 fdcurl_multi_poll()extra_fds[]兼容老代码、不关心空描述符集的立即返回行为curl_multi_wait()一句话总结curl_multi_poll()是当前 libcurl 推荐的 multi 接口等待函数它用poll(2)的语义替代了select(2)用无描述符也按超时等待替代了curl_multi_wait()的立即返回并用curl_multi_wakeup()打通了跨线程唤醒的能力。配合 docs/libcurl/libcurl-multi.md 了解整个 multi 接口的全局模型再对照 docs/examples/multi-app.c 与 tests/libtest/lib1564.c 理解实际用法与行为边界即可写出健壮的并发传输代码。【免费下载链接】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),仅供参考
返回列表