10 KiB
title, date, tags, categories
| title | date | tags | categories | ||
|---|---|---|---|---|---|
| netstat 统计 TCP/UDP 连接状态 | 2026-09-14 |
|
|
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-Q511是 backlog 上限(nginx 默认 511),Recv-Q0说明 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 ← 因队列满被丢弃的连接数
处理方向:
- 调大应用 backlog(如 nginx
listen 80 backlog=8192;、Java 的backlog参数); - 调大内核上限:
net.core.somaxconn(默认常见 128/4096)、net.ipv4.tcp_max_syn_backlog(半连接队列); - 治本:提升应用 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 不过来。