深入解析 curl 的 `--keepalive-cnt`:控制 TCP 保活探测次数与断连判定
2026/9/10 3:42:35 网站建设 项目流程

深入解析 curl 的--keepalive-cnt:控制 TCP 保活探测次数与断连判定

【免费下载链接】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

导读

--keepalive-cnt是 curl 8.9.0 引入的连接类命令行选项,用于设置 TCP 在判定连接已失效前可以发出的无响应 keepalive 探测(probe)次数上限。它通常与--keepalive-time配合使用,把"连接空闲多久开始探测"与"探测多少次后宣告断开"完整地交给你掌控。读完本文,你将理解 TCP keepalive 的判定模型、该选项在 Linux / *BSD / macOS / Windows / Solaris 等平台上的生效方式,以及从 curl 命令行参数一路穿透到setsockopt(2)的完整实现链路。

本文档核心依据为 docs/cmdline-opts/keepalive-cnt.md,并以其同族选项 docs/cmdline-opts/keepalive-time.md、docs/cmdline-opts/no-keepalive.md 及 lib/ 下的源码实现为佐证。

一、为什么需要手动控制 TCP 保活探测次数

TCP 协议本身面向连接,但网络链路可能在中途静默失效(拔线、断电、NAT 超时清理等),此时连接双方并不能立刻感知。TCP keepalive 机制由协议栈负责:当连接在一段时间内没有任何数据交互时,内核会周期性地发送探测报文;若对端持续无应答,协议栈就会判定连接已断开。

内核通常允许通过 socket 选项控制这一过程,curl 把其中最关键的三个参数暴露给用户:

  • 空闲多久后开始探测TCP_KEEPIDLE/ macOS 上的TCP_KEEPALIVE);
  • 相邻两次探测的时间间隔TCP_KEEPINTVL);
  • 连续多少次无应答后宣告连接断开TCP_KEEPCNT)。

其中第三项正是--keepalive-cnt的职责。它决定的不是一个"长连接该不该保活"的是非题,而是最终判定连接死亡所需的容错容忍度:次数越小,链路一旦真正失效就越快被察觉(假死时间短);次数越大,对网络抖动越宽容,但无效连接被回收得更慢。常见的内核默认值差异巨大(如 Linux 默认 9 次、Windows 通常为 5 或 10 次、*BSD/macOS 为 8 次),跨平台脚本如果依赖 keepalive 感知断连速度,就必须显式指定该值以抹平平台差异。

二、--keepalive-cnt参数速览

来自 docs/cmdline-opts/keepalive-cnt.md 的参数元信息可归纳如下表:

项目取值
长选项--keepalive-cnt <integer>
参数类型无符号整数
Help 文本Maximum number of keepalive probes(保活探测次数上限)
引入版本curl 8.9.0(Added: 8.9.0)
分类connection(连接类)
可重复性single(只允许出现一次,后出现者覆盖前者)
关联选项--keepalive-time--no-keepalive
默认值9
文档示例--keepalive-cnt 3 $URL

该选项的核心语义为:设置 TCP 在丢弃连接之前,允许发送却得不到任何响应的保活探测报文的最大数量。它通常与--keepalive-time一起使用——后者负责设定"空闲多久后开始探测"以及"相邻两次探测之间的时间间隔"。

其兼容性边界在文档中写得很明确:

  • 支持平台:Linux、*BSD/macOS、Windows ≥ 10.0.16299、Solaris 11.4 以及较新的 AIX、HP-UX 等;
  • 若使用了--no-keepalive,本选项不产生任何效果
  • 若未显式指定,默认值为 9。

三、典型用法:让长连接"假死"可被及时感知

3.1 基础用法

# 最多发送 3 次无应答的 keepalive 探测即放弃该连接 curl --keepalive-cnt 3 $URL

只设置探测次数而不指定保活时间是可行的,但缺少了"空闲多久、间隔多久"的维度。实践上建议与--keepalive-time配套:

# 空闲 30s 后开始每 30s 探测一次,连续 3 次无应答(约 90s)即判定连接失效 curl --keepalive-time 30 --keepalive-cnt 3 $URL

从 curl 工具的实现(src/config2setopts.c 第 729-740 行)可以看到,--keepalive-time的值会被同时映射到空闲时间与探测间隔两个 socket 选项上:

if(!config->nokeepalive) { my_setopt_long(curl, CURLOPT_TCP_KEEPALIVE, 1); if(config->alivetime) { my_setopt_long(curl, CURLOPT_TCP_KEEPIDLE, config->alivetime); my_setopt_long(curl, CURLOPT_TCP_KEEPINTVL, config->alivetime); } if(config->alivecnt) my_setopt_long(curl, CURLOPT_TCP_KEEPCNT, config->alivecnt); } else my_setopt_long(curl, CURLOPT_TCP_KEEPALIVE, 0);

这意味着在 curl 工具中,探测总耗时可粗略估算为:

假死判定总耗时 ≈ keepalive-time(空闲期)+ keepalive-cnt × keepalive-time(间隔期)

3.2 与--no-keepalive的优先级关系

curl 工具默认会开启 TCP keepalive(只有当--no-keepalive被指定时才显式关闭,见上方源码的else分支)。--keepalive-cnt--keepalive-time只在 keepalive 开启的前提下有意义,一旦命令中出现--no-keepaliveSO_KEEPALIVE都不会被置位,后续的探测次数设置自然全部失效——这正是文档强调"此选项在使用--no-keepalive时无效"的原因。

# 下面这行中 keepalive-cnt 不会起作用 curl --no-keepalive --keepalive-cnt 3 $URL

3.3 在配置文件中固化

所有命令行选项同样可以写入 curl 的配置文件(.curlrc/_curlrc),便于大批量脚本复用:

# ~/.curlrc keepalive-time = 20 keepalive-cnt = 5

各选项的详细互见关系可参考 keepalive-time.md 与 no-keepalive.md。

四、源码级调用链:从--keepalive-cnt 3setsockopt(TCP_KEEPCNT)

要真正理解该选项,值得沿着参数解析、会话结构体、easy 接口、连接过滤器的路径走一遍,完整链路位于 src/tool_getparam.c → src/tool_cfgable.h → src/config2setopts.c → lib/setopt.c → lib/urldata.h → lib/cf-socket.c。

第 1 步:命令行解析。选项表在 src/tool_getparam.c 第 191-193 行注册,类型为ARG_UNUM(无符号数值);命中分支(第 2404-2406 行)把数值存入config->alivecnt

case C_KEEPALIVE_CNT: /* --keepalive-cnt */ config->alivecnt = val; break;

对应字段定义在 src/tool_cfgable.h 第 193 行:long alivecnt; /* keepalive-cnt */

第 2 步:转译为 libcurl 选项。如前节源码所示,src/config2setopts.c 在!config->nokeepalive的前提下,把alivecnt映射为CURLOPT_TCP_KEEPCNT

第 3 步:setopt 校验入库。lib/setopt.c 第 884-888 行处理该选项:

case CURLOPT_TCP_KEEPCNT: result = value_range(&arg, 0, 0, INT_MAX); if(!result) s->tcp_keepcnt = (int)arg; break;

可以看到取值范围被限制在 0 到INT_MAX,最终存入会话配置结构体tcp_keepcnt字段(见 lib/urldata.h 第 970-972 行,同处还有tcp_keepidletcp_keepintvl)。

第 4 步:默认值初始化。在 lib/url.c 第 405-408 行的会话初始化中,libcurl 设定了与文档一致的默认值——tcp_keepidle = 60tcp_keepcnt = 9tcp_keepalive默认为FALSE,即 libcurl 库本身不默认开启 keepalive,需要应用自行开启;而 curl 命令行工具则会主动开启,详见上文 config2setopts 逻辑)。

第 5 步:建立 socket 时下发到内核。真正的落地发生在连接过滤器 lib/cf-socket.c 的tcpkeepalive()函数(第 186-338 行),该函数在 socket 建立后(第 1273-1274 行)被调用。其流程是:先设置SO_KEEPALIVE,成功后再按平台条件设置各细分选项。非 Windows 路径的核心代码片段如下:

#ifdef TCP_KEEPIDLE optval = curlx_sltosi(data->set.tcp_keepidle); KEEPALIVE_FACTOR(optval); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, ...) < 0) { ... } #elif defined(TCP_KEEPALIVE) /* macOS style */ ... #endif #ifdef TCP_KEEPINTVL optval = curlx_sltosi(data->set.tcp_keepintvl); ... #endif #ifdef TCP_KEEPCNT optval = curlx_sltosi(data->set.tcp_keepcnt); if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, ...) < 0) { ... } #endif

凡是底层平台头文件未定义TCP_KEEPCNT的情况,该段代码会被整体裁剪掉——这就是文档中"仅特定平台支持"这一限制的根源:探测次数选项本质上是否生效,取决于内核是否提供对应的 socket 选项

五、平台差异:同样一个值,底层机制并不相同

lib/cf-socket.c 中tcpkeepalive()的实现,恰好印证了 keepalive-cnt.md 与 keepalive-time.md 中列出的平台支持范围。各平台路径可归纳如下:

平台空闲时间选项间隔选项次数选项/等效机制
LinuxTCP_KEEPIDLETCP_KEEPINTVLTCP_KEEPCNT
macOS / *BSDTCP_KEEPALIVE(macOS 风格)或TCP_KEEPIDLETCP_KEEPINTVLTCP_KEEPCNT(若定义)
Windows ≥ 10.0.16299TCP_KEEPIDLE(即TCP_KEEPALIVE=3)TCP_KEEPINTVL(=17)TCP_KEEPCNT(=16),经setsockopt下发
更早的 Windows使用WSAIoctl+SIO_KEEPALIVE_VALS,仅有keepalivetime/keepaliveinterval两字段,没有次数概念同左无(故文档限定 ≥ 10.0.16299)
Solaris < 11.4TCP_KEEPALIVE_THRESHOLDTCP_KEEPALIVE_ABORT_THRESHOLD承载"总超时"cnt × intvl折算为TCP_KEEPALIVE_ABORT_THRESHOLD

几点值得展开的细节:

  • Solaris 的特殊折算:在早于 11.4 的 Solaris 上,没有独立的TCP_KEEPCNT,代码将keepcntkeepintvl相乘后写入TCP_KEEPALIVE_ABORT_THRESHOLD(lib/cf-socket.c 第 300-327 行),并在注释中说明该平台探测并非等间隔,而是采用指数退避算法。相乘时还做了INT_MAX溢出保护。
  • 平台默认探测次数本就不同:lib/cf-socket.c 第 302-311 行的注释与 keepalive-time.md 一致地指出——Linux 默认 9 次、*BSD/macOS 与部分 AIX 为 8 次、Windows 为 5 或 10 次。跨平台统一行为正是--keepalive-cnt的核心价值。
  • Windows 的双轨实现:lib/cf-socket.c 第 199-263 行先用curlx_verify_windows_version(10, 0, 16299, ...)判断系统版本,达到 Windows 10 1709(10.0.16299)及以上才走TCP_KEEP*setsockopt路径;旧系统退化为SIO_KEEPALIVE_VALSWSAIoctl,此时--keepalive-cnt无从生效。

从这些分支可以推断:判断一个平台是否真正支持--keepalive-cnt,等价于判断该平台是否提供独立的"无应答探测次数" socket 选项;仅支持"总超时阈值"的旧系统只能通过乘法近似表达该语义。

六、libcurl 编程接口中的对应物:CURLOPT_TCP_KEEPCNT

命令行选项的底层就是 libcurl easy 接口。若在你的程序中直接使用 libcurl,对应的完整参数是:

命令行libcurl 选项对应内核 socket 选项
--keepalive-timeCURLOPT_TCP_KEEPIDLETCP_KEEPIDLE/ macOSTCP_KEEPALIVE
--keepalive-timeCURLOPT_TCP_KEEPINTVLTCP_KEEPINTVL
--keepalive-cntCURLOPT_TCP_KEEPCNTTCP_KEEPCNT
--keepalive/--no-keepaliveCURLOPT_TCP_KEEPALIVESO_KEEPALIVE

程序化示例:

#include <curl/curl.h> int main(void) { CURL *curl = curl_easy_init(); if(!curl) return 1; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); /* 默认关闭,需要显式开启 */ curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); /* 空闲 30s 后开始探测 */ curl_easy_setopt(curl, CURLOPT_TCP_KEEPIDLE, 30L); /* 每 30s 探测一次 */ curl_easy_setopt(curl, CURLOPT_TCP_KEEPINTVL, 30L); /* 3 次无应答即判定连接断开 */ curl_easy_setopt(curl, CURLOPT_TCP_KEEPCNT, 3L); curl_easy_perform(curl); curl_easy_cleanup(curl); return 0; }

值得注意 libcurl 与 curl 工具的一个差异:libcurl 库默认不开启 keepalive(lib/url.c 第 405 行初始化tcp_keepalive = FALSE),应用必须自行设置CURLOPT_TCP_KEEPALIVE;而 curl 工具为了让普通用户受益,默认即开启(src/config2setopts.c 第 730-731 行)。因此在你的程序里,如果只设置CURLOPT_TCP_KEEPCNT而忘了开启CURLOPT_TCP_KEEPALIVE,该值同样不会生效——这也与文档"配合使用、受 keepalive 开关约束"的语义一致。

七、验证与调优建议

如何验证生效。在 Linux 上可通过系统调用跟踪确认选项确实下发到内核,例如:

strace -e trace=setsockopt curl --keepalive-cnt 3 --keepalive-time 20 https://example.com/ 2>&1 | grep -i keep

预期能看到对SO_KEEPALIVETCP_KEEPIDLETCP_KEEPINTVLTCP_KEEPCNTsetsockopt调用。此外,lib/cf-socket.c 中tcpkeepalive()在选项下发失败时会通过连接过滤器日志输出Failed to set ...之类的诊断信息,开启 curl 的详细跟踪(--trace/--verbose)可辅助排查。

调优建议。具体取值没有"放之四海而皆准"的标准,但可参考以下经验坐标(结合本仓库文档与代码中呈现的内核默认值):

  • 默认的 9 次配合 keepalive-time 60 秒,意味着链路失效后最长约 9~10 分钟才会被发现,适合容忍长假死、追求最小探测开销的场景;
  • 若你的服务运行在云负载均衡或 NAT 之后,NAT 表项回收往往快于内核默认探测周期,可调小--keepalive-time(如 20-30s)并配合适中的--keepalive-cnt(如 3-5 次),把感知时长压缩到分钟级以内;
  • 在 *BSD/macOS 与 Linux 混合部署的场景下,建议显式指定--keepalive-cnt--keepalive-time,避免因系统默认探测次数(8 vs 9)不同而得到不一致的断连判定行为;
  • 脚本与超时上限结合时,注意估算公式:总假死容忍时长 ≈ keepalive-time + keepalive-cnt × keepalive-time,并确保它小于你业务层的应用超时。

结语

--keepalive-cnt虽然只是一个"整数参数",但它是 curl 把 TCP 协议栈能力暴露给应用层的典型接口:从 src/tool_getparam.c 的参数解析,到 lib/setopt.c 的值域校验,再到 lib/cf-socket.c 中按 Linux、macOS/*BSD、Windows(含新旧两套机制)、Solaris 分别落地的setsockopt调用,整条链路清晰展示了 curl 如何在不同内核之间抹平 keepalive 语义差异。理解它,等于掌握了在"过早断开长连接"与"长期占用死连接"之间精确调节的旋钮。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询