网络协议2024-04-0420 分钟阅读

TCP/IP 三次握手与四次挥手:深入理解网络连接的建立与断开

详细讲解 TCP 协议的三次握手和四次挥手过程,包括状态转换、常见问题和实战抓包分析,帮助你彻底理解 TCP 连接机制

TCP/IP网络协议三次握手四次挥手计算机网络

什么是 TCP 协议

TCP(Transmission Control Protocol,传输控制协议)是互联网协议族中最重要的传输层协议之一。它提供可靠的、面向连接的、字节流服务。

TCP 的核心特性

  • 面向连接:通信前必须建立连接
  • 可靠传输:保证数据按序到达,无丢失、无重复
  • 流量控制:防止发送方发送过快导致接收方缓冲区溢出
  • 拥塞控制:防止网络拥塞
  • 全双工通信:双方可以同时发送和接收数据

TCP 三次握手(建立连接)

三次握手是 TCP 建立连接的过程,确保双方都准备好进行数据传输。

握手过程详解

TCP 三次握手过程

第一次握手:客户端发送 SYN

客户端动作

  • 发送 SYN(同步序列号)报文段
  • 设置 SYN=1,seq=x(随机初始序列号)
  • 进入 SYN_SENT 状态

报文内容

SYN = 1
seq = 1000(客户端初始序列号)

目的:告诉服务端"我想和你建立连接,我的初始序列号是 x"

第二次握手:服务端回应 SYN+ACK

服务端动作

  • 收到客户端的 SYN 后,发送 SYN+ACK 报文段
  • 设置 SYN=1,ACK=1,seq=y,ack=x+1
  • 进入 SYN_RCVD 状态

报文内容

SYN = 1
ACK = 1
seq = 2000(服务端初始序列号)
ack = 1001(确认客户端的序列号+1)

目的:告诉客户端"我收到了你的请求,我也准备好了,我的初始序列号是 y,我确认了你的序列号"

第三次握手:客户端确认 ACK

客户端动作

  • 收到服务端的 SYN+ACK 后,发送 ACK 报文段
  • 设置 ACK=1,ack=y+1
  • 进入 ESTABLISHED 状态

报文内容

ACK = 1
seq = 1001
ack = 2001(确认服务端的序列号+1)

目的:告诉服务端"我收到了你的确认,连接建立成功"

服务端动作

  • 收到客户端的 ACK 后,进入 ESTABLISHED 状态
  • 连接建立完成,可以开始传输数据

为什么需要三次握手?

1. 防止旧连接请求导致混乱

场景:客户端发送的第一个 SYN 在网络中延迟了,客户端超时后重新发送了 SYN 并成功建立连接。后来,延迟的旧 SYN 到达服务端。

如果只有两次握手

  • 服务端收到旧 SYN,回复 SYN+ACK
  • 服务端认为连接建立,开始等待数据
  • 客户端不理会这个旧连接,导致服务端资源浪费

三次握手的优势

  • 服务端回复 SYN+ACK 后,等待客户端的第三次 ACK
  • 客户端不会对旧连接发送 ACK
  • 服务端超时后关闭连接,避免资源浪费

2. 确认双方的收发能力

  • 第一次握手:服务端确认客户端的发送能力
  • 第二次握手:客户端确认服务端的收发能力
  • 第三次握手:服务端确认客户端的接收能力

3. 同步双方的初始序列号

TCP 是可靠传输,需要序列号来保证数据的顺序和完整性。三次握手确保双方都知道对方的初始序列号。

三次握手的状态转换

客户端状态

CLOSED → SYN_SENT → ESTABLISHED

服务端状态

CLOSED → LISTEN → SYN_RCVD → ESTABLISHED

TCP 四次挥手(断开连接)

四次挥手是 TCP 断开连接的过程,确保双方都完成数据传输。

挥手过程详解

TCP 四次挥手过程

第一次挥手:客户端发送 FIN

客户端动作

  • 发送 FIN(结束)报文段
  • 设置 FIN=1,seq=u
  • 进入 FIN_WAIT_1 状态

报文内容

FIN = 1
seq = 5000

目的:告诉服务端"我没有数据要发送了,准备关闭连接"

注意:客户端仍然可以接收数据

第二次挥手:服务端确认 ACK

服务端动作

  • 收到客户端的 FIN 后,发送 ACK 报文段
  • 设置 ACK=1,ack=u+1
  • 进入 CLOSE_WAIT 状态

报文内容

ACK = 1
ack = 5001

目的:告诉客户端"我收到了你的关闭请求,但我可能还有数据要发送,请等待"

客户端动作

  • 收到 ACK 后,进入 FIN_WAIT_2 状态
  • 等待服务端发送 FIN

第三次挥手:服务端发送 FIN

服务端动作

  • 数据发送完毕后,发送 FIN 报文段
  • 设置 FIN=1,seq=v
  • 进入 LAST_ACK 状态

报文内容

FIN = 1
seq = 6000

目的:告诉客户端"我的数据也发送完了,可以关闭连接了"

第四次挥手:客户端确认 ACK

客户端动作

  • 收到服务端的 FIN 后,发送 ACK 报文段
  • 设置 ACK=1,ack=v+1
  • 进入 TIME_WAIT 状态
  • 等待 2MSL(Maximum Segment Lifetime)后关闭

报文内容

ACK = 1
ack = 6001

目的:告诉服务端"我收到了你的关闭请求,连接可以关闭了"

服务端动作

  • 收到客户端的 ACK 后,进入 CLOSED 状态
  • 连接完全关闭

为什么需要四次挥手?

1. TCP 是全双工通信

  • 客户端发送 FIN,只是关闭了客户端到服务端的数据传输(半关闭)
  • 服务端可能还有数据要发送给客户端
  • 服务端发送完数据后,再发送 FIN 关闭服务端到客户端的数据传输
  • 因此需要四次挥手来完全关闭双向连接

2. 为什么不能合并成三次?

第二次和第三次挥手不能合并的原因

  • 服务端收到客户端的 FIN 时,可能还有数据在发送
  • 必须先 ACK 确认收到 FIN,然后继续发送数据
  • 数据发送完毕后,再发送 FIN

特殊情况:如果服务端收到 FIN 时没有数据要发送,可以将 ACK 和 FIN 合并,变成三次挥手。

四次挥手的状态转换

主动关闭方(客户端)状态

ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED

被动关闭方(服务端)状态

ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED

TIME_WAIT 状态详解

为什么需要 TIME_WAIT?

1. 确保最后的 ACK 能够到达

  • 如果客户端发送的最后一个 ACK 丢失,服务端会重传 FIN
  • 客户端在 TIME_WAIT 状态可以重新发送 ACK
  • 如果直接关闭,服务端重传的 FIN 会收到 RST,导致异常

2. 防止旧连接的数据包干扰新连接

  • 等待 2MSL(通常是 2-4 分钟)确保网络中的旧数据包都消失
  • 避免新连接收到旧连接的延迟数据包

2MSL 的含义

  • MSL(Maximum Segment Lifetime):报文段最大生存时间
  • 2MSL = 报文段发送的最大时间 + 响应报文段返回的最大时间

TIME_WAIT 过多的问题

  • 占用端口资源(一个端口在 TIME_WAIT 期间不能被重用)
  • 高并发场景下可能导致端口耗尽

解决方案

# Linux 系统优化
# 允许 TIME_WAIT 状态的 socket 被重用
net.ipv4.tcp_tw_reuse = 1

# 快速回收 TIME_WAIT 状态的 socket
net.ipv4.tcp_tw_recycle = 1

# 减少 TIME_WAIT 超时时间
net.ipv4.tcp_fin_timeout = 30

实战:使用 tcpdump 抓包分析

抓取三次握手

# 抓取 80 端口的 TCP 连接
sudo tcpdump -i eth0 'tcp port 80' -nn -vv

# 输出示例:
# 第一次握手:SYN
# 12:00:00.000000 IP 192.168.1.100.50000 > 192.168.1.200.80: Flags [S], seq 1000, win 65535

# 第二次握手:SYN+ACK
# 12:00:00.001000 IP 192.168.1.200.80 > 192.168.1.100.50000: Flags [S.], seq 2000, ack 1001, win 65535

# 第三次握手:ACK
# 12:00:00.002000 IP 192.168.1.100.50000 > 192.168.1.200.80: Flags [.], ack 2001, win 65535

抓取四次挥手

# 抓取连接关闭过程
sudo tcpdump -i eth0 'tcp port 80 and (tcp[tcpflags] & tcp-fin != 0)' -nn -vv

# 输出示例:
# 第一次挥手:FIN
# 12:00:10.000000 IP 192.168.1.100.50000 > 192.168.1.200.80: Flags [F.], seq 5000, ack 6000

# 第二次挥手:ACK
# 12:00:10.001000 IP 192.168.1.200.80 > 192.168.1.100.50000: Flags [.], ack 5001

# 第三次挥手:FIN
# 12:00:10.002000 IP 192.168.1.200.80 > 192.168.1.100.50000: Flags [F.], seq 6000, ack 5001

# 第四次挥手:ACK
# 12:00:10.003000 IP 192.168.1.100.50000 > 192.168.1.200.80: Flags [.], ack 6001

TCP 状态机完整图

客户端状态转换:
CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED

服务端状态转换:
CLOSED → LISTEN → SYN_RCVD → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED

同时打开(罕见):
CLOSED → SYN_SENT → SYN_RCVD → ESTABLISHED

同时关闭(罕见):
ESTABLISHED → FIN_WAIT_1 → CLOSING → TIME_WAIT → CLOSED

总结

三次握手关键点

✅ 确保双方都准备好通信
✅ 同步双方的初始序列号
✅ 防止旧连接请求导致混乱
✅ 确认双方的收发能力

四次挥手关键点

✅ TCP 是全双工通信,需要双向关闭
✅ 被动关闭方可能还有数据要发送
✅ TIME_WAIT 确保连接可靠关闭
✅ 2MSL 等待时间防止旧数据包干扰

最佳实践

  1. 使用长连接:减少连接建立和关闭的开销
  2. 启用 Keep-Alive:防止连接被中间设备断开
  3. 优化系统参数:根据业务场景调整 TCP 参数
  4. 监控连接状态:及时发现和处理异常连接
  5. 使用连接池:复用连接,提高性能

参考资料