直接读取/proc/net/tcp可获取tcp连接快照,但需正确解析十六进制ip(小端转点分十进制)、端口(大端十六进制直接转整数)及状态码(如01→established),且受用户权限限制。

直接读 /proc/net/tcp 就行,但得懂十六进制地址和状态码
Linux 内核把当前所有 TCP 连接快照写在 /proc/net/tcp 里,是纯文本、无锁、零拷贝的底层接口。它不经过 socket API,也不依赖 netstat/ss,所以最轻量——但格式反人类:IP 和端口全用十六进制小端编码,状态是数字,不是字符串。
常见错误是直接用 std::stoi(line, nullptr, 16) 解 IP 却忘了字节序翻转。比如 0100007F 看着像 127.0.0.1,其实是 7F 00 00 01(小端),得先反转字节再转点分十进制。
-
/proc/net/tcp第一行是表头,跳过 - 第2列(
local_address)和第3列(rem_address)都是hex_ip:hex_port格式 - 第4列
st是 TCP 状态,01= ESTABLISHED,0A= LISTEN,查include/net/tcp_states.h或内核文档 - 第10列
inode可用来关联/proc/[pid]/fd/下的 socket 文件(需 root 或同用户)
解析端口时别漏掉 ntohs() 的等效操作
C 语言里通常用 ntohs() 把网络字节序端口转为主机序,C++ 没强制依赖 C 库也能做——因为 /proc/net/tcp 里的端口是十六进制大端表示,但值本身已经是网络序,所以只需转成整数再“按主机序解释”即可。实际就是:读出 0017 → 十进制 23 → 它就是 23,不用额外翻字节。
- 错误做法:
uint16_t port = (hex_val & 0xFF) > 8)——这是对小端 hex 值二次翻转,错 - 正确做法:
unsigned short port = static_cast<unsigned short>(std::stoul(hex_port_str, nullptr, 16))</unsigned> - 注意:
std::stoul("0017", nullptr, 16)返回 23,直接可用
状态码映射必须硬编码,别信字符串匹配
/proc/net/tcp 的 st 字段永远是两位十六进制(如 01、0A),不会变成 ESTABLISHED。想打印可读状态,只能自己建个映射表。内核不会改这个编码规则,但不同内核版本可能新增状态(如 0F = TCP_CLOSE),所以别只覆盖常见几个。
- 关键状态:
"01"→ ESTABLISHED,"0A"→ LISTEN,"06"→ TIME_WAIT,"02"→ SYN_SENT - 别用
if (st == "01")字符串比较——效率低且易拼错;用std::map<:string std::string></:string>或 switch +std::stoi(st, nullptr, 16) - 忽略
st == "00"的条目,那是未完成连接(如 SYN_RECV 中断后残留)
权限和稳定性:非 root 只能看到本用户的连接
读 /proc/net/tcp 本身不需要 root,但内容受 /proc/sys/net/ipv4/ip_unprivileged_port_start 和进程 UID 限制:普通用户只能看到自己创建的 socket 对应的连接行;root 才能看全量。另外,该文件是瞬时快照,两次读取之间连接可能已消失或新建,不适合做精确计时或状态跟踪。
- 如果 open() 失败并报
Permission denied,不是路径错,而是你没权限访问该 proc 条目(检查是否被 nsjail/seccomp 拦截) - 不要用
fseek或反复read()同一个 fd——内核每次 read 都重新生成内容,fd 无意义 - 单次读完就 close,别长期持有;频繁调用建议加毫秒级限频,避免触发内核 proc 接口抖动
真正难的不是读文件,是把 0100007F:0017 在脑子里秒变 127.0.0.1:23,以及记住 0A 是监听不是关闭——这些细节不写进代码注释,下次维护准懵。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











