可用getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &len)获取tcpi_bytes_sent和tcpi_bytes_received累计值,需两次采样求差并除以时间间隔得瞬时速率,注意重传字节包含在内且存在32位溢出风险。

如何用 getsockopt 获取 TCP 连接的已发送/接收字节数
标准 C++ 没有内置接口直接暴露“实时吞吐量”,必须依赖底层 socket 选项。Linux 上最可靠的方式是读取 TCP_INFO(SOCK_STREAM)或使用 SO_SNDBUF/SO_RCVBUF 配合应用层计数——但后者不反映真实网络流量。真正能拿到内核统计的是 getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &len),其中 tcpi_bytes_sent 和 tcpi_bytes_received 字段给出累计值。
-
TCP_INFO在 Linux 2.6+ 可用,glibc ≥ 2.4;Windows 不支持,需换用GetPerTcpConnectionEStats - 字段是只读快照,需两次调用差值 + 时间间隔才能算出瞬时速率(如每 100ms 采样一次)
- 注意:
tcpi_bytes_sent包含重传字节,不是应用层有效吞吐量;若需净吞吐,得结合tcpi_total_retrans手动扣除 - 示例片段:
struct tcp_info info; socklen_t len = sizeof(info); if (getsockopt(sockfd, IPPROTO_TCP, TCP_INFO, &info, &len) == 0) { uint64_t sent = info.tcpi_bytes_sent; }
为什么不能直接用 recv/send 返回值累加来统计吞吐量
应用层调用 recv 或 send 的返回值只代表本次系统调用处理的字节数,不等于实际网络收发量。常见偏差来源包括:
- 内核可能将多个小包合并发送(Nagle 算法),
send()返回后数据仍在 socket 发送缓冲区未上 wire -
recv()返回的是从接收缓冲区拷贝出的字节数,但数据可能早已被内核从网卡 DMA 到内存,只是还没被应用读取 - 启用
SO_LINGER或关闭连接时,未确认的重传包仍会计入内核统计,但不会触发应用层send() - UDP 场景更复杂:
sendto()成功只表示进入发送队列,丢包完全无反馈
跨平台方案:Linux / macOS / Windows 的等效实现差异
没有统一 API,必须按平台分支处理:
- Linux:用
TCP_INFO(推荐)或SO_TXQLEN+SO_RCVBUF估算排队量 - macOS:
TCP_CONNECTION_INFO(需sys/socket.h+netinet/tcp.h),字段名不同(如tcpi_bytes_out对应发送量) - Windows:必须调用
GetPerTcpConnectionEStats(),且需先启用TcpConnectionEstatsPath类型,否则返回ERROR_NOT_SUPPORTED - 所有平台都不提供“当前 1 秒吞吐量”这种聚合值——你得自己维护滑动窗口或环形缓冲区存历史采样点
容易被忽略的精度陷阱:采样频率与系统负载影响
吞吐量是时间导数,采样间隔太长(如 1s)会掩盖突发峰值;太短(如 1ms)则系统调用开销反成瓶颈,且内核统计本身更新也有延迟(通常 10–100ms 级)。
立即学习“C++免费学习笔记(深入)”;
- 建议固定间隔采样(如 100ms),用单调递增的
clock_gettime(CLOCK_MONOTONIC, ...)计时,避免gettimeofday()跳变干扰 - 若进程 CPU 占用高,采样线程可能被调度延迟,导致计算出的速率偏低——此时应记录每个采样点的绝对时间戳,而非假设等间隔
-
tcpi_bytes_*是 32 位字段(旧内核),大流量下 4GB 溢出后归零,务必检查是否发生回绕(比较前后值突降) - 多线程访问同一 socket 时,
getsockopt是线程安全的,但你的累加变量需加锁或用原子操作
真正可靠的吞吐量统计永远需要结合内核指标 + 应用层埋点 + 时间戳对齐,单靠某一个接口必然丢失上下文。


















