应使用 std::chrono::steady_clock 配合令牌桶算法控制下载速率,通过 sleep_until 精确等待、动态补发令牌并实时扣减,避免 sleep_for 粗暴限速导致吞吐抖动。

用 std::chrono 控制下载间隔,别碰 sleep_for 粗暴限速
直接调 std::this_thread::sleep_for 按固定间隔下载,表面看带宽稳了,实际吞吐剧烈抖动——因为没考虑网络 I/O 时间本身也占带宽。令牌桶的核心是「按时间匀速产令牌,按需扣减」,所以必须把下载耗时纳入节奏计算。
实操建议:
- 维护一个
last_refill_time和当前令牌数tokens,每次下载前先按时间差补满(上限为桶容量) - 每次读取
n字节后,从tokens扣掉n,再算下次允许下载的最早时间点,用std::this_thread::sleep_until等待,而非固定休眠 - 避免用
std::clock或time(),它们精度不够,std::chrono::steady_clock是唯一可靠选择
libcurl 的 CURLOPT_XFERINFOFUNCTION 怎么配合令牌桶?
libcurl 默认不暴露每批次传输细节,但启用 CURLOPT_XFERINFOFUNCTION 后,回调里能拿到已传字节数和总大小。关键是:它不等于「本次刚传的字节数」,而是累计值,必须自己做差分才能触发令牌扣除。
常见错误现象:tokens 越扣越负,最后卡死——因为回调被高频触发(比如每毫秒一次),但实际网络只发了几百字节。
立即学习“C++免费学习笔记(深入)”;
实操建议:
- 在回调函数里缓存上一次的
dl_bytes,仅当差值 ≥ 1024 才执行令牌扣除(避免高频小扣) - 设置
CURLOPT_NOPROGRESS为0L,否则回调根本不会触发 - 务必检查
CURLOPT_XFERINFODATA是否正确传入自定义上下文指针,否则tokens状态会乱
令牌桶参数怎么设才不拖慢小文件、又压得住大流量?
桶容量(burst)和填充速率(rate)不是拍脑袋定的。设太小,HTTP 连接建立开销占比飙升;设太大,短时突发就冲垮限速。
典型场景参考:
- 下载单个
10MB文件,目标限速1MB/s:桶容量设256KB(约 250ms 网络抖动缓冲),rate =1024 * 1024bytes/sec - 并发下载 5 个文件,总限速
2MB/s:每个连接 rate =400KB/s,桶容量同步缩到100KB,防止某个连接独占令牌 - 注意
std::chrono::nanoseconds在部分 Windows 版 libcurl 下可能被截断,优先用microseconds
为什么 read() 返回值要立刻检查,不能等整个 buffer 填满?
Linux/Unix 下 TCP socket 的 read() 是「尽力而为」,哪怕你传 8192 字节 buffer,也可能只返回几十字节——尤其在网络拥塞或远端发送方节奏不稳时。如果硬等 buffer 满再扣令牌,实际速率早超标了。
实操建议:
- 每次
read()后立刻用返回值n扣减令牌,哪怕n == 1 - 不要用
fread()封装 socket,它内部有缓冲层,会掩盖真实网络节奏 - 遇到
read()返回-1且errno == EAGAIN或EWOULDBLOCK,说明内核缓冲空了,此时应暂停读取、让令牌桶继续积累,而不是忙等
真正难的不是实现令牌桶,是把网络 I/O 的非确定性、系统调用的边界、以及时间精度这三件事对齐。漏掉任意一环,限速就变成「看起来差不多」。










