libcurl 定时器详解:CURLINFO_APPCONNECT_TIME 与 SSL/SSH 握手计时原理
【免费下载链接】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 项目中 libcurl 提供的一项核心诊断指标——CURLINFO_APPCONNECT_TIME,它精确度量"从传输开始到 SSL/SSH 连接握手完成"所消耗的秒数。无论你是排查 HTTPS 请求握手延迟、对比不同 TLS 后端的性能,还是想基于 curl 命令行工具 的-w输出解析握手耗时,掌握这个选项及其配套的CURLINFO_APPCONNECT_TIME_T(微秒级版本)都至关重要。读完本文,你将理解该时间戳的语义边界、它与CURLINFO_PRETRANSFER_TIME等指标的关系、底层源码中的记录机制,以及如何在自己的 C 程序与 shell 脚本中正确读取它。
1. 选项定位与基本语义
1.1 它是什么
CURLINFO_APPCONNECT_TIME是 libcurl 通过curl_easy_getinfo(3)返回的传输计时指标之一,语义为:
从请求开始到与远程主机的SSL/SSH 连接握手完成所经过的时间(单位:秒,
double类型)。
这里的"应用层连接"(Application Connect)特指 TLS/SSL 握手或 SSH 连接建立过程,与更底层的 TCP 连接计时(CURLINFO_CONNECT_TIME)相互区分。该选项适用于 curl 支持的所有协议(包括 HTTP/HTTPS、FTP/FTPS、IMAP/IMAPS 等),官方文档声明Protocol: All。
1.2 历史版本
CURLINFO_APPCONNECT_TIME自 libcurl7.19.0起提供(见 CURLINFO_APPCONNECT_TIME.md 头部的Added-in字段);- 微秒精度的
CURLINFO_APPCONNECT_TIME_T自7.61.0起提供(见 CURLINFO_APPCONNECT_TIME_T.md 头部)。
在公开头文件 include/curl/curl.h 中可以看到两者的枚举定义分别属于不同类型族:
CURLINFO_APPCONNECT_TIME = CURLINFO_DOUBLE + 33, /* 秒,double */ CURLINFO_APPCONNECT_TIME_T = CURLINFO_OFF_T + 56, /* 微秒,curl_off_t */2. 使用方法与代码示例
2.1 函数原型
#include <curl/curl.h> CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_APPCONNECT_TIME, double *timep);调用约束:
handle必须是已经执行过curl_easy_perform()(或 multi 接口传输)的 easy handle,因为计数值是在传输过程中逐步记录的;timep指向一个double变量,函数会将"秒数"写入该变量;- 返回值是
CURLcode:CURLE_OK(0)表示成功,非零值表示出错(详见 libcurl-errors(3) 一节)。
2.2 完整示例(来自官方文档)
int main(void) { CURL *curl = curl_easy_init(); if(curl) { CURLcode result; double connect; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); result = curl_easy_perform(curl); if(result == CURLE_OK) { result = curl_easy_getinfo(curl, CURLINFO_APPCONNECT_TIME, &connect); if(result == CURLE_OK) { printf("Time: %.1f", connect); } } /* always cleanup */ curl_easy_cleanup(curl); } }注意示例中的输出格式:%.1f只打印一位小数;由于返回值以秒为单位,若需要毫秒/微秒级展示,可自行乘 1000 / 1000000,或直接改用_T变体(见 2.4 节)。
2.3 使用_T微秒变体
当需要更高精度(微秒)时使用CURLINFO_APPCONNECT_TIME_T,其输出类型为curl_off_t:
int main(void) { CURL *curl = curl_easy_init(); if(curl) { CURLcode result; curl_off_t connect; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); result = curl_easy_perform(curl); if(result == CURLE_OK) { result = curl_easy_getinfo(curl, CURLINFO_APPCONNECT_TIME_T, &connect); if(result == CURLE_OK) { printf("Time: %" CURL_FORMAT_CURL_OFF_T ".%06ld", connect / 1000000, (long)(connect % 1000000)); } } /* always cleanup */ curl_easy_cleanup(curl); } }- 微秒值用
CURL_FORMAT_CURL_OFF_T格式化curl_off_t,秒与微秒部分分别取整除与取模; - 官方文档在 curl_easy_getinfo(3) 的 TIMES 一节确认,
CURLINFO_APPCONNECT_TIME_T提供的是微秒数。
2.4 秒级与微秒级如何选择
| 选项 | 类型 | 单位 | 引入版本 | 适用场景 |
|---|---|---|---|---|
CURLINFO_APPCONNECT_TIME | double | 秒 | 7.19.0 | 日志、概览统计 |
CURLINFO_APPCONNECT_TIME_T | curl_off_t | 微秒 | 7.61.0 | 高精度计时、性能剖析 |
两者读取的是同一个底层计数值(源码见第 4 节),只是单位与类型不同,选择依据是精度需求与你的程序类型体系。
3. 语义细节与易混淆点
3.1 与 CURLINFO_PRETRANSFER_TIME 的关系
文档明确指出:这个时间通常与CURLINFO_PRETRANSFER_TIME非常接近,但在 HTTP 多路复用(HTTP/2、HTTP/3 multiplexing)场景下,pre-transfer 时间可能因为流排队等待而显著延后。原因在于:
APPCONNECT在 TLS/SSH 握手完成的瞬间打点;PRETRANSFER在请求即将真正发出(可发送数据)时才打点,若连接需在流队列中等待,二者会出现明显差距。
因此,如果你想衡量"纯握手耗时",APPCONNECT是更干净、更接近真实握手的指标。
3.2 重定向时的累加语义
文档明确:当发生重定向时,每次请求的该时间会被累加。即最终读到的是多次请求握手耗时的总和,而非最后一次或最大的一次。这在分析多跳重定向链路的总耗时时要特别留意,可与CURLINFO_REDIRECT_COUNT、CURLINFO_REDIRECT_TIME结合解读。
3.3 计时起点
所有 libcurl 时间指标都以"传输开始"(curl_easy_perform内部的TIMER_STARTOP时刻)为基准,而不是程序启动时刻。CURLINFO_APPCONNECT_TIME度量的是从该起点到握手完成的间隔。整个时间线在 curl_easy_getinfo.md 的 TIMES 一节中有清晰的层级图:
curl_easy_perform() | |--QUEUE |--|--NAMELOOKUP |--|--|--CONNECT |--|--|--|--APPCONNECT |--|--|--|--|--PRETRANSFER |--|--|--|--|--|--POSTTRANSFER |--|--|--|--|--|--|--STARTTRANSFER |--|--|--|--|--|--|--|--TOTAL |--|--|--|--|--|--|--|--REDIRECT即典型的 HTTPS 请求时间线为:QUEUE → NAMELOOKUP → CONNECT(TCP)→APPCONNECT(TLS)→ PRETRANSFER → STARTTRANSFER → TOTAL。
4. 源码级实现剖析
4.1 打点位置:谁在写appconnect_us
计时值最终存储在 easy handle 的data->progress.total.appconnect_us字段中,该字段在 lib/urldata.h 中定义:
timediff_t appconnect_us; /* same for application connects, e.g. TLS */围绕这一字段,libcurl 在不同协议栈中通过Curl_pgrsTime(data, TIMER_APPCONTECT)/Curl_pgrsTimeWas(...)记录打点(timer 枚举定义在 lib/progress.h):
- TLS/SSL(vtls 层):在 lib/vtls/vtls.c 中,SSL 握手完成(
connssl->handshake_done)时调用Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done),且通过connssl->stats_reported保证每个连接只上报一次; - SSH(libssh / libssh2):认证完成后在 lib/vssh/libssh.c 与 lib/vssh/libssh2.c 直接调用
Curl_pgrsTime(data, TIMER_APPCONNECT),注释明确写着 "SSH is connected"; - QUIC/HTTP3(ngtcp2 / quiche):分别在 lib/vquic/cf-ngtcp2-cmn.c 与 lib/vquic/cf-quiche.c 用握手完成时刻
handshake_at打点; - Happy Eyeballs 连接竞速(cf-ip-happy.c):在 SSH 协议族且胜出连接已建立时于 lib/cf-ip-happy.c 打点。
这些打点最终都汇聚到Curl_pgrsTimeWas()(lib/progress.c),其中TIMER_APPCONNECT分支把delta指向data->progress.total.appconnect_us,实现累加:
case TIMER_APPCONNECT: delta = &data->progress.total.appconnect_us; break;4.2 读取路径:getinfo 如何返回给用户
curl_easy_getinfo内部按选项类型分派:
- 秒级
CURLINFO_APPCONNECT_TIME走getinfo_double()(lib/getinfo.c),通过DOUBLE_SECS(x)((double)(x) / 1000000,定义于 lib/getinfo.c)把微秒计数值换算为秒:case CURLINFO_APPCONNECT_TIME: *param_doublep = DOUBLE_SECS(data->progress.total.appconnect_us); break; - 微秒级
CURLINFO_APPCONNECT_TIME_T走getinfo_offt()(lib/getinfo.c),直接原样返回微秒数:case CURLINFO_APPCONNECT_TIME_T: *param_offt =>curl -o /dev/null -s -w 'appconnect: %{time_appconnect}s\ntotal: %{time_total}s\n' https://example.com/变量
time_appconnect对应的正是CURLINFO_APPCONNECT_TIME_T(见 src/tool_writeout.c 中{ "time_appconnect", VAR_APPCONNECT_TIME, CURLINFO_APPCONNECT_TIME_T, writeTime }的映射),在 docs/cmdline-opts/write-out.md 中有同样定义:它表示"从开始到与远程主机的 SSL/SSH 连接/握手完成所经过的秒数"。结合%{time_connect}(TCP 连接耗时)与%{time_namelookup}(DNS 解析耗时),即可快速拆解 HTTPS 请求各阶段耗时,定位瓶颈在 DNS、TCP 还是 TLS 握手。6. 常见问题与注意事项
- 返回值只在传输成功后可信:若
curl_easy_perform失败(如握手失败、连接被拒),应优先检查返回码;计时值可能不完整或为 0。 - 非 TLS/SSH 协议返回 0:文档声明
Protocol: All表示该选项在所有协议下都可查询,但只有经过 SSL/SSH 握手的传输才会有非零值(测试 tests/libtest/lib1541.c 已证实)。 - 单位与精度陷阱:秒级版本是
double,微秒版本是curl_off_t,两者来自同一计数,注意不要混淆类型;用_T版本做精确比较与统计更稳妥。 - 重定向累加:多跳重定向下,该值是各次请求之和,解读单次握手耗时前需确认是否存在重定向(结合
CURLINFO_REDIRECT_COUNT)。 - 版本前提:使用
CURLINFO_APPCONNECT_TIME需要 libcurl ≥ 7.19.0,使用_T变体需要 ≥ 7.61.0;编译时可用LIBCURL_VERSION_NUM做版本判断。
7. 相关参考
- 本选项文档:docs/libcurl/opts/CURLINFO_APPCONNECT_TIME.md
- 微秒变体文档:docs/libcurl/opts/CURLINFO_APPCONNECT_TIME_T.md
- 获取接口总览(含 TIMES 时间线):docs/libcurl/curl_easy_getinfo.md
- 计时器打点实现:lib/progress.c、lib/progress.h
- 返回值分派实现:lib/getinfo.c
- 命令行
-w变量:docs/cmdline-opts/write-out.md、src/tool_writeout.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),仅供参考