TCP三次握手和四次挥手的基础知识

描述

前言

TCP 三次握手和四次挥手是网络基础知识中的基础,面试时能背出来的人很多,但真正遇到生产环境问题需要排查时,很多人就傻眼了。比如:连接建立后客户端马上断开是怎么回事?为什么 TIME_WAIT 状态的连接这么多?服务端为什么会产生大量 CLOSE_WAIT?RST 报文什么时候会出现?

这些问题靠背答案是无济于事的,必须理解 TCP 状态转换的每个细节、以及内核参数对 TCP 行为的影响,才能在实际场景中快速定位问题。

本文从实际运维角度出发,详细解析 TCP 状态转换、握手挥手的每个阶段、内核参数的影响、以及常见问题的排查方法。

1 TCP 协议基础回顾

1.1 TCP 头部结构

理解 TCP 状态机之前,先看一下 TCP 头部的关键字段:

 

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Source Port          |       Destination Port        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        Sequence Number                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Acknowledgment Number                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Offset|  Reserved |Flags|      Window                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Checksum            |         Urgent Pointer         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options                    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                             data                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 

关键字段说明:

Sequence Number(序列号):本报文的第一个数据字节的序号。用于保证数据按序到达。

Acknowledgment Number(确认号):期望收到的下一个字节的序号。表示确认收到对方的数据。

Flags(标志位):SYN、ACK、FIN、RST、PSH、URG

Window(窗口大小):接收端能接收的数据量,用于流量控制

1.2 TCP 状态一览

 

客户端状态:                                   服务端状态:
CLOSED                                         LISTEN
   |                                              |
   |  ---- SYN ---->                             |
   |                  <---- SYN-ACK ----          |
   |  <---- ACK ---->                           |
SYN_SENT                                       SYN_RCVD
   |                                              |
   |                                              |
   |============== 数据传输 ==============>
   |                                              |
   |  ---- FIN ---->                             |
   |  <---- ACK ----                             |
FIN_WAIT_1                                      CLOSE_WAIT
   |                                              |
   |  <---- ACK ----                             |
FIN_WAIT_2                                      LAST_ACK
   |                                              |
   |  <---- FIN ----                             |
   |  ---- ACK ---->                            |
  TIME_WAIT                                    (消失)
   |                                              |
   |  等待 2MSL                                  |
   |                                              |
CLOSED                                         CLOSED

 

各状态详细说明:

状态 含义 常见场景
CLOSED 连接关闭 初始状态或连接完全关闭
LISTEN 监听中 服务端等待连接请求
SYN_SENT 已发送 SYN 客户端已发起连接请求
SYN_RCVD 已收到 SYN 服务端已收到并回复
ESTABLISHED 已建立 正常数据传输状态
FIN_WAIT_1 已发送 FIN 主动关闭方等待对方 ACK
FIN_WAIT_2 已收到 ACK 主动关闭方等待对方 FIN
CLOSE_WAIT 等待关闭 被动关闭方等待应用关闭
CLOSING 双方同时关闭 双方同时发送 FIN
LAST_ACK 最后确认 被动关闭方等待最后 ACK
TIME_WAIT 等待超时 主动关闭方等待 2MSL

2 三次握手详解

2.1 握手过程

 

客户端                              服务端
    |                                  |
    |  --------- SYN=1, Seq=x -------->  |  第一次握手
    |                                  |
    |  <------ SYN=1, ACK=1, Seq=y ---  |  第二次握手
    |            Ack=x+1                |
    |                                  |
    |  ------- ACK=1, Seq=x+1 ------>  |  第三次握手
    |            Ack=y+1                |
    |                                  |
    |========= ESTABLISHED ===========|
    |                                  |

 

第一次握手:

客户端发送 TCP 报文,SYN=1,Seq=x

SYN=1 表示这是一个连接请求

Seq=x 是初始序列号(ISN),随机生成

发送后客户端进入 SYN_SENT 状态

第二次握手:

服务端收到请求后,回复 SYN+ACK

SYN=1, ACK=1

Seq=y 是服务端的初始序列号

Ack=x+1 表示期望收到的下一个字节是 x+1

服务端进入 SYN_RCVD 状态

第三次握手:

客户端收到服务端的 SYN+ACK 后,发送 ACK

ACK=1, Seq=x+1, Ack=y+1

此时客户端进入 ESTABLISHED 状态

服务端收到 ACK 后也进入 ESTABLISHED 状态

2.2 为什么是三次?

核心原因:TCP 是全双工协议,需要双向确认。

两次握手的问题:

假设客户端发送了第一个 SYN 请求,这个请求在网络中滞留了。客户端等不到响应,又发送了一个 SYN 并完成连接,然后断开。此时第一个延迟的 SYN 到达服务端,如果只有两次握手,服务端会误以为客户端又要建立连接,从而建立无效连接浪费资源。

三次握手时,服务端需要在收到客户端的第三次 ACK 后才真正建立连接,这样延迟的 SYN 不会被当作有效连接请求。

四次握手可以吗?

可以,但没必要。三次已经足够建立可靠连接,四次只是浪费网络资源。第二次握手的 SYN+ACK 可以合并成一次报文发送,所以天然就是三次。

2.3 ISN 为什么要随机?

如果 ISN 是固定的(比如每次都从 0 开始),攻击者就可以伪造 IP 和序列号建立连接。随机 ISN 使得攻击者难以预测下一个连接的序列号,从而防止 TCP Sequence Prediction 攻击。

查看当前连接 ISN:

 

# 查看当前 TCP 连接的序列号
cat /proc/net/tcp
cat /proc/net/tcp6

# 使用 ss 查看详细信息
ss -ti state established

# 使用 netstat 查看
netstat -tn | grep ESTABLISHED

 

2.4 MSS 与窗口缩放

MSS(Maximum Segment Size):最大报文段大小,指 TCP 载荷的最大字节数,不包含 TCP 头部。

 

MSS = MTU - IP头部 - TCP头部
典型值:MTU=1500, MSS=1460

 

窗口缩放(Window Scaling):当接收窗口大于 65535 字节时,需要使用窗口缩放因子。RFC 1323 定义了这个扩展。

 

# 查看窗口缩放因子
cat /proc/sys/net/ipv4/tcp_window_scaling

# 查看缓冲设置
cat /proc/sys/net/ipv4/rmem
cat /proc/sys/net/ipv4/wmem

# 临时调整窗口缩放因子
sysctl -w net.ipv4.tcp_window_scaling=1

 

3 四次挥手详解

3.1 挥手过程

 

客户端                              服务端
    |                                  |
    |========= ESTABLISHED ===========|
    |                                  |
    |  ------ FIN=1, Seq=u -------->  |  第一次挥手
    |  <----- ACK=1, Ack=u+1 --------  |
FIN_WAIT_1                            CLOSE_WAIT
    |                                  |
    |  <---- ACK=1 -----------------  |
FIN_WAIT_2                            |
    |                                  |
    |  <---- FIN=1, Seq=v -----------  |  第二次挥手
    |  ------ ACK=1, Ack=v+1 ------>  |  (此时服务端可以继续发送数据)
TIME_WAIT                           LAST_ACK
    |                                  |
    |  等待 2MSL                       |
    |  (确保对方收到最后的 ACK)         |
    |                                  |
CLOSED                               CLOSED

 

第一次挥手:主动关闭方发送 FIN,进入 FIN_WAIT_1 状态

第二次挥手:被动关闭方收到 FIN 后回复 ACK,进入 CLOSE_WAIT 状态。被动关闭方的应用可能还需要发送剩余数据。

第三次挥手:被动关闭方完成数据发送后,发送 FIN,进入 LAST_ACK 状态

第四次挥手:主动关闭方收到 FIN 后回复 ACK,进入 TIME_WAIT 状态

3.2 TIME_WAIT 状态详解

TIME_WAIT 状态持续时间:2MSL(Maximum Segment Lifetime)

MSL 是 TCP 报文在网络中存在的最大时间,通常是 60 秒(在 Linux 中可以通过 net.ipv4.tcp_fin_timeout 调整,实际最大值是 60 秒)。

所以 TIME_WAIT 状态持续 120 秒(某些系统可能是 60 秒或 30 秒,取决于实现)。

TIME_WAIT 的两个作用:

确保最后的 ACK 能到达被动关闭方:如果第四次挥手的 ACK 丢失,被动关闭方会重发 FIN,主动关闭方在 TIME_WAIT 状态下能响应这个重发的 FIN。

让旧连接的报文在网络中消散:在 2MSL 时间内,同一连接的旧报文会从网络中消失,新连接不会被旧报文干扰。

Linux 内核参数:

 

# 查看 TIME_WAIT 超时时间(秒)
cat /proc/sys/net/ipv4/tcp_fin_timeout
# 默认:60

# 查看当前 TIME_WAIT 连接数
ss -tan state time-wait | wc -l
netstat -an | grep TIME_WAIT | wc -l

# 查看各状态的连接数
ss -s

 

TIME_WAIT 过多的处理方法:

检查是否为正常现象:高并发短连接服务产生大量 TIME_WAIT 是正常的

启用 tcp_tw_reuse:

 

# 允许将 TIME_WAIT 状态的 socket 重用于新连接
cat /proc/sys/net/ipv4/tcp_tw_reuse
# 默认:0,生产环境建议设置为 1

# 临时开启
sysctl -w net.ipv4.tcp_tw_reuse=1

# 永久生效(写入 /etc/sysctl.conf)
echo "net.ipv4.tcp_tw_reuse = 1" >> /etc/sysctl.conf
sysctl -p

 

调整 tcp_max_tw_buckets:

 

# TIME_WAIT 状态连接数的上限
cat /proc/sys/net/ipv4/tcp_max_tw_buckets
# 默认:262144

# 超出会直接丢弃而不是放入 TIME_WAIT
sysctl -w net.ipv4.tcp_max_tw_buckets=100000

 

启用 tcp_timestamps:

 

# tcp_tw_reuse 需要 tcp_timestamps 启用才能工作
cat /proc/sys/net/ipv4/tcp_timestamps
# 默认:1

# 确保开启
sysctl -w net.ipv4.tcp_timestamps=1

 

使用 SO_LINGER 强制关闭(慎用):

 

# 结构体
struct linger {
    int l_onoff;    /* non-zero to linger */
    int l_linger;   /* linger time */
};

# 设置 linger 为 0,等价于 close() 时发送 RST 而不是走完四次挥手
setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));

 

3.3 CLOSE_WAIT 状态详解

CLOSE_WAIT 过多的原因:应用程序没有正确调用 close() 或 shutdown()

 

# 查看 CLOSE_WAIT 连接
ss -tan state close-wait

# 查看各状态的详细统计
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn

# 查看哪些进程有 CLOSE_WAIT 连接
ss -tup

 

常见原因:

代码问题:HTTP 长连接没有设置超时,客户端断开后服务端没有及时关闭 socket

连接池问题:连接池配置不当,连接用完没有归还

异常处理不当:异常发生时跳过了 close() 调用

排查思路:

 

# 1. 查看 CLOSE_WAIT 的详细信息
ss -tup state close-wait

# 2. 查看进程打开的文件描述符
lsof -p 

# 3. 查看应用的连接配置
# 检查连接池大小、超时设置、close() 是否被正确调用

# 4. 如果是 Java 应用,查看堆外内存和连接状态
jstack 
# 查看线程状态和锁等待

 

3.4 RST 报文

RST(Reset) 是一种特殊标志,表示连接异常终止,不需要对方确认。

发送 RST 的情况:

目标端口没有监听:服务器收到 SYN 但端口没有进程监听,回复 RST

 

# 模拟:连接一个未监听的端口
nc -zv 127.0.0.1 9999
# Connection refused,说明收到了 RST

 

SO_LINGER 设置为 0:调用 close() 时直接发送 RST

收到无法识别的报文:序列号不在接收窗口内

应用程序异常终止:进程被 kill,内核会发送 FIN,但如果发送缓冲区还有数据,可能会发送 RST

查看 RST 报文:

 

# 抓取 RST 报文
tcpdump -i eth0 'tcp[tcpflags] == tcp-rst'

# 统计 RST 数量
netstat -s | grep -i "reset"

# 查看 TCP 错误统计
cat /proc/net/netstat | grep TcpExt
ss -s

 

4 tcpdump 抓包分析

4.1 基本抓包

 

# 抓取所有 TCP 流量
tcpdump -i eth0 tcp

# 抓取特定端口的流量
tcpdump -i eth0 port 80
tcpdump -i eth0 port 3306

# 抓取特定主机的流量
tcpdump -i eth0 host 192.168.1.100

# 抓取特定端口和主机的组合
tcpdump -i eth0 port 80 and host 192.168.1.100

# 不解析 IP,反显示原始 IP
tcpdump -i eth0 -n

# 显示完整包内容(十六进制)
tcpdump -i eth0 -X

# 显示时间戳
tcpdump -i eth0 -tttt

# 抓取指定数量的包后退出
tcpdump -i eth0 -c 100

# 保存到文件
tcpdump -i eth0 -w capture.pcap

# 读取保存的抓包文件
tcpdump -r capture.pcap

# 读取时过滤
tcpdump -r capture.pcap 'tcp[13] & 2 != 0'

 

4.2 TCP 标志位过滤

TCP 头部的第 13 个字节包含 8 个标志位:

 

|FIN|SYN|RST|PSH|ACK|URG|ECE|CWR|
  7   6   5   4   3   2   1   0
# 抓取 SYN 包
tcpdump -i eth0 'tcp[13] & 2 != 0'

# 抓取 SYN-ACK 包
tcpdump -i eth0 'tcp[13] = 18'

# 抓取 FIN 包
tcpdump -i eth0 'tcp[13] & 1 != 0'

# 抓取 RST 包
tcpdump -i eth0 'tcp[13] & 4 != 0'

# 抓取 ACK 包(排除 SYN)
tcpdump -i eth0 'tcp[13] & 16 != 0'

# 抓取所有握手和挥手包
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0'

 

4.3 三次握手抓包示例

 

# 在服务端抓包
tcpdump -i eth0 -nn 'port 8080' -w handshake.pcap

# 或者实时显示
tcpdump -i eth0 -nn -tttt 'port 8080' | grep -E "(SYN|ACK|FIN)"

 

正常三次握手输出:

 

1023.456789 IP 192.168.1.10.45678 > 192.168.1.20.8080: Flags [S], seq 1000, win 65535, options [mss 1460], length 0
1023.456792 IP 192.168.1.20.8080 > 192.168.1.10.45678: Flags [S.], seq 2000, ack 1001, win 65535, options [mss 1460], length 0
1023.457001 IP 192.168.1.10.45678 > 192.168.1.20.8080: Flags [.], ack 2001, win 65535, length 0

 

flags 说明:

[S]:SYN

[S.]:SYN-ACK(. 表示 ACK)

[.]:纯 ACK

[F]:FIN

[R.]:RST-ACK

4.4 四次挥手抓包示例

 

# 抓取挥手过程
tcpdump -i eth0 -nn 'port 8080 and (tcp[tcpflags] & (tcp-fin|tcp-syn|tcp-rst) != 0)'

 

正常四次挥手输出:

 

1001.123456 IP 192.168.1.10.45678 > 192.168.1.20.8080: Flags [F.], seq 5000, ack 8000, win 65535, length 0
1001.123500 IP 192.168.1.20.8080 > 192.168.1.10.45678: Flags [.], ack 5001, win 65535, length 0
1001.234567 IP 192.168.1.20.8080 > 192.168.1.10.45678: Flags [F.], seq 8000, ack 5001, win 65535, length 0
1001.234600 IP 192.168.1.10.45678 > 192.168.1.20.8080: Flags [.], ack 8001, win 65535, length 0

 

5 TCP 半连接与全连接队列

5.1 半连接队列(SYN Queue)

当服务端收到客户端的 SYN 后,进入 SYN_RCVD 状态,此时连接放在半连接队列中。

相关内核参数:

 

# 半连接队列最大长度
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
# 默认:128(实际效果受 somaxconn 限制)

# 查看当前半连接队列使用情况
ss -ltn state syn-recv

 

查看 SYN Flood 攻击:

 

# 查看是否有 SYN 攻击
netstat -s | grep -i "SYN"
ss -ltn state syn-recv | wc -l

# 如果半连接队列满,新连接会被丢弃
# 查看丢弃计数
cat /proc/net/netstat | grep -i "SYN"

 

5.2 全连接队列(Accept Queue)

当三次握手完成后,连接从半连接队列移到全连接队列,等待应用调用 accept()。

相关内核参数:

 

# 全连接队列最大长度(对应 listen backlog)
cat /proc/sys/net/core/somaxconn
# 默认:128

# 查看当前全连接队列使用情况
ss -ltn state listen

# 观察 Recv-Q 和 Send-Q
ss -ltn
# Recv-Q:全连接队列当前使用数
# Send-Q:全连接队列最大长度(已接受但未 accept 的连接)

 

全连接队列溢出的表现:

 

# 查看溢出统计
netstat -s | grep -i "overflow"

# 查看 accept 延迟
ss -ti state established | grep -i "delay"

# 抓包观察是否有连接建立后立即 RST
tcpdump -i eth0 'tcp[tcpflags] & tcp-rst != 0'

 

5.3 队列大小配置建议

 

# Nginx 配置(nginx.conf)
server {
    listen 8080 backlog=65535;
}

# Golang 配置
ln, _ := net.Listen("tcp", ":8080")
// listen backlog 默认是系统 somaxconn

# Java 配置(Tomcat)


# Python 配置
import socket
s = socket.socket()
s.listen(2048)  # backlog 参数

 

生产环境建议:

tcp_max_syn_backlog:至少 2048

somaxconn:至少 2048

应用 listen backlog:至少 1024

 

# 调整参数
sysctl -w net.ipv4.tcp_max_syn_backlog=2048
sysctl -w net.core.somaxconn=2048

 

6 生产环境常见问题与排查

6.1 连接建立后立即断开

现象:客户端发起连接后,收到服务端的 RST。

可能原因:

服务端端口没有监听

服务端队列已满,来不及 accept

防火墙拦截

服务端进程崩溃

排查步骤:

 

# 1. 检查端口是否监听
ss -tlnp | grep 8080

# 2. 检查防火墙规则
iptables -L -n
firewall-cmd --list-all

# 3. 检查 TCP 状态
ss -ti state established

# 4. 抓包分析
tcpdump -i eth0 port 8080 -nn

# 5. 检查进程状态
systemctl status nginx
ps aux | grep nginx

 

6.2 服务端产生大量 CLOSE_WAIT

现象:很多连接处于 CLOSE_WAIT 状态。

排查步骤:

 

# 1. 查看 CLOSE_WAIT 详情
ss -tup state close-wait

# 2. 查看对应的进程
ss -tup | grep CLOSE_WAIT

# 3. 检查进程的线程/连接处理
# Java: jstack
# Python: strace -p
# Go: runtime/pprof

# 4. 检查连接超时配置
# 确保客户端断开后服务端能检测到
# 检查 keepalive 配置

 

解决方案:

检查应用代码,确保正确调用 close()

设置合理的 SO_TIMEOUT

启用 TCP keepalive

使用连接池并正确配置归还逻辑

6.3 TIME_WAIT 连接过多

现象:大量连接处于 TIME_WAIT 状态。

排查步骤:

 

# 1. 查看各状态的数量
ss -s

# 2. 查看 TIME_WAIT 详情
ss -tan state time-wait

# 3. 查看连接来源
ss -tan state time-wait | awk '{print $4}' | cut -d: -f1 | sort | uniq -c | sort -rn

 

解决方案:

 

# 1. 启用 tcp_tw_reuse
sysctl -w net.ipv4.tcp_tw_reuse=1

# 2. 调整 FIN 超时
sysctl -w net.ipv4.tcp_fin_timeout=30

# 3. 增加 tcp_max_tw_buckets
sysctl -w net.ipv4.tcp_max_tw_buckets=100000

# 4. 如果是 HTTP 服务,考虑使用 HTTP keep-alive
# 或者升级到 HTTP/2、WebSocket

# 5. 客户端使用连接池复用连接

 

6.4 端口被占满

现象:无法建立新连接,报错 "Cannot assign requested address" 或 "Address already in use"。

原因:客户端作为连接发起方,每次连接会占用一个临时端口(通常是 32768-60999)。

 

# 查看可用端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
# 默认:32768 60999

# 查看已使用的连接数
ss -tan | awk '{print $4}' | grep -E ":[0-9]+$" | wc -l

# 查看每个 IP 的连接数
ss -tan | awk '{print $4}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20

 

解决方案:

 

# 1. 扩大端口范围
sysctl -w net.ipv4.ip_local_port_range="1024 65535"

# 2. 启用 TIME_WAIT 复用
sysctl -w net.ipv4.tcp_tw_reuse=1

# 3. 减少 TIME_WAIT 超时
sysctl -w net.ipv4.tcp_fin_timeout=15

# 4. 客户端使用长连接而不是短连接

 

6.5 半打开连接(Half-Open)

现象:一方崩溃后,另一方不知道连接已失效。

原因:没有心跳机制检测对端存活。

解决方案:启用 TCP Keepalive

 

# 查看当前 keepalive 设置
cat /proc/sys/net/ipv4/tcp_keepalive_time
# 默认:7200 秒(2小时)

# 查看 keepalive 探针参数
cat /proc/sys/net/ipv4/tcp_keepalive_intvl
# 默认:75 秒

cat /proc/sys/net/ipv4/tcp_keepalive_probes
# 默认:9 次

# 调整 keepalive 参数
sysctl -w net.ipv4.tcp_keepalive_time=600      # 10分钟开始探测
sysctl -w net.ipv4.tcp_keepalive_intvl=30     # 每30秒探测一次
sysctl -w net.ipv4.tcp_keepalive_probes=5     # 探测5次失败后断开

# 永久生效
cat >> /etc/sysctl.conf <

 

应用层也可以实现心跳:

 

# Python heartbeat 示例
import socket
import time

def heartbeat(sock, interval=30):
    while True:
        try:
            sock.send(b'PING')
            sock.recv(1024)
        except:
            # 连接已断开
            reconnect()
        time.sleep(interval)

 

7 连接状态监控脚本

7.1 监控 TCP 连接状态统计

 

#!/bin/bash
# filename: tcp_status_monitor.sh
# 监控 TCP 连接状态,发现异常及时告警

# 输出统计
echo "=== TCP 连接状态统计 ==="
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn

echo ""
echo "=== TIME_WAIT 连接数 ==="
timewait_count=$(ss -tan state time-wait | wc -l)
echo "当前 TIME_WAIT: $timewait_count"
if [ $timewait_count -gt 50000 ]; then
    echo "警告: TIME_WAIT 连接数过高"
fi

echo ""
echo "=== CLOSE_WAIT 连接数 ==="
closewait_count=$(ss -tan state close-wait | wc -l)
echo "当前 CLOSE_WAIT: $closewait_count"
if [ $closewait_count -gt 1000 ]; then
    echo "警告: CLOSE_WAIT 连接数异常"
fi

echo ""
echo "=== SYN_RECVD 连接数(半连接) ==="
synrecv_count=$(ss -tan state syn-recv | wc -l)
echo "当前 SYN_RECVD: $synrecv_count"
if [ $synrecv_count -gt 1000 ]; then
    echo "警告: SYN 队列可能正在被攻击"
fi

echo ""
echo "=== Established 连接 TOP 10 来源 IP ==="
ss -tan state established | awk '{print $4}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -10

echo ""
echo "=== 最后一次抓取的 TCP 错误统计 ==="
cat /proc/net/netstat | grep TcpExt | tail -1

 

7.2 监控端口连接数

 

#!/bin/bash
# filename: port_conn_monitor.sh
# 监控指定端口的连接数

PORT=${1:-80}
THRESHOLD=${2:-1000}

echo "=== 端口 $PORT 连接状态统计 ==="
ss -tunlp | grep ":$PORT " | head -5

echo ""
echo "=== 端口 $PORT Established 连接数 ==="
est_count=$(ss -tan "sport = :$PORT or dport = :$PORT" state established | wc -l)
echo "当前Established: $est_count"

if [ $est_count -gt $THRESHOLD ]; then
    echo "警告: 连接数超过阈值 $THRESHOLD"
fi

echo ""
echo "=== 端口 $PORT 各状态统计 ==="
ss -tan "sport = :$PORT or dport = :$PORT" | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn

 

7.3 抓包分析连接问题

 

#!/bin/bash
# filename: tcp_debug.sh
# 抓包分析 TCP 连接问题

OUTPUT_DIR="/tmp/tcpdump"
mkdir -p $OUTPUT_DIR
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

# 抓取指定端口的完整流量
echo "开始抓包,保存到 $OUTPUT_DIR/tcp_${TIMESTAMP}.pcap"
timeout 60 tcpdump -i eth0 -w "$OUTPUT_DIR/tcp_${TIMESTAMP}.pcap" port 8080 2>/dev/null &

# 抓包过程中检查状态
sleep 10
echo "=== 10秒后状态 ==="
ss -tunlp | grep 8080

sleep 10
echo "=== 20秒后状态 ==="
ss -tunlp | grep 8080

# 分析抓包文件
wait
echo ""
echo "=== 握手统计 ==="
tcpdump -r "$OUTPUT_DIR/tcp_${TIMESTAMP}.pcap" 2>/dev/null | grep -E "(SYN|ACK|FIN|RST)" | wc -l

echo ""
echo "=== 异常统计 ==="
tcpdump -r "$OUTPUT_DIR/tcp_${TIMESTAMP}.pcap" 2>/dev/null | grep -c "RST"
echo " RST 包数量"

 

8 总结

8.1 核心要点回顾

三次握手:

第一次:客户端发送 SYN,进入 SYN_SENT

第二次:服务端回复 SYN+ACK,进入 SYN_RCVD

第三次:客户端回复 ACK,双方进入 ESTABLISHED

三次是确保双向确认的最小次数

四次挥手:

主动关闭方发送 FIN,进入 FIN_WAIT_1

被动关闭方回复 ACK,进入 CLOSE_WAIT

被动关闭方发送 FIN,进入 LAST_ACK

主动关闭方回复 ACK,进入 TIME_WAIT

TIME_WAIT 等待 2MSL 确保双方都收到最后的 ACK

关键状态:

TIME_WAIT:主动关闭方的最后等待,防止旧报文干扰新连接

CLOSE_WAIT:被动关闭方等待应用关闭连接

SYN_RCVD:服务端等待第三次握手

半连接队列:存放未完成握手的连接

全连接队列:存放已完成握手等待 accept 的连接

8.2 常用排查命令

 

# 查看连接状态统计
ss -s

# 查看各状态连接详情
ss -tan state {state}

# 查看监听端口
ss -tlnp

# 抓包分析
tcpdump -i eth0 'tcp[tcpflags] & (tcp-syn|tcp-fin|tcp-rst) != 0'

# 查看内核参数
cat /proc/sys/net/ipv4/tcp_*
cat /proc/sys/net/core/somaxconn

# 查看 TCP 错误统计
cat /proc/net/netstat | grep TcpExt

 

8.3 常见问题处理

问题 可能原因 处理方法
连接后立即 RST 端口未监听、队列满、防火墙 检查监听状态、队列配置、防火墙
大量 CLOSE_WAIT 应用未调用 close() 检查代码逻辑、设置超时
大量 TIME_WAIT 短连接过多 启用 tcp_tw_reuse、长连接
端口耗尽 短连接过多、端口范围小 扩大端口范围、连接池复用
半连接队列满 SYN Flood 攻击 增加 tcp_max_syn_backlog
全连接队列满 应用 accept 太慢 优化应用、增加队列大小

8.4 内核参数调优建议

 

# /etc/sysctl.conf 添加以下内容

# TCP 时间等待复用
net.ipv4.tcp_tw_reuse = 1

# FIN 超时时间
net.ipv4.tcp_fin_timeout = 30

# TIME_WAIT 上限
net.ipv4.tcp_max_tw_buckets = 100000

# 半连接队列大小
net.ipv4.tcp_max_syn_backlog = 2048

# 全连接队列大小
net.core.somaxconn = 2048

# 端口范围
net.ipv4.ip_local_port_range = 1024 65535

# Keepalive
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5

# TCP timestamps(tw_reuse 需要)
net.ipv4.tcp_timestamps = 1

# 生效配置
sysctl -p

 

理解 TCP 状态转换是网络排查的基本功。面试能背答案不重要,真正遇到生产环境问题时能快速定位根因才是本事。建议在实际环境中多抓包、多分析、多调试,把理论知识和实际操作结合起来。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分