0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

怎么理解TCP三次握手和四次挥手

马哥Linux运维 来源:马哥Linux运维 2026-04-10 16:42 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

引言:TCP 是运维的基石

作为运维工程师,无论是排查网络故障、分析日志,还是配置负载均衡器,都需要对 TCP 协议有深入理解。很多"疑难杂症"的根源,往往在于对 TCP 状态转换和连接管理理解不够透彻。

本文从 TCP 协议头部开始,详细讲解三次握手和四次挥手的每个细节,配合 Wireshark 抓包分析,帮助初中级运维工程师建立完整的 TCP 知识体系。

前置知识:OSI 七层模型基础、IP 基础概念

实验环境Linux 系统、Wireshark、tcpdump

1 TCP 协议头部解析

1.1 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           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data |    |C|E|U|A|P|R|S|F|                |
| Offset| Flags |W|C|R|C|S|S|Y|I|      Window       |
|    |    |R|E|G|K|H|T|N|N|                |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      Checksum      |     Urgent Pointer    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Options and Padding            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|               Data               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

1.2 各字段详解

# 使用 tcpdump 查看 TCP 头部
# 捕获 SYN 包
sudo tcpdump -i eth0'tcp[tcpflags] == tcp-syn'-c 5 -v

# 输出解析
# IP (tos 0x0, ttl 64, id 0, proto TCP (6), length 60)
#   192.168.1.100.45678 > 93.184.216.34.80: Flags [S], seq 0:0, win 65535
#            ^^^^ 源端口     ^^^^ 目标端口 ^^^^ SYN 标志

关键字段说明

字段 长度 作用
Source Port 16bit 客户端随机选择的端口
Destination Port 16bit 服务端监听端口
Sequence Number 32bit 数据字节流编号
Acknowledgment Number 32bit 期望收到的下一个字节编号
Data Offset 4bit TCP 头部长度(4字节为单位)
Flags 9bit 控制标志
Window 16bit 滑动窗口大小
Checksum 16bit 头部+数据校验和
Urgent Pointer 16bit 紧急数据指针

1.3 TCP 标志位详解

# TCP 标志位(9个)
# - URG: 紧急指针有效
# - ACK: 确认号有效
# - PSH: 推送数据给应用层
# - RST: 重置连接
# - SYN: 同步序号(建立连接)
# - FIN: 结束连接
# - ECE: ECN 显式拥塞通知
# - CWR: 拥塞窗口减小
# - NS: 保留(Nonce Sum)

# tcpdump 显示标志位组合
# [S]   = SYN
# [S.]   = SYN + ACK
# [.]   = ACK
# [F]   = FIN
# [F.]   = FIN + ACK
# [R]   = RST
# [R.]   = RST + ACK
# [P.]   = PSH + ACK
# [S.] [.] [R] = 三次握手序列

# 捕获所有带 RST 标志的包
sudo tcpdump -i eth0'tcp[tcpflags] & tcp-rst != 0'-n

# 捕获带 PSH+ACK 的数据包
sudo tcpdump -i eth0'tcp[tcpflags] == tcp-ack and tcp[tcpflags] & tcp-psh != 0'-n

1.4 序列号与确认号机制

# TCP 序列号计算示例
classTCPSeqAck:
 def__init__(self, isn):
   # ISN: Initial Sequence Number,初始序列号
    self.isn = isn
    self.next_expected = isn +1

 defsend_data(self, data):
   """发送数据,返回序列号"""
    seq = self.next_expected
    self.next_expected += len(data)
   returnseq

 defreceive_ack(self, ack):
   """收到 ACK,计算确认号"""
   ifack > self.next_expected:
     # 确认号大于期望,可能是重复 ACK 或数据丢失
     return"ACK is larger than expected"
   elifack == self.next_expected:
     return"Full ACK - all data received"
   else:
     return"Partial ACK"

# 示例
tcp = TCPSeqAck(isn=1000)
seq1 = tcp.send_data(b"Hello") # seq=1000, 发送 "Hello" (5 bytes)
print(f"发送数据,序列号:{seq1}, 下一个期望:{tcp.next_expected}")

# 对方返回 ACK
result = tcp.receive_ack(1005) # 确认号 1005
print(f"收到 ACK 1005:{result}")

2 三次握手深度解析

2.1 为什么需要三次握手?

客户端                  服务端
 |                    |
 | 问题:客户端发出的 SYN 可能在网络中滞留  |
 |                    |
 | 滞留的旧 SYN 到达服务端         |
 | --> 服务端 认为是新连接请求       |
 | --> 服务端 分配资源等待客户端响应    |
 | --> 但客户端 早已关闭连接        |
 |                    |
 | 解决方案:第三次握手让客户端确认     |
 | --> 客户端 收到 SYN+ACK        |
 | --> 客户端 检查序列号是否是自己发出的  |
 | --> 确认后才发送 ACK          |
 |                    |
 | 如果是旧 SYN,客户端会发送 RST     |
 |                    |

三次握手核心目的

验证双方发送和接收能力正常

协商初始序列号(ISN)

防止旧连接请求干扰新连接

2.2 三次握手详细过程

时间线      客户端               服务端
 |                    |
 | 1. CLOSED              | 1. LISTEN
 |                    |
 | --> 选择客户端 ISN (c_isn)      |
 | --> 发送 SYN            |
T1 |------------------------------------>|
 |   SEQ=c_isn            |
 |   Flags=[SYN]           |
 |   状态: SYN_SENT          |
 |                    |
 |             2. 收到 SYN |
 |             --> 选择服务端 ISN (s_isn)
 |             --> 发送 SYN+ACK
T2 |<------------------------------------|
   |     SEQ=s_isn                        |
   |     ACK=c_isn+1                      |
   |     Flags=[SYN,ACK]                  |
   |     状态: SYN_RECEIVED                |
   |                                       |
   |  3. 收到 SYN+ACK                      |
   |  --> 验证 ACK 是否正确         |
 | --> 发送 ACK            |
T3 |------------------------------------>|
 |   SEQ=c_isn+1           |
 |   ACK=s_isn+1           |
 |   Flags=[ACK]           |
 |   状态: ESTABLISHED         |
 |                    |
 |             4. 收到 ACK |
 |             --> 验证 ACK |
 |             --> 状态: ESTABLISHED
 |                    |
 | 双向通信建立完成           |
 |                    |

2.3 Wireshark 三次握手实战

# 捕获 HTTP 握手包
sudo tcpdump -i eth0'tcp port 80 and tcp[tcpflags] & tcp-syn != 0'-w /tmp/handshake.pcap

# 在另一个终端发起请求
curl -I http://example.com

# 停止捕获,用 Wireshark 分析
wireshark /tmp/handshake.pcap &
# 或用 tshark 命令行分析
tshark -r /tmp/handshake.pcap -Y"tcp.flags.syn==1 or tcp.flags.synack==1 or tcp.flags.ack==1"

Wireshark 抓包结果解析

Frame 1: 62 bytes on wire, 62 bytes captured
Ethernet, Src: VMware_xxxx, Dst: Intel_xxxx
Internet Protocol Version 4, Src: 192.168.1.100, Dst: 93.184.216.34
Transmission Control Protocol, Src Port: 45678, Dst Port: 80
  Source Port: 45678
  Destination Port: 80
  [Stream index: 0]
  Sequence number: 0  (relative sequence number)
  Acknowledgment number: 0
  0110 .... = Header Length: 32 bytes (8)
  Flags: 0x002 (SYN)
  Window size value: 65535
  Checksum: 0xabcd [unverified]
  Options: (12 bytes)
    Maximum segment size: 1460 bytes
    WS: 7
    No-Operation (NOP)
    No-Operation (NOP)
    Timestamps: TSval 1234567890, TSecr 0
    No-Operation (NOP)
    No-Operation (NOP)
    SackOK: sack permits

Frame 2: 62 bytes
TCP Flags: 0x012 (SYN, ACK)
Sequence number: 0 (relative sequence number)
Acknowledgment number: 1 (relative ack number)
Options: (12 bytes)
  Maximum segment size: 1460 bytes
  Timestamps: TSval 987654321, TSecr 1234567890
  ...

2.4 ISN 随机化与安全性

# ISN (Initial Sequence Number) 随机化原理
# RFC 793 定义:ISN = M + F(localip, localport, remoteip, remoteport, secret)
# F 是一个哈希函数,产生 32 位随机值

# 查看系统 ISN 生成策略
cat /proc/sys/net/ipv4/tcp_syncookies
# 1 = 启用 SYN Cookie

# 查看当前连接的 ISN
ss -ti
# State   Recv-Q Send-Q  Local Address:Port  Peer Address:Port Process
# ESTAB   0    0    192.168.1.100:45678 93.184.216.34:80
#      ts sack reno wscale:7,7  --> TS val 1234567890 ecr 0

# ISN 预测攻击演示(不要在生产环境操作)
# ISN 应该每次都随机,防止攻击者预测下一个 ISN

2.5 三次握手异常场景

# 场景 1:SYN 泛洪攻击
# 攻击者发送大量 SYN,但不完成第三次握手
# 服务端资源被耗尽

# 防御措施
# 查看 SYN Flood 状态
netstat -an | grep SYN_RECV | wc -l

# 启用 SYN Cookie
echo1 > /proc/sys/net/ipv4/tcp_syncookies

# 调整 SYN Backlog
echo2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
echo1 > /proc/sys/net/ipv4/tcp_synack_retries

# 场景 2:连接超时
# 网络延迟过大导致握手超时
ss -o state syn-sent
# Timer:(connect timeout)

# 场景 3:端口未监听
# 发送 SYN 后收到 RST
# 目的端口没有进程监听

# tcpdump 观察
sudo tcpdump -i eth0'tcp[tcpflags] & tcp-rst != 0'-n

3 四次挥手深度解析

3.1 为什么是四次挥手?

客户端                  服务端
 |                    |
 | 关闭连接的原因:            |
 | 1. TCP 是全双工(bidirectional)    |
 | 2. 双方各自独立关闭发送通道       |
 | 3. 每一方都需要发送 FIN 并收到 ACK  |
 |                    |
 | 为什么挥手需要 4 个包?         |
 |                    |
 | 主动关闭方 --> FIN --> 被动关闭方    | (关闭发送通道)
 | 被动关闭方 --> ACK --> 主动关闭方   |
 |                    |
 | 被动关闭方 --> FIN --> 主动关闭方    | (对方也关闭发送通道)
 | 主动关闭方 --> ACK --> 被动关闭方    |
 |                    |

关键点

主动关闭方发送 FIN,表示"我不会再发送数据了"

被动关闭方收到 FIN 后返回 ACK,但此时可能仍有数据要发送

被动关闭方数据发送完毕后,才发送自己的 FIN

TIME_WAIT 状态确保最后的 ACK 能到达对方

3.2 四次挥手详细过程

时间线      客户端               服务端
 |                    |
 | 假设当前状态: ESTABLISHED       |
 |                    |
 | 客户端应用进程调用 close()      |
 | --> 发送 FIN,进入 FIN_WAIT_1     |
T1 |------------------------------------>|
 |   SEQ=1000, ACK=2000         |
 |   Flags=[FIN,ACK]          |
 |   状态: FIN_WAIT_1          |
 |                    |
 |             2. 收到 FIN  |
 |             --> 发送 ACK |
T2 |<------------------------------------|
   |     SEQ=2000, ACK=1001                 |
   |     Flags=[ACK]                        |
   |     状态: CLOSE_WAIT                   |
   |     (服务端等待应用进程处理完数据)        |
   |                                       |
   |  3. 收到 ACK                          |
   |  --> 进入 FIN_WAIT_2          |
 | 状态: FIN_WAIT_2           |
 | (等待服务端的 FIN)          |
 |                    |
 |             4. 应用进程  |
 |               处理完数据 |
 |             --> 调用 close|
 |             --> 发送 FIN |
T3 |<------------------------------------|
   |     SEQ=2000, ACK=1001                 |
   |     Flags=[FIN,ACK]                    |
   |     状态: LAST_ACK                     |
   |                                       |
   |  5. 收到 FIN                          |
   |  --> 发送 ACK             |
T4 |------------------------------------>|
 |   SEQ=1001, ACK=2001         |
 |   Flags=[ACK]            |
 |   状态: TIME_WAIT          |
 |                    |
 | 6. 等待 2MSL 后           |
 | --> 进入 CLOSED            |
 |                    |
 |             7. 收到 ACK |
 |             --> 进入 CLOSED|
 |                    |

3.3 TIME_WAIT 状态详解

# TIME_WAIT 的作用
# 1. 确保最后的 ACK 能到达对方
#  - ACK 可能丢失
#  - 服务端 会重发 FIN
#  - 如果客户端已关闭,服务端无法收到 ACK

# 2. 等待网络中所有旧数据包消散
#  - 防止延迟的旧数据包被新连接误收
#  - MSL (Maximum Segment Lifetime) = 2分钟

# 查看 TIME_WAIT 连接数
netstat -an | grep TIME_WAIT | wc -l

# 查看各状态连接数
ss -s
# Total: 256 (kernel 512)
# TCP:  6 (kernel 6)
# ...

# TIME_WAIT 超时时间
# Linux 默认 60 秒(2MSL,通常 MSL=30秒)
cat /proc/sys/net/ipv4/tcp_fin_timeout
# 输出:60

# 调整 TIME_WAIT 超时(谨慎)
echo30 > /proc/sys/net/ipv4/tcp_fin_timeout

# 启用 TIME_WAIT 复用
echo1 > /proc/sys/net/ipv4/tcp_tw_reuse

# 启用快速回收(慎用,可能导致问题)
echo1 > /proc/sys/net/ipv4/tcp_tw_recycle

3.4 CLOSE_WAIT 状态问题

# CLOSE_WAIT 问题的原因
# 服务端收到客户端 FIN 后返回 ACK
# 但服务端的应用程序没有调用 close()
# 导致连接一直处于 CLOSE_WAIT 状态

# 排查 CLOSE_WAIT
netstat -an | grep CLOSE_WAIT | head -20
# 输出示例:
# Proto Recv-Q Send-Q Local Address     Foreign Address    State
# tcp    0   0 0.0.0.0:3306     192.168.1.100:45678  CLOSE_WAIT

# 定位问题进程
ss -tlnp | grep :3306
# 查看哪些进程持有连接

# 示例:Python 应用程序未关闭连接
python3 << 'EOF'
import socket

def handle_client(client_sock, addr):
    # 问题:函数结束时未调用 client_sock.close()
    # 这会导致 CLOSE_WAIT
    data = client_sock.recv(1024)
    client_sock.sendall(b"OK")

# 正确做法:
def handle_client_correct(client_sock, addr):
    try:
        data = client_sock.recv(1024)
        client_sock.sendall(b"OK")
    finally:
        client_sock.close()  # 确保关闭
EOF

3.5 四次挥手异常场景

# 场景 1:RST 强制关闭
# 一方发送 RST,另一方立即关闭

# 触发 RST 的情况
# - 访问不存在的连接(如服务器崩溃后重启)
# - SO_LINGER 设置为 0
# - 故意abort连接

# 查看 RST 包
sudo tcpdump -i eth0'tcp[tcpflags] & tcp-rst != 0'-n

# 场景 2:FIN_WAIT_2 超时
# 客户端进入 FIN_WAIT_2,但服务端不发送 FIN
# 默认 60 秒后自动关闭

# 场景 3:大量 TIME_WAIT
# 高并发短连接场景
# 解决方案:
# 1. 调整 tcp_fin_timeout
# 2. 启用 tcp_tw_reuse
# 3. 使用 SO_LINGER
# 4. 客户端使用 HTTP Keep-Alive

# 场景 4:LAST_ACK 状态长时间存在
# 服务端发送 FIN 后未收到 ACK
# 可能网络问题或对端崩溃

4 TCP 状态转换图

4.1 完整状态转换图

             应用层调用
               |
               v
            +-----------+
            | CLOSED |
            +-----------+
               |
               | 被动打开 (listen)
               v
            +-----------+
     +----------->|  LISTEN |<-----------+
          |            +-----------+            |
          |                  |                  |
          | 主动发送 SYN     |                  | 收到 SYN
          |                  |                  | 发送 SYN+ACK
          v                  v                  |
    +-----------+      +-----------+            |
    | SYN_SENT  |      |  SYN_RCVD |<-----------+
    +-----------+      +-----------+     收到 ACK
          |                  |
          |                  |
          | 收到 SYN+ACK     | 收到 ACK
          |                  |
          v                  v
    +---------------------------+
    |      ESTABLISHED          |
    |      (数据传输状态)         |
    +---------------------------+
          |                  ^
          |                  |
          | 主动 close       | 被动 close
          | 发送 FIN         | 收到 FIN
          v                  |
    +-----------+            |
    |FIN_WAIT_1 |            |
    +-----------+            |
          |                  |
          | 收到 ACK        | 收到 FIN
          | (半关闭)        | 发送 ACK
          v                  |
    +-----------+            |
    |FIN_WAIT_2 |<-----------+
    +-----------+     收到 ACK
          |
          | 收到 FIN
          | 发送 ACK
          v
    +-----------+
    |TIME_WAIT  |
    +-----------+
          |
          | 2MSL 超时
          v
    +-----------+
    |  CLOSED   |
    +-----------+

服务端状态:
LISTEN -> SYN_RCVD -> ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED

客户端状态:
CLOSED -> SYN_SENT -> ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2 -> TIME_WAIT -> CLOSED

4.2 状态查看命令

# 查看所有 TCP 状态
ss -ant

# 各状态含义
# LISTEN: 监听中
# SYN_SENT: 客户端已发送 SYN
# SYN_RECEIVED: 服务端收到 SYN
# ESTABLISHED: 连接已建立
# FIN_WAIT_1: 主动关闭,已发送 FIN
# FIN_WAIT_2: 收到 ACK,等待对方 FIN
# CLOSE_WAIT: 被动关闭,收到 FIN,等待应用关闭
# CLOSING: 双方同时关闭
# LAST_ACK: 最后确认状态
# TIME_WAIT: 等待 2MSL

# 统计各状态数量
ss -ant | awk'{print $1}'| sort | uniq -c | sort -rn

# 查看特定状态
ss -ant state time-wait
ss -ant state close-wait
ss -ant state syn-sent

# 查看进程对应的连接
ss -tlnp
# 输出示例:
# State   Recv-Q  Send-Q   Local Address:Port  Peer Address:Port  Process
# LISTEN  0     128    0.0.0.0:22      0.0.0.0:*       users:(("sshd",pid=1234,fd=3))

5 连接管理实战

5.1 半连接队列与全连接队列

# 半连接队列 (SYN Queue)
# 服务端收到 SYN 后进入此队列
# 大小由 tcp_max_syn_backlog 控制

cat /proc/sys/net/ipv4/tcp_max_syn_backlog
# 默认值:128 (Linux 2.6+)

# 全连接队列 (Accept Queue)
# 完成三次握手后,accept() 之前进入此队列
# 大小由 listen() 的 backlog 参数决定

# 查看队列溢出
netstat -s | grep -i"overflow|listen"
# TCPBacklogDrop: 12345

# 查看当前队列状态
ss -ltn
# Recv-Q: 当前 accept 队列中的连接数
# Send-Q: 对端未确认的连接数

# 调整参数
echo2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
echo1024 > /proc/sys/net/core/somaxconn

5.2 TCP Keepalive

# TCP Keepalive 作用
# 检测空闲连接是否仍然存活
# 适用于:长连接、HTTP 长轮询

# 系统级配置
# /proc/sys/net/ipv4/tcp_keepalive_*

# 启用 Keepalive
echo1 > /proc/sys/net/ipv4/tcp_keepalive_probes

# Keepalive 空闲时间(秒)
echo7200 > /proc/sys/net/ipv4/tcp_keepalive_time

# 探测间隔(秒)
echo75 > /proc/sys/net/ipv4/tcp_keepalive_intvl

# 探测次数
echo9 > /proc/sys/net/ipv4/tcp_keepalive_probes

# 应用程序设置 Keepalive
python3 << 'EOF'
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

# Linux 特定选项
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 7200)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 75)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPALIVE, 9)
EOF

# Java 设置
# socket.setKeepAlive(true);
# -Dtcp.keepalive.time=7200
# -Dtcp.keepalive.intvl=75
# -Dtcp.keepalive.probes=9

5.3 TCP 保活机制

# 查看连接空闲时间
ss -ti state established | grep -E"timer|Idle"

# 查看 Keepalive 状态
cat /proc/net/sockstat
# sk_refcnt: 引用计数
# timer: 定时器状态

# 示例:Nginx 配置 keepalive
# /etc/nginx/nginx.conf
http {
  upstream backend {
    server 127.0.0.1:8080;
    keepalive 32; # 保持的空闲连接数
  }

  server {
    location / {
      proxy_pass http://backend;
      proxy_http_version 1.1;
      proxy_set_header Connection"";
     # 或
     # proxy_set_header Connection "keep-alive";
    }
  }
}

5.4 连接复用与优化

# tcp_timestamps 允许精确的 RTT 计算和 PAWS
cat /proc/sys/net/ipv4/tcp_timestamps
# 1 = 启用

# tcp_sack 允许选择性确认
cat /proc/sys/net/ipv4/tcp_sack
# 1 = 启用

# tcp_window_scaling 允许窗口缩放
cat /proc/sys/net/ipv4/tcp_window_scaling
# 1 = 启用

# 查看当前连接的窗口大小
ss -ti
# wscale: 发送窗口缩放因子
# rcv_wscale: 接收窗口缩放因子

# 调整 MTU 和 MSS
# MSS = MTU - IP头(20) - TCP头(20)
# 以太网 MTU 通常 1500
# MSS 典型值:1460

# 查看 MSS
ss -i | grep -E"rcv_space|snd_wnd"

6 常见 TCP 问题排查

6.1 连接超时

# 排查步骤
# 1. 检查网络连通性
ping -c 5 target_host

# 2. 检查路由
traceroute target_host
# 或
mtr target_host

# 3. 检查目标端口是否开放
nc -zv target_host 80
# 或
nmap -p 80 target_host

# 4. 检查本地端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
# 通常:32768 60999

# 5. 抓包分析
sudo tcpdump -i eth0 host target_host and port 80 -w /tmp/timeout.pcap

# 6. 分析三次握手
tshark -r /tmp/timeout.pcap -Y"tcp.flags.syn==1"-T fields -e frame.time -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport

6.2 连接被重置

# 排查 RST 原因
# 1. 端口未监听
sudo tcpdump -i eth0'tcp[tcpflags] & tcp-rst != 0'-n

# 2. 防火墙拦截
sudo iptables -L -n | grep DROP
sudo iptables -L -n | grep REJECT

# 3. 服务崩溃
journalctl -u nginx | tail -50
systemctl status nginx

# 4. 常见 RST 场景
# - 连接请求发送到未监听的端口
# - SO_LINGER 设置为 0
# - 服务器重启
# - 应用调用 close() 而对方未读取数据

6.3 连接队列满

# 现象:连接建立成功但无法通信

# 检查半连接队列溢出
netstat -s | grep -i"SYN"
# TCPRcvCoalesce: 12345
# TCPOFODrop: 123

# 检查全连接队列溢出
ss -ltn | grep Recv-Q
# 如果 Recv-Q 持续等于 backlog,说明队列满

# 增加队列大小
# 方法 1:临时调整
echo8192 > /proc/sys/net/core/somaxconn
echo8192 > /proc/sys/net/ipv4/tcp_max_syn_backlog

# 方法 2:永久调整
# /etc/sysctl.conf
cat >> /etc/sysctl.conf << 'EOF'
net.core.somaxconn = 8192
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.ip_local_port_range = 32768 60999
EOF
sysctl -p

# 方法 3:Nginx 配置
# nginx.conf
# listen 80 backlog=8192;

6.4 TIME_WAIT 过多

# 现象:连接数达到上限

# 检查 TIME_WAIT 数量
ss -ant | awk'{print $1}'| sort | uniq -c | sort -rn

# 如果 TIME_WAIT 过多(> 10000)
# 解决方案:

# 1. 调整 tcp_fin_timeout
echo30 > /proc/sys/net/ipv4/tcp_fin_timeout

# 2. 启用 tcp_tw_reuse(客户端)
echo1 > /proc/sys/net/ipv4/tcp_tw_reuse

# 3. 使用 SO_LINGER 强制关闭
# 应用程序代码
struct linger ling;
ling.l_onoff = 1;
ling.l_linger = 0;
setsockopt(sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
close(sock);
# 注意:这会导致 RST,可能造成数据丢失

# 4. 客户端使用 HTTP Keep-Alive
# Nginx 作为反向代理时:
# upstream 中配置 keepalive

# 5. 观察实际影响
ss -s
# 如果实际连接数不高,TIME_WAIT 多是正常的

7 Wireshark 高级分析

7.1 常用过滤表达式

# 基本过滤
tcp.port == 80          # 端口 80
tcp.srcport == 12345      # 源端口
tcp.dstport == 443       # 目标端口
ip.addr == 192.168.1.100    # IP 地址
tcp.flags.syn == 1       # SYN 标志
tcp.flags.ack == 1       # ACK 标志
tcp.flags.fin == 1       # FIN 标志
tcp.flags.rst == 1       # RST 标志

# 组合过滤
tcp.port == 80 and ip.addr == 192.168.1.100
tcp.flags.syn == 1 and tcp.flags.ack == 0 # 纯粹的 SYN
tcp.flags.syn == 1 and tcp.flags.ack == 1 # SYN+ACK

# 序列号过滤
tcp.seq == 1000         # 特定序列号
tcp.ack == 2000         # 特定确认号

# 时间过滤
frame.time_relative < 1           # 相对时间 < 1秒

# 专家信息
tcp.analysis.retransmission      # 重传
tcp.analysis.duplicate_ack      # 重复 ACK
tcp.analysis.out_of_order        # 乱序
tcp.analysis.fast_retransmission # 快速重传

7.2 跟随 TCP 流

# Wireshark 中跟随 TCP 流
# 右键点击包 -> Follow -> TCP Stream

# tshark 命令行跟随流
# 1. 找到流索引
tshark -r /tmp/capture.pcap -q -z"conv,tcp"| head -20

# 2. 跟随特定流
tshark -r /tmp/capture.pcap -Y"tcp.stream eq 0"-T fields -e data

# 3. 导出完整 HTTP 会话
tshark -r /tmp/capture.pcap -Y"http"-T fields -e ip.src -e http.request.method -e http.request.uri

7.3 TCP 统计分析

# 流统计
tshark -r /tmp/capture.pcap -q -z"io,stat,0.1,tcp.len"| head -30

# 连接统计
tshark -r /tmp/capture.pcap -q -z"conv,tcp"

# 重传统计
tshark -r /tmp/capture.pcap -q -z"io,stat,0.1,tcp.analysis.retransmission"

# 绘制时间序列图
# Wireshark -> Statistics -> TCP Stream Graphs
# - Time-Sequence Graph (Stevens)
# - Throughput Graph
# - Round Trip Time Graph

7.4 抓包脚本

#!/bin/bash
# tcp_capture.sh - TCP 抓包脚本

CAPTURE_FILE="/tmp/tcp_capture_$(date +%Y%m%d_%H%M%S).pcap"
FILTER="$1"
DURATION="${2:-60}"# 默认 60 秒

if[ -z"$FILTER"];then
 echo"用法:$0<过滤器> [持续秒数]"
 echo"示例:$0'tcp port 80' 120"
 exit1
fi

echo"开始抓包..."
echo"过滤器:$FILTER"
echo"持续时间:${DURATION}秒"
echo"输出文件:$CAPTURE_FILE"

# 使用 tcpdump 抓包
sudo tcpdump -i eth0 -w"$CAPTURE_FILE""$FILTER"&
PID=$!

# 等待指定时间
sleep"$DURATION"

# 停止抓包
kill$PID2>/dev/null
wait$PID2>/dev/null

echo"抓包完成"
echo"文件大小:$(du -h "$CAPTURE_FILE" | cut -f1)"

# 快速统计
echo""
echo"=== 抓包统计 ==="
echo"总包数:$(sudo tcpdump -r "$CAPTURE_FILE" 2>/dev/null | wc -l)"
echo"SYN 包:$(sudo tcpdump -r "$CAPTURE_FILE" 'tcp[tcpflags] == tcp-syn' 2>/dev/null | wc -l)"
echo"FIN 包:$(sudo tcpdump -r "$CAPTURE_FILE" 'tcp[tcpflags] == tcp-fin' 2>/dev/null | wc -l)"
echo"RST 包:$(sudo tcpdump -r "$CAPTURE_FILE" 'tcp[tcpflags] == tcp-rst' 2>/dev/null | wc -l)"

8 实战案例分析

案例一:Web 服务偶发性连接失败

现象:用户反馈网站偶尔打不开,刷新后正常

排查过程

# 1. 在服务端抓包
sudo tcpdump -i eth0 -w /tmp/http_issue.pcap'tcp port 80'&
sleep 30
# 等待问题复现
kill%1

# 2. 分析抓包文件
tshark -r /tmp/http_issue.pcap -Y"tcp.flags.syn==1"-T fields 
  -e frame.time_relative -e ip.src -e tcp.srcport 
  -e ip.dst -e tcp.dstport -e tcp.len | head -20

# 3. 查找 SYN 重传
tshark -r /tmp/http_issue.pcap -Y"tcp.analysis.retransmission"| wc -l
# 如果有重传,说明网络或服务端有问题

# 4. 检查服务端 backlog
ss -ltn | grep :80
# Recv-Q 应该接近 0,Send-Q 应该较小

# 5. 查看系统连接限制
cat /proc/sys/net/core/somaxconn
cat /proc/sys/net/ipv4/tcp_max_syn_backlog

根因:服务端 somaxconn 过小,高峰期 SYN 队列溢出。

解决

# 增加队列大小
echo4096 > /proc/sys/net/core/somaxconn
echo8192 > /proc/sys/net/ipv4/tcp_max_syn_backlog

# Nginx 配置
# worker_processes auto;
# worker_connections 4096;
# listen 80 backlog=4096;

案例二:数据库连接池耗尽

现象:应用日志显示 "Too many connections"

# 1. 检查 MySQL 连接数
mysql -u root -p -e"SHOW PROCESSLIST;"| wc -l
mysql -u root -p -e"SHOW STATUS LIKE 'Threads_connected';"

# 2. 检查 TIME_WAIT
netstat -an | grep TIME_WAIT | wc -l
# 大量 TIME_WAIT 说明连接没有正确复用

# 3. 检查连接来源
netstat -ant | awk'{print $5}'| cut -d: -f1 | sort | uniq -c | sort -rn | head -10

# 4. 查看慢查询
mysql -u root -p -e"SHOW GLOBAL STATUS LIKE 'Slow_queries';"

根因:应用使用短连接,每次请求都创建新连接,高并发时耗尽连接池。

解决

# 方案 1:使用连接池
importmysql.connector.pool

pool = mysql.connector.pooling.MySQLConnectionPool(
  pool_name="mypool",
  pool_size=10,
  host="localhost",
  database="test"
)

# 使用连接
conn = pool.get_connection()
cursor = conn.cursor()
# ... 操作后归还连接
conn.close() # 实际归还到池中

案例三:API 请求超时

现象:移动端 API 调用超时率高

# 1. 检查网络质量
ping -c 20 api.example.com
# 查看 RTT 抖动

# 2. 分析 TCP 重传
sudo tcpdump -i eth0 -w /tmp/api_tcp.pcap'host api.example.com'&
# 复现问题
tshark -r /tmp/api_tcp.pcap -Y"tcp.analysis.retransmission"| wc -l

# 3. 检查 TCP 窗口
# 在 Wireshark 中查看 Window Size 变化
# 如果 Window 接近 0,说明接收端 buffer 满

# 4. 查看服务处理时间
# 应用日志中的请求处理时间
# nginx access log 的 response_time

根因:服务端处理慢,TCP 窗口收缩,客户端收不到数据而超时。

解决

# 1. 增加服务端 buffer
echo16777216 > /proc/sys/net/core/rmem_max
echo16777216 > /proc/sys/net/core/wmem_max

# 2. Nginx 配置
# proxy_buffering on;
# proxy_buffer_size 128k;
# proxy_buffers 4 256k;

# 3. 应用优化
# - 增加处理线程
# - 使用异步处理
# - 优化数据库查询

9 总结与速查表

三次握手状态机

客户端               服务端
CLOSED ──────────────────────────> LISTEN
 |                  |
 | 1. 发送 SYN           |
 |  SEQ=c_isn           |
 | ─────────────────────────────>  |
 |                  |
 |       SYN_SENT       LISTEN
 |                  |
 | <─────────────────────────────    |
   | 2. 收到 SYN+ACK                   |
   |    SEQ=s_isn                      |
   |    ACK=c_isn+1                    |
   |                                    |
   |              SYN_RCVD             |
   |                                    |
   | 3. 发送 ACK                       |
   |    ACK=s_isn+1                     |
   | ──────────────────────────────>  |
 |                  |
 |     ESTABLISHED       ESTABLISHED
 |                  |

四次挥手状态机

主动方               被动方
ESTABLISHED ──────────────────────> ESTABLISHED
 |                  |
 | 1. 发送 FIN            |
 |  SEQ=x              |
 | ──────────────────────────────>  |
 |                  |
 |      FIN_WAIT_1       CLOSE_WAIT
 |                  |
 | <─────────────────────────────    |
   | 2. 收到 ACK                        |
   |    ACK=x+1                         |
   |                                    |
   |           FIN_WAIT_2              CLOSE_WAIT
   |                                    |
   |                                    | 3. 数据发送完毕
   |                                    |    调用 close()
   |                                    |    发送 FIN
   |                                    |    SEQ=y
   | <─────────────────────────────    |
   | 4. 收到 FIN                        |
   |    SEQ=y                           |
   |    发送 ACK                        |
   |    ACK=y+1                         |
   |                                    |
   |           TIME_WAIT               LAST_ACK
   |                                    |
   |                                    |
   | <─────────────────────────────    |
   | 5. 收到 ACK                        |
   |                                    |
   |           CLOSED                  CLOSED

TCP 状态速查

状态 客户端 服务端 说明
LISTEN 等待连接
SYN_SENT 已发送 SYN
SYN_RCVD 收到 SYN
ESTABLISHED 连接建立
FIN_WAIT_1 已发 FIN
FIN_WAIT_2 收到 ACK
CLOSE_WAIT 收到 FIN
TIME_WAIT 等待 2MSL
LAST_ACK 最后确认

常用命令速查

# 查看连接状态
ss -ant               # 所有 TCP 连接
ss -ti               # 连接详细信息(带 timer)
ss -tlnp              # 监听端口

# 抓包分析
tcpdump -i eth0'tcp port 80'-w a.pcap
tshark -r a.pcap -Y"tcp"-T fields

# 查看网络参数
cat /proc/sys/net/ipv4/tcp_*
sysctl -a | grep tcp

# 连接统计
netstat -s | grep -i tcp
ss -s

故障排查流程

TCP 连接异常
  |
  ├── 三次握手问题
  |  ├── SYN 没发出  -> 检查网络
  |  ├── SYN 没收到  -> 检查防火墙
  |  ├── SYN+ACK 没收到 -> 抓包分析
  |  └── ACK 没发出  -> 抓包分析
  |
  ├── 四次挥手问题
  |  ├── FIN 没发出  -> 检查应用 close()
  |  ├── TIME_WAIT 多 -> 调整参数或连接复用
  |  └── CLOSE_WAIT 多 -> 应用未关闭连接
  |
  ├── 连接中断
  |  ├── RST 原因   -> 抓包分析
  |  ├── 重传过多   -> 检查网络质量
  |  └── 超时     -> 检查延迟
  |
  └── 性能问题
    ├── 窗口为 0   -> 增加 buffer
    ├── 队列满    -> 增加 somaxconn
    └── 大量短连接  -> 使用连接池

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

    关注

    14

    文章

    8327

    浏览量

    95546
  • TCP
    TCP
    +关注

    关注

    8

    文章

    1432

    浏览量

    83757
  • 模型
    +关注

    关注

    1

    文章

    3810

    浏览量

    52253

原文标题:初中级运维怎么理解 TCP 三次握手和四次挥手?

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    讲一讲的TCP三次握手四次挥手

    如果你学过网络基础知识,那么你一定对TCP三次握手不陌生。今天我想用通俗的话来给大家讲一讲TCP三次握手
    的头像 发表于 02-03 10:43 3505次阅读
    讲一讲的<b class='flag-5'>TCP</b><b class='flag-5'>三次</b><b class='flag-5'>握手</b>和<b class='flag-5'>四次</b><b class='flag-5'>挥手</b>

    三次握手四次挥手你懂吗

    程序员面试被问到“三次握手四次挥手”怎么办?
    发表于 04-08 07:23

    TCP三次握手的过程描述

    本文档主要描述TCP三次握手的过程,一个完整的三次握手也就是 请求---应答---再次确认
    发表于 03-02 15:37 8次下载

    TCP/IP协议工作过程的三次握手四次挥手

    )、第三次握手:Client收到确认后,检查ACK是否为1,如果正确则将标志位ACK置为1,并将该数据包发送给Server,Server检查ACK是否为1,如果正确则连接建立成功,Client
    的头像 发表于 10-25 09:49 7680次阅读

    TCP三次握手过程及四次挥手过程说明

    三次握手 置位概念:根据 TCP 的包头字段,存在 3 个重要的标识 ACK、SYN、FIN ACK:表示验证字段 SYN:位数置 1,表示建立 TCP 连接 FIN:位数置 1,表示
    的头像 发表于 03-01 12:00 4935次阅读

    TCP三次握手四次挥手以及11种状态资料下载

    电子发烧友网为你提供TCP三次握手四次挥手以及11种状态资料下载的电子资料下载,更有其他相关的电路图、源代码、课件教程、中文资料、英文资料
    发表于 04-15 08:41 2次下载
    <b class='flag-5'>TCP</b><b class='flag-5'>三次</b><b class='flag-5'>握手</b>和<b class='flag-5'>四次</b><b class='flag-5'>挥手</b>以及11种状态资料下载

    TCP三次握手四次挥手过程中的异常情况

    TCP 三次握手四次挥手过程中,途中某一步的报文丢失了,会发生什么?
    的头像 发表于 09-05 10:23 2125次阅读

    如何使用WireShark进行TCP三次握手

    WireShark是一种非常方便的网络抓包工具,下面演示,使用WireShark来抓取TCP三次握手过程。
    的头像 发表于 11-01 09:50 2999次阅读

    TCP建立连接概述及三次握手四次挥手的流程

    具备上述个条件后A获取B的信息是有要求的,根本上的要求是数据信道可靠,就是平时所说的可靠连接,那么如何保证连接的可靠性呢,TCP协议就是靠确认应答机制、超时重传机制等保证连接可靠性的,接下来就通过TCP协议的
    的头像 发表于 03-23 15:57 2192次阅读
    <b class='flag-5'>TCP</b>建立连接概述及<b class='flag-5'>三次</b><b class='flag-5'>握手</b>、<b class='flag-5'>四次</b><b class='flag-5'>挥手</b>的流程

    用恋爱的方式解释TCP三次握手四次挥手

    前言今天的分享,是关于前两天读到的心得,TCP建立连接时三次握手,断开时为何4握手的自我理解
    的头像 发表于 08-28 16:11 1627次阅读
    用恋爱的方式解释<b class='flag-5'>TCP</b>的<b class='flag-5'>三次</b><b class='flag-5'>握手</b>和<b class='flag-5'>四次</b><b class='flag-5'>挥手</b>

    说说TCP三次握手的过程?为什么是三次而不是两四次

    说说TCP三次握手的过程?为什么是三次而不是两四次T
    的头像 发表于 02-04 11:03 2024次阅读

    TCP三次握手协议的作用

    在计算机网络中,数据的传输需要在发送方和接收方之间建立一个稳定的连接,以确保数据的完整性和顺序。TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,它通过三次握手协议来建立
    的头像 发表于 01-03 17:15 1813次阅读

    TCP三次握手安全性分析

    TCP(传输控制协议)的三次握手是建立可靠连接的重要机制,它确保了通信双方在数据传输前的连接状态是可靠和准确的。然而,从安全性的角度来分析,TCP
    的头像 发表于 01-03 18:10 2016次阅读

    TCP三次握手四次挥手,这样解释太通俗易懂了!

    TCP连接的建立和释放分别通过“三次握手”和“四次挥手”来完成。三次
    的头像 发表于 04-24 19:33 1668次阅读
    <b class='flag-5'>TCP</b><b class='flag-5'>三次</b><b class='flag-5'>握手</b>和<b class='flag-5'>四次</b><b class='flag-5'>挥手</b>,这样解释太通俗易懂了!

    TCP三次握手四次挥手的详细过程

    TCP 三次握手四次挥手,大概是网络领域被问烂了的面试题。但真正能把状态变迁、序列号变化、抓包细节讲清楚的人并不多。很多人背了八股文,一到
    的头像 发表于 02-25 10:38 416次阅读