ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

libcurl 连接维护机制实战:CURLOPT_UPKEEP_INTERVAL_MS 详解

libcurl 连接维护机制实战:CURLOPT_UPKEEP_INTERVAL_MS 详解 libcurl 连接维护机制实战CURLOPT_UPKEEP_INTERVAL_MS 详解【免费下载链接】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本文围绕 libcurl 的CURLOPT_UPKEEP_INTERVAL_MS选项展开讲解连接维护connection upkeep机制的由来、配置方法与底层实现。阅读本文后你将掌握如何在 HTTP/2、MQTT 等长连接场景下通过设置维护间隔并显式调用curl_easy_upkeep()持续向服务端发送存活探测如 HTTP/2 PING 帧有效对抗防火墙空闲超时导致的连接意外断开并理解该机制在 curl 源码中的完整调用链。背景为什么需要连接维护在 HTTP/1.1 时代连接的生命周期通常较短而 HTTP/2 支持多路复用一个 TCP 连接上可以并行承载大量请求连接被长时间保持是常态。但许多防火墙、NAT 网关或运营商中间设备会对长时间无流量的连接实施空闲超时回收——连接看似还在实际已被中间设备静默丢弃导致下一次请求失败或延迟。libcurl 为此引入了连接维护connection upkeep机制协议层在已有连接上定期发送少量流量以证明连接仍然存活从而阻止中间设备将其判定为死连接。根据官方文档CURLOPT_UPKEEP_INTERVAL_MS 手册该机制同样用于对抗过于激进的防火墙overzealous firewalls。需要特别强调的是upkeep 是应用层协议级的存活探测与 TCP 层的SO_KEEPALIVE由 CURLOPT_TCP_KEEPALIVE 控制属于不同层面二者可以配合使用。选项速览CURLOPT_UPKEEP_INTERVAL_MS#include curl/curl.h CURLcode curl_easy_setopt(CURL *handle, CURLOPT_UPKEEP_INTERVAL_MS, long upkeep_interval_ms);该选项自7.62.0起加入适用于所有协议文档标记Protocol: All但只有实现了 upkeep 回调的协议才会真正产生流量详见下文。属性值选项名CURLOPT_UPKEEP_INTERVAL_MS参数类型long毫秒默认值CURL_UPKEEP_INTERVAL_DEFAULT即60000L60 秒引入版本7.62.0适用协议全部当前仅有 HTTP/2、MQTT 实际使用在公开头文件 include/curl/curl.h 中可以看到默认值的定义/* The default connection upkeep interval in milliseconds. */ #define CURL_UPKEEP_INTERVAL_DEFAULT 60000L同时在选项登记表中它以CURLOPTTYPE_LONG类型注册见 include/curl/curl.h 中的选项 ID 281并在 lib/easyoptions.c 的运行时选项表中登记为{ UPKEEP_INTERVAL_MS, CURLOPT_UPKEEP_INTERVAL_MS, CURLOT_LONG, 0 }。参数校验与取值范围选项落地到内部状态时会经过统一的毫秒级超时设置函数setopt_set_timeout_mslib/setopt.cstatic CURLcode setopt_set_timeout_ms(timediff_t *ptimeout_ms, long ms) { if(ms 0) return CURLE_BAD_FUNCTION_ARGUMENT; #if LONG_MAX TIMEDIFF_T_MAX if(ms TIMEDIFF_T_MAX) { *ptimeout_ms TIMEDIFF_T_MAX; return CURLE_OK; } #endif *ptimeout_ms (timediff_t)ms; return CURLE_OK; }从源码可以确认两个关键行为传入负数会直接返回CURLE_BAD_FUNCTION_ARGUMENT在LONG_MAX大于TIMEDIFF_T_MAX的平台上超出上限的值会被静默截断为TIMEDIFF_T_MAX而不是报错传入0表示禁用该句柄上的 upkeep 探测下文 MQTT 的源码中正是以 0作为启用条件。选项最终写入Curl_easy状态结构中的set.upkeep_interval_ms字段lib/setopt.c并在句柄初始化时被赋予默认值 60000lib/url.cset-upkeep_interval_ms CURL_UPKEEP_INTERVAL_DEFAULT;关键前提upkeep 必须显式触发与 TCP keepalive 由内核自动定时发送不同libcurl 的 connection upkeep不会自动执行。文档明确指出The user needs to explicitly call curl_easy_upkeep(3) in order to perform the upkeep work.也就是说CURLOPT_UPKEEP_INTERVAL_MS只是定义多长时间算一次超时的门槛真正的探测动作必须由应用在合适的时机主动调用 curl_easy_upkeep 来驱动。这正是事件驱动设计应用在自己的事件循环或空闲时段调用一次libcurl 遍历连接池仅对距上次维护已超过间隔的连接发送探测流量。curl_easy_upkeep的入口实现在 lib/easy.cCURLcode curl_easy_upkeep(CURL *curl) { struct Curl_eapi_guard guard; CURLcode result; if(CURL_EAPI_ENTER(guard, curl, easy_upkeep, result)) { /* Use the common function to keep connections alive. */ result Curl_cpool_upkeep((struct Curl_easy *)curl); } CURL_EAPI_LEAVE(guard); return result; }底层原理从连接池到协议过滤器1. 遍历连接池Connection Poolcurl_easy_upkeep最终调用Curl_cpool_upkeeplib/conncache.c它持锁遍历句柄关联的连接缓存cpool对每个连接执行conn_upkeep回调static int conn_upkeep(struct cpool *cpool, struct Curl_easy *admin, struct connectdata *conn, void *param) { const struct curltime *pnow Curl_pgrs_now(admin); (void)param; if((curlx_ptimediff_ms(pnow, conn-created) - conn-lastupkeep_ms) admin-set.upkeep_interval_ms) { CURLcode result; conn-lastupkeep_ms curlx_ptimediff_ms(pnow, conn-created); /* briefly attach for action */ Curl_attach_connection(admin, conn, FALSE); result Curl_conn_keep_alive(admin, conn); Curl_detach_connection(admin); if(result !CONN_INUSE(conn)) { cpool_conn_close(cpool, admin, conn, FALSE); return 1; } } return 0; /* continue iteration */ }这段代码揭示了核心判定逻辑lib/conncache.c用(当前时间 - 连接创建时间) - 上次维护时间与upkeep_interval_ms比较只有超过间隔的连接才触发维护每次维护后更新conn-lastupkeep_ms避免同一连接被重复探测维护期间临时把连接挂载到 admin 句柄上Curl_attach_connection完成后立刻卸载Curl_detach_connection若维护返回错误且连接无人使用该连接会被直接关闭cpool_conn_close避免继续持有失效连接。2. 沿过滤器链分发cfilter 机制Curl_conn_keep_alivelib/cfilters.c会沿连接的过滤器链cfilter逐个调用keep_alive回调直到某个过滤器返回错误为止CURLcode Curl_conn_keep_alive(struct Curl_easy *data, struct connectdata *conn) { CURLcode result CURLE_OK; int i; for(i 0; (i (int)CURL_ARRAYSIZE(conn-cfilter)) !result; i) { struct Curl_cfilter *cf conn-cfilter[i]; if(cf) result cf-cft-keep_alive(cf, data); ...3. HTTP/2发送 PING 帧当前唯一实现 upkeep 的是 HTTP/2 过滤器。其keep_alive回调lib/http2.c非常简洁static CURLcode cf_h2_keep_alive(struct Curl_cfilter *cf, struct Curl_easy *data) { CURLcode result; struct cf_call_data save; CF_DATA_SAVE(save, cf, data); result http2_send_ping(cf, data); CF_DATA_RESTORE(cf, save); return result; }真正的 PING 发送在http2_send_pinglib/http2.c它借助 nghttp2 库实现static CURLcode http2_send_ping(struct Curl_cfilter *cf, struct Curl_easy *data) { struct cf_h2_ctx *ctx cf-ctx; int rc; rc nghttp2_submit_ping(ctx-h2, 0, ZERO_NULL); if(rc) { failf(data, nghttp2_submit_ping() failed: %s(%d), nghttp2_strerror(rc), rc); return CURLE_HTTP2; } rc nghttp2_session_send(ctx-h2); if(rc) { failf(data, nghttp2_session_send() failed: %s(%d), nghttp2_strerror(rc), rc); return CURLE_SEND_ERROR; } return CURLE_OK; }从实现可以总结出先通过nghttp2_submit_ping把 PING 帧提交到 nghttp2 会话队列立即调用nghttp2_session_send冲刷到网络提交失败返回CURLE_HTTP2发送失败返回CURLE_SEND_ERRORPING 帧是 HTTP/2 标准的存活探测手段服务端必须回复 PONG因此既能让中间设备看到双向流量也能让 curl 侧感知对端是否仍然可达。4. MQTT发送 PINGREQ 报文根据 curl_easy_upkeep 文档MQTT 同样使用 upkeep 机制维护间隔决定何时发送 ping 请求以防止服务端主动断开空闲客户端。这在 lib/mqtt.c 的mqtt_ping中实现if(mqtt-state MQTT_FIRST !mq-pingsent >#include curl/curl.h int main(void) { CURL *curl curl_easy_init(); if(curl) { CURLcode result; /* Make a connection to an HTTP/2 server. */ curl_easy_setopt(curl, CURLOPT_URL, https://example.com); /* Set the interval to 30000ms / 30s */ curl_easy_setopt(curl, CURLOPT_UPKEEP_INTERVAL_MS, 30000L); result curl_easy_perform(curl); /* Perform more work here. */ /* While the connection is being held open, curl_easy_upkeep() can be called. If curl_easy_upkeep() is called and the time since the last upkeep exceeds the interval, then an HTTP/2 PING is sent. */ curl_easy_upkeep(curl); /* Perform more work here. */ /* always cleanup */ curl_easy_cleanup(curl); } }实践中的典型场景是应用维护一个长生命周期的 easy 句柄在两次请求之间反复调用curl_easy_upkeep(curl)。由于底层会自行比较距上次维护的耗时 vs 间隔频繁调用也不会产生多余流量——只有真正超时才会发 PING。基于 multi 接口的轮询建议在使用curl_multi的异步场景下可以在curl_multi_poll/curl_multi_perform的循环间隙周期性调用curl_easy_upkeep从而让维护动作与事件循环自然融合/* 伪代码示意在 multi 事件循环中定期触发 upkeep */ while(running) { curl_multi_poll(multi, NULL, 0, 100, NULL); curl_multi_perform(multi, running); /* 每个循环周期调用一次内部会按间隔过滤 */ curl_easy_upkeep(easy); }共享连接缓存下的行为8.10.0 起curl_easy_upkeep 文档还指出如果你在一个**使用共享连接缓存shared connection cache**的 easy 句柄上调用curl_easy_upkeep那么维护会作用于该缓存中的全部连接即使这些连接从未被该 easy 句柄使用过该行为自 8.10.0 起加入。这与 curl 的admin 句柄设计有关。在 lib/url.c 中可以看到当句柄隶属于某个 multi 实例时curl 会定位到 admin 句柄并把包括upkeep_interval_ms在内的一组生命周期相关选项同步过去if(admin ! data) { admin-set.conn_max_idle_ms contenteditable="false">【免费下载链接】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),仅供参考
返回列表