Linux用/proc/net/dev解析网卡收发字节数最直接,关键在于严格控制两次读取时间间隔、跳过头部两行和lo接口,按网卡名匹配后提取第2/10列字节数,结合steady_clock计算增量速率;Windows须用GetIfEntry2()配合QueryPerformanceCounter(),避免统计延迟与毛刺影响。

Linux下用/proc/net/dev解析网卡收发字节数最直接
实时速率本质是单位时间内的字节增量,/proc/net/dev每秒可稳定读取,字段对齐固定,无需额外依赖。关键不是“怎么读”,而是“怎么算准”——两次读取间隔必须严格控制,且要跳过头部两行和回环接口lo。
- 每次读取后按空格分割第2列(接收字节数)和第10列(发送字节数),注意字段可能被多个空格分隔,推荐用
std::istringstream配合>>自动跳空白 - 记录上一次的值和对应
std::chrono::steady_clock::now()时间戳,当前值减去上次值得到增量,除以时间差(秒)即得B/s,再×8得bps - 别硬编码字段索引:先扫描行首匹配网卡名(如
"ens33:"),再提取其后数值,避免因内核版本导致列偏移
C++中用std::this_thread::sleep_for()控制采样间隔反而容易漂移
想每秒统计一次?直接sleep_for(1s)会导致累积误差——读取、计算、输出本身耗时未扣除,实际间隔越来越长。尤其当系统负载高时,sleep可能被延迟数百毫秒。
- 正确做法是用
std::chrono::steady_clock做“目标时间点”控制:每次循环末计算下次采集应处的时间点,再sleep_until()过去 - 例如:起始
next = steady_clock::now() + 1s,循环体执行完立刻sleep_until(next),然后更新next += 1s - 若某次处理超时(比如磁盘IO卡顿),
sleep_until会立即返回,此时应跳过本次统计或打日志,避免把异常延迟计入速率
Windows平台必须用GetIfEntry2()而非GetIfTable()
GetIfTable()返回的是快照式数据,且部分字段(如dwInOctets)在旧版系统中不更新;而GetIfEntry2()支持实时查询,且能获取精确到纳秒的时间戳,适配现代网卡驱动。
- 先调用
ConvertStringToGuid()将网卡名转为NET_LUID,再用GetIfEntry2()查ifInOctets和ifOutOctets - 注意
GetIfEntry2()需链接iphlpapi.lib,且最低要求Windows 7 SP1,XP/2003无法使用 - 速率计算逻辑与Linux一致,但时间差必须用
QueryPerformanceCounter()而非GetTickCount64(),后者分辨率仅15ms,不足以支撑1秒级精度
别忽略网络栈缓冲区和统计延迟带来的毛刺
即使采样逻辑完美,/proc/net/dev或GetIfEntry2()返回的仍是内核软中断处理完成后的计数,TCP ACK确认、网卡DMA写入、驱动批量上报都会引入10–200ms不等的延迟。这意味着你看到的“实时速率”其实是100ms前的真实流量。
立即学习“C++免费学习笔记(深入)”;
- 画趋势图时,单点突增(如从0跳到12MB/s)大概率是缓冲区集中flush,不是真实突发;建议对原始速率做3点滑动平均,但窗口别超过3秒,否则掩盖真实抖动
- 如果应用层需要响应真实拥塞(比如自适应码率),别依赖这个统计——它滞后,应改用socket-level的
SO_RCVBUF水位或TCP_INFO中的tcpi_unacked等指标 - 容器环境里,
docker0或cni0的统计值可能包含宿主机转发流量,要区分业务容器IP段再聚合,不能直接加总


















