Libcurl libevent_结合libcurl和libevent实现异步网络请求

13次阅读

libcurl线程与libEvent驱动不可混用:前者为阻塞I/O+pthread,后者为单线程非阻塞事件循环;必须通过curl_multi_*接口配合socket事件通知,动态管理libevent的event实例,并严格同步socket生命周期与http/2/TLS事件。

Libcurl libevent_结合libcurl和libevent实现异步网络请求

libcurl 多线程 vs libevent 驱动:别混用 pthread + event loop

直接用 pthread 创建多个线程跑 curl_easy_perform(),和用 libevent 驱动 libcurl异步接口(curl_multi_*)是两套完全不同的模型。前者是阻塞 I/O + 线程并发,后者是单线程事件循环 + 非阻塞 I/O。强行在线程里嵌套 libevent 主循环或在 libevent 回调里调 curl_easy_perform(),大概率触发死锁或未定义行为。

  • libcurl 的异步能力只通过 curl_multi_* 系列函数暴露,必须配合其内部的 socket 事件通知机制
  • libevent 不能直接“接管” curl_easy_handle,只能监听 curl_multi_socket_action() 返回的 socket fd 变化
  • 每个 CURLM 句柄需绑定一个独立的 libevent Struct event_base *,不可跨 base 共享

curl_multi_socket_action 如何与 libevent 绑定

核心在于把 libcurl 多接口句柄的 socket 状态变化,转为 libevent 可监听的事件。关键步骤不是“注册回调”,而是动态管理 event 实例:

  • 调用 curl_multi_socket_action(multi_handle, CURL_SOCKET_TIMEOUT, 0, &still_running) 获取超时值,用 event_add() 设置定时器
  • curl_multi_socket_action() 返回新 socket fd 或状态变更,用 event_del() 清旧 event,再用 event_new() + event_add() 注册新监听(EV_READ/EV_WRITE
  • libevent 回调中必须再次调用 curl_multi_socket_action(),否则 curl 内部状态不同步
static void curl_perform_cb(int fd, short kind, void *userp) {   CURLM *multi = (CURLM *)userp;   int action = (kind & EV_READ) ? CURL_CSELECT_IN : CURL_CSELECT_OUT;   int running_handles;   curl_multi_socket_action(multi, fd, action, &running_handles); }

常见崩溃点:socket fd 生命周期错位

libcurl 可能在任意时刻通过 CURLMOPT_SOCKETFUNCTION 回调告诉你:“这个 fd 我不用了,请删掉对应 event”。但如果你在 libevent 回调里刚调完 curl_multi_socket_action() 就立刻 event_free(),而 libcurl 内部还在引用该 fd,就会 crash。

  • 必须实现 socket 关闭钩子:curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, sock_cb)
  • sock_cb 中收到 action == CURL_POLL_REMOVE 时,仅标记待清理,**不立即 free event**;等下一次 curl_multi_socket_action() 调用前统一清理
  • libevent 的 event 必须用 event_set() 初始化而非 event_new(),避免重复分配内存导致悬挂指针

HTTP/2 和 TLS 握手对事件循环的影响

启用 CURLOPT_HTTP_VERSION = CURL_HTTP_VERSION_2TLS 后,libcurl 可能触发额外的非 socket 事件(如 ALPN 协商、HPACK 解码),这些不会反映在 socket fd 上,但会卡住 curl_multi_perform() 进度。

  • 必须设置 curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, timer_cb),并在 timer_cb 中用 event_add() 更新超时,否则 HTTP/2 流控会失效
  • OpensslSSL_read()/SSL_write() 在非阻塞模式下返回 SSL_WANT_READ/SSL_WANT_WRITE 时,libcurl 会自动重试,但要求你确保对应 socket 仍在 libevent 监听中 —— 别在 SSL_WANT_WRITE 时误删 EV_READ 事件
  • 调试时加 curl_multi_setopt(multi, CURLMOPT_VERBOSITY, CURLPIPE_MULTIPLEX) 查看是否真走 HTTP/2 多路复用

libcurl + libevent 的异步链路里,最易被忽略的是「socket fd 的所有权移交时机」:libcurl 告诉你“删”,不等于你能立刻删;libevent 触发可读,不等于 curl 已准备好收数据 —— 中间所有状态同步都靠反复调 curl_multi_socket_action() 维系,少一次就可能卡死。

text=ZqhQzanResources