curl 限速指南:使用--limit-rate精确控制上传与下载带宽
【免费下载链接】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
--limit-rate是 curl 命令行工具中用于限制传输速率的核心参数,它同时作用于下载与上传,适用于带宽受限的窄带链路(limited pipe)或需要避免单个传输占满整条带宽的场景。阅读本文后,你将掌握该参数的完整语法(含 1024 进制单位后缀与 8.19.0 起支持的小数值)、它与--speed-limit/--speed-time/--max-time的协作关系,并了解从命令行参数解析到 libcurl 令牌桶(token bucket)限速引擎的完整底层实现。本文内容以 limit-rate 选项文档为主体,结合仓库源码与实际配置展开。
选项速览
以下元数据取自 limit-rate.md 的文件头,是 curl 帮助系统与 man page 的生成来源:
| 属性 | 值 |
|---|---|
| 长选项 | --limit-rate |
| 参数 | <speed> |
| 帮助文本 | Limit transfer speed to RATE |
| 分类(Category) | connection |
| 引入版本(Added) | 7.10 |
| 是否可复用(Multi) | single(单次调用仅生效一次,后出现的值覆盖先前的值) |
| See-also | --rate、--speed-limit、--speed-time |
--limit-rate的功能与使用场景
--limit-rate用于指定 curl 希望使用的最大传输速率,且同时约束下载与上传两个方向。文档的原话是:
Specify the maximum transfer rate you want curl to use - for both downloads and uploads. This feature is useful if you have a limited pipe and you would like your transfer not to use your entire bandwidth.
适用场景非常明确:当你处于窄带链路上,或运行环境要求 curl 的传输不能占满整条带宽时,用它可以刻意让传输比默认更慢、更"温和"("To make it slower than it otherwise would be")。典型用法包括:
- 后台下载大文件时给其他应用留出带宽;
- 上传到远端服务器时避免打满上行而影响其他业务;
- 在自动化脚本中对每个传输做流控,避免瞬时并发冲击。
单位语法:1024 进制的字节速率
默认情况下<speed>以字节/秒为单位;一旦附加后缀,则按 1024 进制换算:
| 后缀(大小写均可) | 含义 | 换算 |
|---|---|---|
| 无后缀 | 字节/秒 | 例如1000即 1000 字节/秒 |
k/K | 千字节 | 1k= 1024 |
m/M | 兆字节 | 1m= 1024 × 1024 |
g/G | 吉字节 | 1024³ |
t/T | 太字节 | 1024⁴ |
p/P | 拍字节 | 1024⁵ |
文档原句强调:The supported suffixes (k, M, G, T, P) are 1024-based. For example 1k is 1024.也就是说这里没有所谓的"商业十进制换算",1k严格等于 1024 字节而非 1000。官方给出的示例值包括200K、3m和1G。
8.19.0 起支持小数
从 curl 8.19.0 开始,速率可以用小数指定,例如2.5M表示"每秒两个半兆字节"(two and a half megabytes per second)。注意两点限制:
- 分隔符只能是小写句点(
.),与系统 locale 的偏好无关——即不要使用逗号等形式; - 该语法由命令行解析层支持(详见下文 GetSizeParameter),解析出的最终值仍是整数字节数。
# 每秒 2500 KB(2.5 MiB/s)级下载 curl --limit-rate 2.5M https://example.com/big.bin基本用法示例
原文档给出四个可直接运行的示例,覆盖了纯字节、小写/大写后缀、以及限速与超时联用的场景:
# 1) 小数 + 后缀:约 123.45 KiB/s curl --limit-rate 123.45K $URL # 2) 纯数字,无后缀:每秒不超过 1000 字节 curl --limit-rate 1000 $URL # 3) 大写后缀:每秒不超过 10 MiB curl --limit-rate 10M $URL # 4) 限速 + 总超时:即使 200 KiB/s 也最多只跑 60 秒 curl --limit-rate 200K --max-time 60 $URL示例 4 是两个"自我保护"型参数的组合:--limit-rate让速度不超过阈值,--max-time则设定整个操作的最长耗时,两者常用于下载场景的上限约束。
参数解析实现:GetSizeParameter 与 GetParameter
命令行层对--limit-rate的解析位于 src/tool_getparam.c:
case C_LIMIT_RATE: /* --limit-rate */ err = GetSizeParameter(nextarg, &value); if(!err) { config->recvpersecond = value; config->sendpersecond = value; } break;可见一个关键实现细节:一次--limit-rate会同时写入recvpersecond与sendpersecond两个字段,这正是它同时约束上下行速率的来源。两个字段定义于 src/tool_cfgable.h:
curl_off_t sendpersecond; /* send to peer */ curl_off_t recvpersecond; /* receive from peer */真正的"数值 + 单位 + 小数"解析由同一文件中的GetSizeParameter()完成(src/tool_getparam.c)。该函数与--max-filesize共用,其代码注释明确声明:"We support P, T, G, M and K (case insensitive) suffixes",并有对应单元测试(代码注释中的 "Unit test 1623")。其内部要点如下:
- 单位查找表
getunit()定义了 p/t/g/m/k 五个后缀及各自的十进制位长(用于小数部分的对齐换算); - 单位匹配是大小写不敏感的:匹配时通过
(unit | 0x20)统一转为小写比较; - 解析先读整数部分,若紧接着读到
.则继续解析小数位(curlx_str_number/curlx_str_single),小数部分按单位十进制位数换算回字节整数;位数超过单位精度时会被"修剪"; - 计算结果会做溢出防护:
if(value > ((CURL_OFF_T_MAX - add) / mul)) return PARAM_NUMBER_TOO_LARGE;; - 传入的原始参数若是形如
123.45K、10M或1000的裸数字,都会在这里被换算为以字节为单位的curl_off_t整数。
之后,src/config2setopts.c 在把配置写回 libcurl 句柄时映射为两个 libcurl 选项:
my_setopt_offt(curl, CURLOPT_MAX_SEND_SPEED_LARGE, config->sendpersecond); my_setopt_offt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, config->recvpersecond);这意味着--limit-rate命令行参数最终对应到 libcurl 传输层的CURLOPT_MAX_SEND_SPEED_LARGE / CURLOPT_MAX_RECV_SPEED_LARGE两个选项。在 src/config2setopts.c 中还有一处细微优化:当recvpersecond非零且小于默认缓冲区大小时,会同步调小CURLOPT_BUFFERSIZE,避免接收缓冲过大而破坏细粒度限速。
底层限速引擎:令牌桶算法
curl 的限速并不只是"每 N 字节睡一会儿",而是由 lib 层的rate limiter(lib/ratelimit.h、lib/ratelimit.c)实现。该模块注释开宗明义地指出其算法本质:
This is a rate limiter that provides "tokens" to be consumed per second. In the literature, this is referred to as a "token bucket"(令牌桶).
令牌桶模型
以"每秒 1 MiB"为例,lib/ratelimit.h 的注释描述了直观行为:
- 初始状态下桶里有 100 万令牌(对应 1 MiB);
- 传输时令牌被逐字节"抽取"(drain),第一个秒内抽完;
- 若在下一秒到来之前检查可用令牌,返回 0;
- 到达/超过下一秒后,桶里重新补满 100 万令牌;
- 若中途空闲了一秒,令牌会累积到 200 万,但burst(突发)上限会把它封顶(例如封顶在 150 万),从而保证"平均速率"而非"瞬时吞吐峰值"被约束。
核心数据结构struct Curl_rlimit(lib/ratelimit.h)包含:
struct Curl_rlimit { int64_t rate_per_sec; /* rate tokens generated per second */ int64_t burst_per_sec; /* burst rate of tokens per second */ int64_t rate_per_step; /* rate tokens generated per step us */ int64_t burst_per_step; /* burst rate of tokens per step us */ timediff_t step_us; /* microseconds between token increases */ int64_t tokens; /* tokens available in the next second */ timediff_t spare_us; /* microseconds unaffecting tokens */ struct curltime ts; /* time of the last update */ BIT(blocked); /* blocking sets available tokens to 0 */ };注释中还解释了 burst 语义的两极:若把 burst 设为CURL_OFF_T_MAX(趋近无穷大),令牌在整个传输生命周期内持续累积,则从开始到结束的平均速率约等于设定值(对应文档所说 "averaging ... over a period of multiple seconds");若把 burst 设为与 rate 相同,则传输会始终尝试保持不高于该速率,空闲产生的多余令牌不会带来突发。
引擎的关键函数
lib/ratelimit.c 提供了整套原语:
Curl_rlimit_init()(L154):按每秒速率与突发速率初始化令牌桶,初始即注入一个 step 的令牌;Curl_rlimit_start()(L171):重置令牌桶,并调用rlimit_tune_steps()依据"本次传输预计消耗的总令牌数"微调 step 时长——其注释(L82-L152)解释了为什么要"调步":默认按 1 秒为一步发放令牌,若剩余流量不足一步(例如以 1k 限速下载 1.5kb,最后一跳可能瞬间跑完导致平均超速),调步逻辑会把最后一步压缩到只分配总令牌的 1%(至少 1 个),使末尾不会出现"超速冲刺";Curl_rlimit_avail()(L200):返回当前可用令牌数,blocked状态下恒为 0;Curl_rlimit_drain()(L219):消耗令牌;Curl_rlimit_wait_ms()(L247):计算还需等待多少毫秒令牌才会重新为正,供调度层休眠;Curl_rlimit_next_step_ms()(L274):返回距离下一次令牌补充还有多久;Curl_rlimit_block()(L289):阻塞/解除阻塞限速,解除后历史清零、从零重新开始计时。
其中几个常量值得留意:CURL_RLIMIT_MIN_RATE = 4 * 1024(调步后单步最少令牌数)、CURL_RLIMIT_STEP_MIN_MS = 2(最小 step 时长,过小的 step 会被放弃调步),以及每步默认step_us = CURL_US_PER_SEC(lib/ratelimit.c)。
与传输主循环的挂钩
限速器被挂接在传输主循环的多个节点上,从代码结构看形成了"检查令牌 → 等待 → 读取/写入 → 抽取令牌"的闭环:
- 初始化:在 lib/setopt.c 中,设置
CURLOPT_MAX_SEND_SPEED_LARGE/CURLOPT_MAX_RECV_SPEED_LARGE时会分别对上传(ul)与下载(dl)两个方向调用Curl_rlimit_init; - 启动:每个传输方向开始时调用
Curl_rlimit_start(lib/sendf.c 与 lib/sendf.c); - 抽取令牌:实际收发字节后调用
Curl_rlimit_drain,例如 lib/progress.c 在进度统计更新时把本周期传输的字节数(delta)从对应令牌桶中扣除; - 调度等待:lib/multi.c 会先检测
Curl_rlimit_avail()判断收发是否被限速阻塞;随后在 lib/multi.c 用Curl_rlimit_wait_ms()/Curl_rlimit_next_step_ms()计算需要等待的毫秒数,并据此延后相关事件; - 暂停/恢复:传输暂停时会
Curl_rlimit_block(lib/transfer.c),恢复时在 lib/request.c 解除阻塞并按当前时间重置,阻塞期间不产生令牌。
对 HTTP/2、HTTP/3 的适配
限速令牌还被用于高级协议层面的流量控制:例如 HTTP/2 的窗口与流控制需要感知可用令牌(lib/http2.c),HTTP/3(QUIC/ngtcp2)的cf-ngtcp2.c与代理链cf-ngtcp2-proxy.c也会查询Curl_rlimit_avail(&data->progress.dl.rlimit)(lib/vquic/cf-ngtcp2.c、lib/vquic/cf-ngtcp2-proxy.c)。这意味着即便在 h2/h3 多路复用场景下,--limit-rate依然能作用于聚合后的整体速率。
限速的"平均"语义与窗口特性
文档特别澄清了限速逻辑的工作方式:
The rate limiting logic works on averaging the transfer speed to no more than the set threshold over a period of multiple seconds.
即 curl 并不保证任意一个瞬间都不超过阈值,而是保证在一段以秒为单位的时间窗内平均速率不越过设定值。对应到令牌桶实现,就是令牌按秒级 step 发放、burst 上限对"闲时积攒"进行封顶(见 lib/ratelimit.h 的 burst 说明)。因此:
- 对于很小的文件、很短的传输,瞬时速率可能看起来"超标";
- 传输时间越长,整体平均速率越贴近设定值;
- 不要指望它对单个字节包做硬性节流。
与--speed-limit、--speed-time的优先级关系
--speed-limit与--speed-time是一对"低速中止"参数(参见 speed-limit.md 与 speed-time.md):如果传输速度持续低于--speed-limit指定的字节/秒并超过--speed-time指定的秒数(默认 30 秒),传输会被直接中止。这与--limit-rate(仅限速、不中止)目的相反。
当两者同时使用时,文档明确指出其优先级关系:
If you also use the --speed-limit option, that option takes precedence and might cripple the rate-limiting slightly, to help keep the speed-limit logic working.
即--speed-limit优先。为了让"低速检测"能够正常触发(否则传输始终以限速阈值附近的低速运行,可能永远不会低于speed-limit而被认为仍在健康传输),curl 会略微"削弱"限速——让实际节流稍稍放松一点、速度偶尔超过--limit-rate阈值,从而保证--speed-limit的低速判定逻辑依然有效。这是两者共存时的有意为之,而非 bug。
因此实际组合策略通常是:
- 想要控制带宽占用→ 只用
--limit-rate; - 想要在链路异常变慢时及时退出→ 使用
--speed-limit+--speed-time; - 想要"限速 + 兜底"→ 三者或与
--max-time联用,例如原文档示例curl --limit-rate 200K --max-time 60 $URL。
通过 libcurl API 使用限速
--limit-rate在命令行同时设置了两个 libcurl 选项(见 src/config2setopts.c),因此 libcurl 使用者可以分别对两个方向独立限速:
CURLOPT_MAX_RECV_SPEED_LARGE——限制下载速率(字节/秒);CURLOPT_MAX_SEND_SPEED_LARGE——限制上传速率(字节/秒)。
这与命令行的"一次设置、双向生效"不同:命令行因为只有一个参数值,只能把同一个值赋给两个方向(src/tool_getparam.c);而通过 API 可以做到"下载限 2M、上传限 500K"这类不对称限速。例如:
CURL *curl = curl_easy_init(); /* 下载不超过 2 MiB/s,上传不超过 512 KiB/s */ curl_easy_setopt(curl, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t)(2 * 1024 * 1024)); curl_easy_setopt(curl, CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t)(512 * 1024));与限速相关的还有CURLOPT_LOW_SPEED_LIMIT/CURLOPT_LOW_SPEED_TIME(对应命令行--speed-limit/--speed-time),可做低速中止。
使用注意事项小结
- 单位均为1024 进制:
1K = 1024字节,不是 1000;后缀 k/m/g/t/p 大小写皆可(解析时统一做小写匹配)。 - 从 8.19.0 起支持小数(如
2.5M),但分隔符只能是英文句点.,与 locale 无关;小数解析超过单位精度时会被截断对齐(见GetSizeParameter的修剪逻辑)。 - 速率默认同时作用于下载与上传;若需非对称限速,请直接使用 libcurl 的
CURLOPT_MAX_RECV_SPEED_LARGE/CURLOPT_MAX_SEND_SPEED_LARGE。 - 限速是秒级平均约束而非逐字节精确节流,令牌桶的 burst 上限决定了它如何平滑"闲时积攒"带来的突发。
- 与
--speed-limit并存时,后者优先,限速会被轻微放松以维持低速检测的有效性(limit-rate.md 原文说明)。 --limit-rate的 Category 为 connection、Multi 为 single,同一命令行中重复出现时以最后一次为准;--rate(rate.md)则是另一个完全不同用途的选项——它限制的是发起请求的频率(每秒/每分/每小时执行多少次传输,实现于 src/tool_getparam.c 的set_rate),勿与传输速率混淆。
延伸阅读
若需深入了解本文涉及的相邻主题,可继续阅读仓库中的以下文档与源码:
- 选项主文档:limit-rate.md
- 相邻选项:speed-limit.md、speed-time.md、rate.md
- 令牌桶实现:lib/ratelimit.c、lib/ratelimit.h
- 参数解析与选项映射:src/tool_getparam.c、src/config2setopts.c
- 调度与字节抽取:lib/multi.c、lib/progress.c、lib/transfer.c、lib/sendf.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),仅供参考