curl 内部时间管理机制解析:从 curlx_now() 到 Curl_pgrs_now() 的演进与实现

发布时间:2026/9/11 23:39:14

curl 内部时间管理机制解析:从 curlx_now() 到 Curl_pgrs_now() 的演进与实现 curl 内部时间管理机制解析从 curlx_now() 到 Curl_pgrs_now() 的演进与实现【免费下载链接】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 仓库内部设计文档 docs/internals/TIME-KEEPING.md系统剖析 libcurl 如何在传输过程中获取与维护当前时间超时判定、低速检测、速率限制、事件时间线timeline记录都依赖一套统一的时间来源。全文围绕curlx_now()与Curl_pgrs_now()两个核心接口还原从循环内取一次时间并向下传递指针的旧方案到时间戳就地存放、按需获取新方案的演进原因与源码级实现。读完本文你将掌握 curl 内部时间戳的存放位置、获取路径、跨平台单调时钟实现以及多句柄multi handle状态机中时间是如何被消费的。一、传输为什么需要当前时间数据传输过程天然与时间强耦合libcurl 在以下场景中必须读取当前时间超时控制CURLOPT_TIMEOUT总超时、CURLOPT_CONNECTTIMEOUT连接超时、CURLOPT_LOW_SPEED_TIME等选项需要把剩余时间与流逝的时间做减法进度与速率统计计算下载/上传速度bytes/sec决定进度回调触发频率低速检测low-speed limit连续一段时间低于速度阈值即判定CURLE_OPERATION_TIMEDOUT速率限制rate limitingCURLOPT_MAX_RECV_SPEED_LARGE/CURLOPT_MAX_SEND_SPEED_LARGE需要按时间窗发放流量令牌事件时间线记录为curl_easy_getinfo()提供CURLINFO_NAMELOOKUP_TIME、CURLINFO_CONNECT_TIME、CURLINFO_APPCONNECT_TIME、CURLINFO_STARTTRANSFER_TIME等统计数据。正如文档开篇所言Transfers need the current time to handle timeouts and keep a record of events.传输需要当前时间来处理超时并记录事件。二、统一时间入口curlx_now() 与单调时钟当前时间获取函数是curlx_now()在大多数平台上它基于单调时钟monotonic clock。这意味着返回的时间戳只增不减时间永远向前推进但它并不是真实世界的墙上时钟——CURLINFO_*_TIME等统计数据是相对差值因此不需要与墙上时钟对齐。struct curltime的结构定义在 lib/curlx/timeval.hstruct curltime { time_t tv_sec; /* seconds */ int tv_usec; /* microseconds */ };curlx_now()的实现在 lib/curlx/timeval.c它只是对内部函数curlx_pnow()的封装struct curltime curlx_now(void) { struct curltime now; curlx_pnow(now); return now; }2.1 跨平台实现矩阵curlx_pnow()通过条件编译在不同平台选择最优时间源其完整优先级如下对应 lib/curlx/timeval.c平台 / 编译宏时间源特性_WIN32QueryPerformanceCounter()高精度性能计数器使用前必须先调用curlx_now_init()取得QueryPerformanceFrequency()频率timeval.cHAVE_CLOCK_GETTIME_MONOTONIC_RAWclock_gettime(CLOCK_MONOTONIC_RAW, ...)最纯净的单调时钟不受 NTP 调整影响HAVE_CLOCK_GETTIME_MONOTONICclock_gettime(CLOCK_MONOTONIC, ...)标准单调时钟HAVE_MACH_ABSOLUTE_TIMEmacOSmach_absolute_time()需通过mach_timebase_info()换算纳秒timeval.cHAVE_GETTIMEOFDAYgettimeofday()兜底方案不保证单调可能因时钟漂移或时间同步发生前后跳变源码注释明确说明了这一风险其他time(NULL)最简陋兜底仅秒级精度值得注意的两处细节CLOCK_MONOTONIC_RAW优先于CLOCK_MONOTONIC当两者都可用时curl 优先尝试 RAW 版本失败后才回退到普通单调时钟timeval.c运行时回退即使配置阶段检测到单调时钟可用运行时仍可能调用失败例如 Apple SDK 将clock_gettime声明为弱符号老版本系统上调用会崩溃因此代码中用__builtin_available()做运行时检查失败后回退到gettimeofday()timeval.c。2.2 配套时间差工具除了取时间本身lib/curlx/timeval.h 还提供了一组时间差计算工具全部要求第一个参数newer比第二个参数older更新否则会得到负的时间差curlx_timediff_ms()/curlx_ptimediff_ms()毫秒级差值过大差值会被钳制到TIMEDIFF_T_MAX/TIMEDIFF_T_MINcurlx_timediff_ceil_ms()毫秒级差值并向上取整curlx_timediff_us()/curlx_ptimediff_us()微秒级差值curlx_us_to_ceil_ms()微秒向上取整转毫秒。curlx_ptimediff_ms的实现还带有unittest: 1323注释标记timeval.c说明该函数由单元测试覆盖读者可在tests/unit中检索对应测试。三、历史方案Initial Approach循环入口取一次时间并向下传指针文档中详细记录了旧方案的来龙去脉这对理解新方案至关重要。3.1 旧方案的做法多句柄的事件处理循环loop processing在循环开头调用一次curlx_now()然后把struct curltime now的指针一路传递给所有可能涉及时间的函数试图让它们共用同一个循环内时间快照从而节省重复调用系统时钟的开销。3.2 为什么被废弃旧方案存在两个致命问题API 污染把now指针作为参数层层下传会pollute the internal APIs——内部函数的签名被时间参数绑架任何深层的函数都不得不接受一个与自身职责无关的指针时间倒流jumps in time有些函数继续自行调用curlx_now()获取实时时间而另一些函数使用的是循环开头传入的过去的时间戳指针。两者混用导致同一次传输经历的时间线出现跳跃因果倒置——在快速机器上通常不明显但在慢速机器或 CI 环境下会引发偶发且令人恼火的测试失败。尤其严重的是当 curl 增加了传输时间线必须按序排列的断言后问题被放大。所谓时间线即事件发生的先后顺序queue - namelookup - connect - appconnect - ...排队 → 域名解析 → TCP 连接 → TLS/应用层连接 → ……如果某处用了过期的now指针而另一处用了新鲜的curlx_now()这条时间线的单调性就可能被破坏从而触发断言失败。这正是旧方案被推翻的直接原因。四、修订方案Revised Approach时间戳就地存放、按需获取新策略在文档中被总结为三条铁律并已在当前源码中完整落地Keep a now timestamp in the multi handle. Keep a fallback now timestamp in the easy handle.Always useCurl_pgrs_now(data)to get the current time of a transfer.Do not usecurlx_now()directly for transfer handling (exceptions apply for loops).4.1 时间戳存放在哪里从当前源码看now 时间戳实际存放于每个 easy 句柄的进度结构体中。lib/urldata.h 中struct Progress的第一个字段即是struct Progress { struct curltime now; /* current time of processing */ struct curltime start; /* when transfer was initialized, set once */ ... };文档描述的设计意图是在 multi 层维护统一时间戳、在 easy 层保存 fallback落地实现中now与start都挂在Curl_easy的progress成员上multi 状态机通过data句柄统一访问多传输之间天然共享同一套时间管理代码路径。4.2 统一获取入口Curl_pgrs_now(data)所有传输处理代码必须通过Curl_pgrs_now(data)获取当前时间。它的实现非常简洁lib/progress.cconst struct curltime *Curl_pgrs_now(struct Curl_easy *data) { curlx_pnow(data-progress.now); return data-progress.now; }关键点它调用curlx_pnow()就地更新data-progress.now然后返回指向该字段的const指针。这就实现了文档所说的三个优点无需向下传struct curltime或过时时间戳的指针——每个函数需要时直接Curl_pgrs_now(data)即可无需提前计算精确的 now——延迟到真正使用的那一刻才刷新避免取早了导致的过期时间const指针优于结构体传值而所有传输共享同一内存位置更新则更优——同一份内存被反复就地刷新调用方永远读到最新值且避免了结构体的值拷贝开销。该接口的声明位于 lib/progress.h。4.3 唯一例外循环内部文档明确指出例外exceptions apply for loops循环本身仍可直接使用curlx_now()。典型例子在 easy 句柄的事件驱动循环中lib/easy.c 用curlx_now()记录 poll 开始时间用于计算本轮 poll 实际等待了多久从而在下一次超时值中扣减timediff_t spent_ms curlx_timediff_ms(curlx_now(), start); if(spent_ms 0) { if(spent_ms ev-ms) ev-ms 0; else ev-ms - (long)spent_ms; }这类测量循环自身耗时的场景与单个传输的时间线无关属于合理使用curlx_now()的例外。4.4 注意事项Caveat文档给出的唯一警告是不要把Curl_pgrs_now(data)返回的指针保存到超出当前代码调用范围的地方。因为该指针指向data-progress.now这块会被反复就地刷新的内存任何持久的引用都会读到不断变化的最新值而非快照如果需要保留历史时刻必须把struct curltime按值拷贝走例如data-progress.start、state.keeps_speed等都是按值保存的时间点。五、源码级消费路径时间在 multi 状态机中的流向Curl_pgrs_now()在 libcurl 各模块中被广泛使用仅 lib/progress.c、lib/multi.c、lib/transfer.c 就有数十处调用Curl_pgrs_now还出现在lib/http.c、lib/ftp.c、lib/smtp.c、lib/connect.c、lib/vtls/openssl.c等 30 余个文件中。以下梳理几条典型的消费链路。5.1 事件时间线Curl_pgrsTime 与 timerid 枚举时间线timeline的里程碑由timerid枚举定义lib/progress.htypedef enum { TIMER_NONE, TIMER_STARTOP, /* 传输开始 */ TIMER_STARTSINGLE, /* 单个请求开始可能进入排队 */ TIMER_POSTQUEUE, /* 出队后立即 */ TIMER_NAMELOOKUP, /* 域名解析 */ TIMER_CONNECT, /* 连接 */ TIMER_APPCONNECT, /* 应用层连接TLS 等 */ TIMER_PRETRANSFER, TIMER_STARTTRANSFER, TIMER_POSTRANSFER, TIMER_REDIRECT, TIMER_LAST /* must be last */ } timerid;这与文档中提及的queue - namelookup - connect - appconnect时间线一一对应。时间戳记录分为两个接口lib/progress.cCurl_pgrsTimeWas(data, timer, timestamp)把给定的时间戳按标签记录并计算与progress.start的差值累加到对应统计项Curl_pgrsTime(data, timer)内部就是Curl_pgrsTimeWas(data, timer, *Curl_pgrs_now(data))——先取最新时间再记录该函数带有unittest: 1399注释标记。其中TIMER_STARTTRANSFER还有一个防重复逻辑startransfer_added标志保证除非发生重定向否则t_starttransfer只记录第一次progress.c避免多次触发覆盖掉真实的首个字节到达时间。5.2 状态机初始化与超时注入在 multi 状态机进入MSTATE_SETUP时lib/multi.cmultistate_setup()用最新时间完成两件事const struct curltime *pnow Curl_pgrs_now(data); Curl_pgrsTimeWas(data, TIMER_STARTSINGLE, *pnow); if(data-set.timeout) Curl_expire_set(data, EXPIRE_TIMEOUT,>pnow Curl_pgrs_now(data); if(CURL_REQ_WANT_IO(data)) { if(Curl_timeleft_now_ms(data, pnow) 0) { failf(data, Operation timed out after ... milliseconds with ... bytes received); result CURLE_OPERATION_TIMEDOUT; goto out; } }正是因为每次循环迭代都从同一内存位置刷新时间旧方案中部分函数用过期时间、部分用实时时间导致超时判断失准的问题在这里被彻底消除。5.4 低速限制检测低速检测函数pgrs_speedcheck()lib/progress.c体现了按值保存历史时刻的正确姿势当瞬时速度低于low_speed_limit时它把*pnow按值拷入data-state.keeps_speed记录开始变慢的时刻之后每次检测用curlx_ptimediff_ms(pnow, data-state.keeps_speed)计算已持续的低速时长一旦超过low_speed_time即返回CURLE_OPERATION_TIMEDOUT。这里keeps_speed是值拷贝的时间点而pnow是每次刷新的最新时间——正是不要存储指针、需要历史点就拷值原则的活教材。5.5 速率限制rate limitingmulti 层的mspeed_check()lib/multi.c也遵循同一模式先Curl_pgrs_now(data)取当前时间再交给Curl_rlimit_wait_ms()计算距离下一个流量令牌发放还需等待多久随后用Curl_expire_set(data, EXPIRE_TOOFAST, ..., pnow)注册下一次唤醒。整个速率限制子系统相关文档见 docs/internals/RATELIMITS.md都建立在这套统一时间基准之上。5.6 传输开始与进度统计Curl_pgrsStart()lib/progress.c在传输初始化时把progress.start置为当前时间并清零所有统计项Curl_pgrsDone()progress.c则在传输结束时执行最终强制的进度更新。Curl_pgrsUpdate()、progress_calc()等也都统一从Curl_pgrs_now(data)取时间计算速率与剩余时间。六、对开发者的启示与最佳实践综合文档与源码这套时间管理机制沉淀出几条可迁移到其他项目的最佳实践循环内集中取时按需刷新不要在每层函数里随意调用系统时钟而是让当前时间集中存放于句柄对象中统一入口此处为Curl_pgrs_now()就地刷新不要传递时间戳指针函数签名不应被now参数污染谁需要时间谁自己取取到的一定是最新值需要历史时刻就按值拷贝struct curltime是 16 字节左右的小结构按值保存毫无压力切勿保存指向可刷新内存的指针用单调时钟而非墙上时钟只要目的是测间隔超时、速率、耗时统计单调时钟就能规避 NTP 调整、闰秒、手动改系统时间导致的跳变只有需要展示给用户的绝对时刻才用墙上时钟为时间线单调性加断言文档提到的时间线顺序断言queue → namelookup → connect → appconnect是发现时间倒流类 bug 的高性价比手段。七、总结curl 的内部时间管理经历了从循环开头取一次时间、指针向下传递到时间戳就地存放于struct Progress.now、通过Curl_pgrs_now(data)统一按需刷新的演进。这一设计消除了过时时间戳引发的时间跳跃问题保证了 multi 状态机中超时、低速检测、速率限制与时间线统计的一致性。核心代码集中在 lib/curlx/timeval.c跨平台单调时钟与 lib/progress.cCurl_pgrs_now/Curl_pgrsTime/Curl_pgrsTimeWas与进度统计设计初衷与演进理由则完整记录在 docs/internals/TIME-KEEPING.md 中。理解这套机制不仅有助于阅读 libcurl 源码也为自研网络库的时间管理设计提供了现成的、经过生产验证的范本。【免费下载链接】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),仅供参考
延伸阅读

更多相关文章

2026/9/11 23:39:14

Liver肝脏癌症2D医学图像分割实战指南

简介:本资源是面向医学图像分析与深度学习研究者的肝脏肿瘤分割专用数据集,适用于计算机辅助诊断、AI医学影像课程实验及分割模型(如U-Net、nnUNet)的训练验证。数据源自Liver3D原始NIfTI格式(.nii.gz)&…

2026/9/11 23:39:14

在线视频压缩原理与实操:平衡画质、体积与平台兼容性

1. 项目概述:为什么“在线视频压缩”成了2024年最被低估的刚需技能你有没有过这样的经历:拍了一段3分钟的家庭聚会视频,用手机录的,画质看着挺清楚,结果发微信给爸妈——发送失败;发邮箱被退回;…

2026/9/12 0:39:21

毕业设计之django图书馆座位预约系统

题目:毕业设计之django图书馆座位预约系统一、项目介绍随着时代的发展,人们的生活方式得到巨大的改变,从而慢慢地产生了大量图书馆座位预约,图书馆座位预约需要一个现代化的系统,进行图书馆座位预约的管理。图书馆座位…

2026/9/12 0:39:21

ShuffleNet轻量级网络实战:从分组卷积到宠物年龄识别

简介:这套基于 shufflenet 的宠物年龄识别项目,面向希望快速上手 PyTorch 图像分类的 Python/CV 学习者,解决从数据整理到模型训练、界面推理的完整闭环问题。代码仅三个 py 文件,流程简洁:可自动生成训练验证 txt、训…

2026/9/12 0:39:21

基于Android的跑步App源码全解析:定位、前台服务与数据算法

简介:基于Android平台、采用Java开发的跑步App完整项目源码,面向Android初学者和需要完成课程设计的学生,可用于快速掌握移动端应用开发流程。资源内置用户注册登录、计步传感器监测、运动计时、任务目标设定、跑步记录持久化存储等功能模块&…

2026/9/12 0:39:21

Python异步编程:核心原理与高并发实战

1. Python异步编程的核心价值与应用场景在当今高并发的互联网应用中,传统的同步编程模式常常面临性能瓶颈。我十年前第一次处理Web爬虫项目时,就深刻体会到了同步请求的效率问题——每个请求都要等待前一个完成,导致程序大部分时间都在空转。…

2026/9/12 0:34:20

MIMO-OFDM链路级仿真:信道估计、均衡与SCM信道模型

简介:面向无线通信研究与工程人员的多输入多输出正交频分复用(MIMO-OFDM)Matlab仿真资源,对应3G、4G、5G中多天线与正交频分复用核心技术的代码实现,包含完整的收发链路、信道估计与空间信道模型(SCM&#…

2026/9/10 16:39:38

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊!#雷神 #复联”这类调侃式短标题,第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里,但细想一下就能发现,它真正碰到的根本不是…

2026/9/10 11:16:38

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊,可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”,你会发现,这场比较本质上是两个不同 IP 策略的长期结果对比:超人赢在定义了整个超级英雄题材…

2026/9/9 16:31:09

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介:本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案,聚焦调制信号自动检测与识别这一典型无线通信任务,解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件(10.73MB)&…

2026/9/12 0:04:17

MATLAB仿生优化框架:长鼻浣熊算法多策略融合实现

简介:本资源是一份面向智能优化算法研究者与MATLAB初学者的仿生智能算法实践代码包,聚焦于长鼻浣熊优化算法(COA)的多策略改进与性能验证。针对传统COA易陷局部最优、收敛精度不足等问题,作者融合Circle映射初始化提升…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 JavaWeb 的校园一卡通管理系统的设计与实现 基于 JavaWeb 的校园卡业务管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/12 0:04:17

【JAVA毕设源码分享】基于 Java 的图书馆借阅管理平台的搭建与实现 基于 Java 的图书馆综合管理系统(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/9/10 12:32:02

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/10 15:19:50

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/10 15:49:53

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码