Linux服务器如何查看TCP连接状态?ss命令与连接队列详解

服务器连接数上不去、接口偶尔卡顿、怀疑被攻击,这些时候都需要把"当前的 TCP 连接到底处于什么状态"看清楚。Linux 上做这件事最趁手的工具是 ss,它比老一辈的 netstat 快得多,尤其在连接数上万时,netstat 可能直接卡死几十秒,而 ss 秒出结果。

这篇文章分三块内容:第一块讲 ss 的常用用法和 TCP 状态含义,帮你从输出里读懂连接在哪一步卡住;第二块讲连接队列(半连接队列和全连接队列),这是"连接超时""connection reset"类故障的高发区;第三块讲把两者结合起来排查实战,从现象反推根因。内容面向运维新手和中小企业 IT,命令都标注了发行版差异。

想看懂 TCP 连接状态,得先建立"三次握手 + 四次挥手"的心智模型,把状态和握手挥手阶段对应起来。下面先讲状态,再讲队列,最后讲实战,层层递进。

一、ss 命令基础用法

ss 属于 iproute2 包,现代发行版基本都自带。没有的话,RHEL/CentOS/Rocky 用 yum install iproute(或 dnf install iproute),Debian/Ubuntu 用 apt install iproute2。

# 最常用组合:-t TCP,-a 所有(含监听和已建立),-n 显示数字端口,-p 显示进程
ss -tanp

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

# 只看监听端口
ss -lntp

# 统计各状态连接数量(最常用的一眼总览)
ss -s

# 按状态分类统计
ss -tan | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn

ss -tanp 输出的每一列含义:

State      Recv-Q Send-Q  Local Address:Port   Peer Address:Port   Process
LISTEN     0      128     0.0.0.0:80           0.0.0.0:*
ESTAB      0      0       10.0.0.5:80          203.0.113.7:52134   users:(("nginx",pid=1234,fd=6))
  • State:连接状态。
  • Recv-Q / Send-Q:这两个字段含义随状态而变,非常关键,第四节细讲。
  • Local/Peer Address:本地和对端地址端口。
  • Process:只有加 -p 且以 root 运行时才显示。

二、TCP 状态一览与故障指向

TCP 连接的状态有一串,把常见的列出来并给出故障指向:

状态 含义 异常指向
LISTEN 端口在监听 正常
SYN-SENT 已发 SYN,等对方 SYN-ACK 大量说明出向连接不通或被丢弃
SYN-RECV 收到 SYN 并回了 SYN-ACK,等对方 ACK 大量说明半连接队列堆积,疑似 SYN Flood
ESTABLISHED 连接已建立,正常通信 —
FIN-WAIT-1 主动关闭方发出 FIN —
FIN-WAIT-2 收到对方 ACK,等对方 FIN 大量说明对方迟迟不关,应用未正常关闭
TIME-WAIT 连接已关闭,等待 2MSL 大量会占用端口,但属正常机制
CLOSE-WAIT 收到对方 FIN,本端还没关 大量说明应用有 bug,未关闭连接
LAST-ACK 被动关闭方发完 FIN 等 ACK —
CLOSING 双方同时关闭 少见
CLOSED 连接已完全关闭 —

几个必须记住的异常信号:

  • 大量 CLOSE-WAIT:代码里打开了连接却没 close(),是应用层连接泄漏,ss -tanp state close-wait 能直接定位到进程。
  • 大量 TIME-WAIT:高并发短连接业务的正常现象,但如果端口耗尽(本地端口用光)就要处理。
  • 大量 SYN-RECV:半连接队列被打满,可能是 SYN Flood 攻击或客户端握手中断。
  • 大量 FIN-WAIT-2:对端收到 FIN 后不关闭,可能是对端应用僵死或网络问题。
# 分别统计各状态数量
ss -tan | awk 'NR>1{print $1}' | sort | uniq -c
ss -s

# 直接过滤特定状态
ss -tan state close-wait
ss -tan state time-wait | wc -l
ss -tan state syn-recv
ss -tan state established | wc -l

三、连接队列:半连接与全连接

这是排查连接建立类故障的核心知识,必须讲透。TCP 服务端在 listen() 之后,会有两个队列:

半连接队列(SYN 队列):收到客户端 SYN、服务端回了 SYN-ACK 但还没收到最终 ACK 的连接,暂存在这里。队列大小由 net.ipv4.tcp_max_syn_backlog 控制。

全连接队列(accept 队列):三次握手完成、等待应用调用 accept() 取走的连接,放在这里。队列大小取 backlog(listen 系统调用的参数,Nginx 里是 listen 80 backlog=511)和 net.core.somaxconn 两者的较小值。

连接建立过程中的失败点,就对应着这两个队列:

  • 半连接队列满:新来的 SYN 被丢弃,客户端表现为连接超时重试。
  • 全连接队列满:默认行为是丢弃新来的 ACK(或发 RST),客户端表现为"连上了又断"或 connection reset。
# 查看队列相关内核参数
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.ipv4.tcp_abort_on_overflow

# 查看某监听端口的队列情况(注意:ss -lnt 的 Recv-Q/Send-Q 在此含义特殊)
ss -lnt
# 对 LISTEN 状态:Recv-Q 是当前全连接队列长度,Send-Q 是队列上限
# 例如:LISTEN 0 511 ... 表示队列上限 511,当前有 0 个等待 accept

这是 ss -lnt 最容易被人忽略却最有价值的一点:LISTEN 行的 Recv-Q 表示当前等待 accept 的连接数,Send-Q 表示全连接队列的最大长度。如果 Recv-Q 长期接近 Send-Q,说明应用 accept 太慢,全连接队列快满了。用 watch 盯住它:

watch -n 1 'ss -lnt | grep -E "80|443"'

再看握手过程中的网络层表现:

# 看 SYN 相关的统计和丢弃
netstat -s | grep -iE 'listen|syn|overflow|drop'
# 典型输出里关注:
#   SYNs to LISTEN sockets dropped        (半连接队列满丢弃)
#   times the listen queue of a socket overflowed  (全连接队列溢出)
#   listen queue overflows

# 用 nstat 持续观察
nstat -az | grep -iE 'ListenOverflows|ListenDrops'

ListenOverflows 和 ListenDrops 这两个计数器持续增长,就是队列不够用的铁证。

四、Recv-Q / Send-Q 的含义随状态而变

很多人在 ss 上栽过跟头,就是因为 Recv-Q/Send-Q 在不同状态下含义不同:

连接状态 Recv-Q 含义 Send-Q 含义
LISTEN 当前全连接队列长度(等待 accept) 全连接队列上限(backlog)
ESTABLISHED 内核接收缓冲中未被应用读走的字节数 已发但未被对端确认的字节数
SYN-RECV 等 队列相关 队列相关
其他 通常为 0 通常为 0

对 ESTABLISHED 连接:如果 Recv-Q 长期很大,说明应用读得慢(接收缓冲堆积);如果 Send-Q 长期很大,说明对端收得慢或网络拥塞(发送缓冲堆积)。这两个值配合 ss -tn 的 -i 选项(显示内部信息)能挖出更多细节:

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

# 只看某端口、某对端
ss -tnp dst 203.0.113.7
ss -tnp sport = :80
ss -tnp dport = :443

五、实战排查场景

场景一:用户反馈"连接超时,但服务在跑"。

# 1. 看监听和队列
ss -lntp | grep -E ':80|:443'
ss -s
# 2. 看是否有大量 SYN-RECV
ss -tan state syn-recv | wc -l
# 3. 看内核队列丢弃
netstat -s | grep -iE 'overflow|dropped'
# 4. 确认内核参数
sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog

如果 SYN-RECV 多、ListenDrops 在涨,就是队列太小或被攻击。

场景二:服务器挂了大量连接,怀疑连接泄漏。

# 按状态统计,重点看 CLOSE-WAIT / FIN-WAIT-2
ss -tan | awk 'NR>1{print $1}' | sort | uniq -c
# 找出哪个进程持有大量 CLOSE-WAIT
ss -tanp state close-wait | awk '{print $NF}' | sort | uniq -c | sort -rn | head

CLOSE-WAIT 堆积几乎可以断定应用没有正确关闭 socket,要去改代码,重启进程只能临时缓解。

场景三:连接数上不去,端口被占满。

# 统计本机占用最多的本地端口/连接数
ss -s
ss -tan | wc -l
# 看是否 TIME-WAIT 过多
ss -tan state time-wait | wc -l
# 查看可用本地端口范围
sysctl net.ipv4.ip_local_port_range

短连接高并发时,TIME-WAIT 会占用本地端口。可调 ip_local_port_range,开启 tcp_tw_reuse(相对安全),但 tcp_tw_recycle 在现代内核已移除且跨 NAT 有坑,不要用。

场景四:压测时 qps 上不去,怀疑被队列拖累。

用 ss -lnt 盯住 LISTEN 行的 Recv-Q,如果它在压测期间持续大于 0 且逼近 Send-Q,说明握手完成的连接在等服务端 accept。这时不要急着加机器,先确认应用是不是 accept 循环里有阻塞操作(比如 accept 之后同步做了一堆重活)。很多框架把 accept 和处理放在同一个事件循环里,慢处理会反过来拖慢 accept,表现就是全连接队列堆积、新连接被丢。

六、调优参数与风险提示

# 调大全连接队列(必须先改 somaxconn,否则 Nginx backlog 超过它也没用)
# 写入 /etc/sysctl.d/99-net.conf
# net.core.somaxconn = 4096
# net.ipv4.tcp_max_syn_backlog = 8192
# net.ipv4.ip_local_port_range = 10000 65535

sysctl -p /etc/sysctl.d/99-net.conf

# Nginx 侧对应配置(/etc/nginx/nginx.conf)
# events {
#     worker_connections 10240;
# }
# server {
#     listen 80 backlog=4096;   # backlog 不能超过 somaxconn
# }
nginx -t && systemctl reload nginx

风险提示与回滚:

  • 改 sysctl 前先 cp /etc/sysctl.d/99-net.conf /root/99-net.bak(或记录 sysctl -a),出问题还原文件后 sysctl -p。
  • tcp_abort_on_overflow=1 会让队列满时直接发 RST,比默认的静默丢弃更"响亮",但会导致客户端收到 connection reset,生产环境一般保持默认 0。
  • 改 Nginx worker_connections 和 backlog 前先 nginx -t 检查语法,再 reload 而非 restart,避免中断已有连接。
  • 不要把 somaxconn 一次性调到极大(如 65535)就不管,它对应内核内存开销,按业务实际并发调整即可,几千到上万是常见量级(经验值)。

常见问题

Q:ss -s 和 netstat -s 该看哪个?
ss -s 给的是当前连接状态汇总(有多少 ESTAB、多少 TIME-WAIT),快且轻量;netstat -s 给的是协议栈累计统计(重传、队列溢出等)。排查"当前状态"用 ss,排查"历史累计"用 netstat -s 或 nstat。

Q:ss 和 netstat 有什么区别,为什么推荐 ss?
两者功能相近,但 ss 直接读内核 netlink 接口,连接数大时性能远好于 netstat(后者要遍历 /proc)。上万连接时 netstat 可能卡几十秒,ss 基本秒出。新系统应优先用 ss。

Q:LISTEN 那一行的 Recv-Q 不为 0,说明什么?
说明有连接完成了三次握手但还没被应用 accept() 取走,即全连接队列里排着队。少量正常,持续接近 Send-Q(队列上限)就说明应用 accept 太慢,要查应用为什么处理不过来。

Q:TIME-WAIT 很多会把端口占满吗?怎么处理?
会。TIME-WAIT 保留 2MSL(默认 60 秒),短连接高并发会占用大量本地端口。可以调大 ip_local_port_range、开启 net.ipv4.tcp_tw_reuse=1,或者改用长连接/连接池从根上减少 TIME-WAIT 数量。

Q:SYN-RECV 大量堆积一定是被攻击吗?
不一定。也可能是客户端发起握手后不再回应(网络断、客户端崩溃),或者服务器 CPU 忙不过来导致回包慢。先用 ss -tan state syn-recv 看对端地址分布,如果来源大量且分散、短时间内激增,才更像 SYN Flood,可考虑启用 syncookies(net.ipv4.tcp_syncookies=1)。

总结

看 TCP 连接状态首选 ss,常用组合是 ss -tanp(全部)、ss -lntp(监听)、ss -s(汇总)、ss -tin(内部细节),比 netstat 快得多。

TCP 状态要结合握手挥手机制理解,重点关注 CLOSE-WAIT(应用连接泄漏)、SYN-RECV(半连接堆积)、TIME-WAIT(端口占用)这三类异常堆积。

连接建立故障的核心是连接队列:LISTEN 行的 Recv-Q 是全连接队列当前长度、Send-Q 是上限,ListenOverflows/ListenDrops 持续增长说明队列不够用。

队列调优要成对改:net.core.somaxconn(全连接)和 net.ipv4.tcp_max_syn_backlog(半连接),且 Nginx 的 backlog 不能超过 somaxconn,改完 nginx -t 再 reload。

所有内核参数和配置变更前先备份、评估内存开销、准备回滚;tcp_tw_recycle 不要用,tcp_abort_on_overflow 生产环境保持默认。