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

306 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: netstat 统计 TCP/UDP 连接状态
date: 2026-09-14
tags:
- linux
categories:
- linux
---
## 1. 经典命令解析
```bash
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 的写法
```bash
# 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 快几个数量级,是现在的标准工具。
```bash
# 查看所有 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 字节数据躺在内核缓冲区里,应用还没读。
进阶参数:
```bash
# -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(老命令)
```bash
netstat -ant # 所有 TCP
netstat -lnt # 只监听
netstat -antp # 带进程(需要 root)
```
输出列含义与 ss 一致。注意:netstat 逐条遍历 `/proc/net/tcp` 并做解析,大连接数机器上非常慢,线上优先用 `ss`。
### 4.4 查看方式三:直接读 /proc/net 目录
内核把 socket 信息暴露在 procfs 下,不依赖额外工具,适合容器/精简环境或写脚本:
```bash
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 的连接:
```bash
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() 速度跟不上握手完成速度,全连接队列溢出,内核开始丢新连接。
确认丢包计数:
```bash
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)。
```bash
sysctl -w net.core.somaxconn=4096
sysctl -w net.ipv4.tcp_max_syn_backlog=4096
```
**场景二:ESTABLISHED 的 Recv-Q 持续增长**
现象:某些 ESTAB 连接的 Recv-Q 越来越大且不回落。
含义:对端一直在发,本端应用读得太慢或不读(业务卡死、GC、线程池打满)。
定位:
```bash
# 找到是哪个进程
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 接收缓冲区满后报文被**静默丢弃**,可配合计数器确认:
```bash
nstat -az | grep -iE 'udprcvbuferrors|udpinerrors'
```
### 4.6 持续监控写法
```bash
# 每 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 的正确排查姿势
```bash
# 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 不过来**。