Files
baim0-learn/linux/01 netstat-TCP-UDP状态统计.md
2026-09-16 18:13:56 +08:00

10 KiB
Raw Permalink Blame History

title, date, tags, categories
title date tags categories
netstat 统计 TCP/UDP 连接状态 2026-09-14
linux
linux

1. 经典命令解析

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a,S[a]}'

作用:统计当前所有 TCP 连接按状态分组的数目(不是只查 TIME_WAIT,TIME_WAIT 只是输出中可能出现的一种状态)。

实际输出示例:

ESTABLISHED 4
TIME_WAIT 2

逐段解析

部分 作用
netstat -n 列出所有连接,-n 用数字 IP/端口,不做 DNS 反解(更快)
/^tcp/ 只匹配以 tcp 开头的行(即 TCP 连接)
++S[$NF] 用每行最后一列(连接状态 State)作为 key 计数
END {for(a in S) print a,S[a]} 遍历数组,打印每种状态及数量

2. 常见 TCP 状态含义

状态 含义 异常信号
ESTABLISHED 已建立的正常连接 —
TIME_WAIT 主动关闭方等待 2MSL 大量出现(成千上万)说明短连接过多,考虑 keep-alive / 连接池
CLOSE_WAIT 对方已关闭、本端还没 close 大量堆积通常是程序 bug(socket 没正确关闭)
LISTEN 监听中的服务端口 —
SYN_SENT / SYN_RECV 握手进行中 异常增多可能是网络问题或 SYN 攻击

3. 只看 TIME_WAIT 的写法

# netstat 精确过滤
netstat -n | awk '/^tcp/ && $NF=="TIME_WAIT" {n++} END {print n+0}'

# 或用 ss(推荐,速度快很多)
ss -ant state time-wait | wc -l

# ss 自带汇总
ss -s

4. 深入:Recv-Q / Send-Q 队列详解

ss -ant / netstat -ant 输出的第 2、3 列 Recv-Q 和 Send-Q 是排查「连接被拒绝」「请求超时」「应用处理慢」类问题的第一手指标。

4.1 Recv-Q 到底是什么

Recv-Q 的含义取决于 socket 所处的状态,两种场景完全不同:

socket 状态 Recv-Q 含义 Send-Q 含义
LISTEN(监听中) 已完成三次握手、等待应用 accept() 的连接数(全连接队列当前长度) 全连接队列的最大容量(backlog)
ESTABLISHED(已建立) 内核已收到、但应用还没 read() 的数据字节数 已发送但尚未被对端 ACK 的字节数

⚠️ 老内核(2.x 及更早的一些版本)中 LISTEN 行的 Send-Q 列显示为 0,新内核(3.10+ 配合新版 iproute2)显示的是 backlog 上限。现代发行版直接看 ss -lnt 即可。

4.2 查看方式一:ss 命令(推荐)

ss(socket statistics)来自 iproute2 包,直接读内核 netlink,比 netstat 快几个数量级,是现在的标准工具。

# 查看所有 TCP 连接的 Recv-Q / Send-Q
ss -ant

# 只看监听端口(Recv-Q = 等待 accept 的连接数,Send-Q = backlog 上限)
ss -lnt

# 只看已建立的连接
ss -ant state established

# 按状态过滤 + 带进程信息(哪个进程积压了数据)
ss -antp state established

# 只过滤某个端口
ss -antp 'sport = :80'

实际输出示例:

State    Recv-Q  Send-Q   Local Address:Port   Peer Address:Port
LISTEN   0       4096     127.0.0.54:53        0.0.0.0:*
LISTEN   0       2048     127.0.0.1:8088       0.0.0.0:*
LISTEN   0       511      0.0.0.0:80           0.0.0.0:*
LISTEN   0       4096     0.0.0.0:22           0.0.0.0:*
ESTAB    1024    0        10.0.0.1:80          10.0.0.2:52314

解读:

  • :80 是 LISTEN 行:Send-Q 511 是 backlog 上限(nginx 默认 511),Recv-Q 0 说明 accept 队列没有积压。
  • 最后一行 ESTAB:Recv-Q 1024 表示有 1024 字节数据躺在内核缓冲区里,应用还没读。

进阶参数:

# -m 显示 socket 内存使用(rb=接收缓冲区上限,r=实际占用)
ss -lntm

# -i 显示 TCP 内部信息(拥塞窗口、RTT、重传等)
ss -anti state established

# -o 显示定时器信息(keepalive/retrans)
ss -anto

ss -ltnm 输出示例:

LISTEN 0 4096 127.0.0.54:53 0.0.0.0:*
     skmem:(r0,rb131072,t0,tb16384,f0,w0,o0,bl0,d0)

其中 r0 = 接收队列当前字节数,rb131072 = 接收缓冲区上限 128KB。

4.3 查看方式二:netstat(老命令)

netstat -ant     # 所有 TCP
netstat -lnt     # 只监听
netstat -antp    # 带进程(需要 root)

输出列含义与 ss 一致。注意:netstat 逐条遍历 /proc/net/tcp 并做解析,大连接数机器上非常慢,线上优先用 ss。

4.4 查看方式三:直接读 /proc/net 目录

内核把 socket 信息暴露在 procfs 下,不依赖额外工具,适合容器/精简环境或写脚本:

cat /proc/net/tcp      # TCP IPv4
cat /proc/net/tcp6     # TCP IPv6
cat /proc/net/udp      # UDP
cat /proc/net/unix     # Unix socket

字段格式:

  sl  local_address rem_address   st tx_queue:rx_queue tr tm->when retrnsmt   uid  timeout inode
   0: 3600007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000   992        0 731603 1 ...

关键列:

列 含义
local_address 本地地址,格式 十六进制IP:十六进制端口,IP 是小端序
rem_address 对端地址,同上
st socket 状态,0A = LISTEN,01 = ESTABLISHED
tx_queue:rx_queue 发送队列:接收队列,均为十六进制字节数 —— 这里的 rx_queue 就是 Recv-Q

手工解码示例:3600007F:0035

  • IP 3600007F 小端 → 字节序 7F 00 00 36 → 127.0.0.54
  • 端口 0035 → 十进制 53

用 awk 快速筛出 rx_queue 非 0 的连接:

awk 'NR>1 {split($5,q,":"); if (strtonum("0x" q[2]) > 0) print $2, $3, $4, strtonum("0x" q[2])}' /proc/net/tcp

由 inode 反查进程:ls -l /proc/<pid>/fd 中 socket:[731603] 的编号对应 inode 列。

4.5 典型排查场景

场景一:LISTEN 的 Recv-Q 持续打满 Send-Q(backlog)

现象:ss -lnt 看到 Recv-Q ≈ Send-Q,客户端报 connection refused / timeout。

原因:应用 accept() 速度跟不上握手完成速度,全连接队列溢出,内核开始丢新连接。

确认丢包计数:

nstat -az | grep -iE 'listenoverflow|listendrop'
# ListenOverflows   ← 半连接/全连接队列溢出次数
# ListenDrops       ← 因队列满被丢弃的连接数

处理方向:

  1. 调大应用 backlog(如 nginx listen 80 backlog=8192;、Java 的 backlog 参数);
  2. 调大内核上限:net.core.somaxconn(默认常见 128/4096)、net.ipv4.tcp_max_syn_backlog(半连接队列);
  3. 治本:提升应用 accept 能力(多 worker、SO_REUSEPORT)。
sysctl -w net.core.somaxconn=4096
sysctl -w net.ipv4.tcp_max_syn_backlog=4096

场景二:ESTABLISHED 的 Recv-Q 持续增长

现象:某些 ESTAB 连接的 Recv-Q 越来越大且不回落。

含义:对端一直在发,本端应用读得太慢或不读(业务卡死、GC、线程池打满)。

定位:

# 找到是哪个进程
ss -antp state established | awk 'NR==1 || $2 > 0'

# 持续观察,5 秒采样一次
watch -n5 "ss -antp | awk 'NR==1 || \$2 > 0'"

处理方向:查该进程的 CPU/线程/GC 状态,确认消费逻辑是否阻塞。

场景三:UDP 的 Recv-Q

UDP 没有连接,ss -anu / cat /proc/net/udp 里的 rx_queue 表示已收到但未被应用读取的报文字节数。UDP 接收缓冲区满后报文被静默丢弃,可配合计数器确认:

nstat -az | grep -iE 'udprcvbuferrors|udpinerrors'

4.6 持续监控写法

# 每 2 秒刷新所有队列非空的连接
watch -n2 'ss -ant | awk "NR==1 || \$2>0 || \$3>0"'

# 只盯 80 端口 accept 队列
watch -n1 "ss -lnt 'sport = :80'"

采集进监控体系的话,Prometheus node_exporter 的 node_netstat / 自定义 textfile 脚本解析 ss -ant 输出即可。

4.7 速查表

需求 命令
看所有连接队列 ss -ant
看 accept 队列积压 ss -lnt(Recv-Q vs Send-Q)
找积压数据的进程 ss -antp
看 socket 内存占用 ss -lntm / ss -tm
裸文件方式 cat /proc/net/tcp(第 5 列 tx_queue:rx_queue,hex)
accept 队列丢包计数 nstat -az | grep -i listen
调大 accept 队列 net.core.somaxconn + 应用 backlog 参数
持续观察 watch -n2 'ss -ant'

5. UDP 为什么不能照搬这个命令

关键区别:UDP 是无连接的,没有 TIME_WAIT 这类状态。

  • netstat -anu 输出中 UDP 行的 State 列是空的
  • 若还按 $NF 分组,统计到的是倒数第一列 0.0.0.0:*(Foreign Address),毫无意义
  • 另外 netstat -n 不加 -a 时,大部分 UDP socket(未 connect 的)根本不显示

UDP 行长这样(State 列为空):

udp   0   0 127.0.0.54:53    0.0.0.0:*

6. UDP 的正确排查姿势

# 1. 看所有 UDP socket(必须加 -a,否则大部分不显示)
netstat -anu

# 2. 按本地端口统计,看哪些服务开着 UDP
netstat -anu | awk '/^udp/ {split($4,a,":"); ++S[a[length(a)]]} END {for(p in S) print p, S[p]}'

# 3. 用 ss 看状态计数(UDP 只有 UNCONN / ESTAB 两种)
ss -uan | awk 'NR>1 {++S[$1]} END {for(a in S) print a, S[a]}'

# 4. UDP 整体健康度:丢包/错误统计(UDP 排查最该看的)
netstat -su

ss 里 UDP 的两种状态:

  • UNCONN:未调用 connect() 的 socket,约等于"监听/空闲"
  • ESTAB:调用过 connect() 的 UDP socket

7. TCP vs UDP 排查对照表

TCP 里看什么 UDP 里对应看什么
TIME_WAIT 堆积 没有这个概念
CLOSE_WAIT 泄漏 socket 数量是否只增不减(句柄泄漏)
连接异常 netstat -su 里的 packet receive errors、RcvbufErrors、丢包计数
连接数 Recv-Q 列(netstat 第 2 列)是否长期非 0,说明应用消费不过来、缓冲区积压

8. 一句话总结

TCP 按状态统计用 $NF 没问题;搬到 UDP 上时,把思路换成 按端口统计 + 看 Recv-Q 积压 + netstat -su 看丢包。Recv-Q 积压的通用规律:LISTEN 满 = accept 不过来,ESTAB 涨 = read 不过来。