Skip to content

TCP 三次握手与四次挥手

📚 相关文档

一、TCP 协议概述

TCP(Transmission Control Protocol) 是核心传输层协议。

核心特性

  • 面向连接:通信前必须建立连接
  • 可靠传输:不丢失、不重复、按序到达
  • 全双工:双方可同时收发
  • 流量控制:防止发送过快
  • 拥塞控制:避免网络过载

对比 UDP

特性TCPUDP
连接面向连接无连接
可靠性可靠不可靠
速度较慢
应用网页、文件、邮件视频直播、DNS、游戏

二、三次握手详解

1. 为什么需要三次?

目标

  1. ✅ 双方都具备收发能力
  2. ✅ 同步初始序列号(ISN)
  3. ✅ 防止历史连接干扰

两次不够的原因

mermaid
sequenceDiagram
    participant Client as 客户端
    participant Server as 服务器
    
    Note over Client,Server: ❌ 两次握手的问题
    
    Client->>Server: SYN (旧请求,延迟到达)
    Server->>Client: SYN+ACK
    Note over Client: 客户端忽略(序列号不匹配)
    Note over Server: ❌ 但服务器认为连接已建立,浪费资源!

如果只有两次握手,服务器可能在未收到客户端确认的情况下就建立连接,导致资源浪费和 SYN Flood 攻击风险。


2. 三次握手流程

mermaid
sequenceDiagram
    participant Client as 客户端
    participant Server as 服务器
    
    Note over Client,Server: 第一次握手
    Client->>Server: SYN (seq=x)
    Note over Client: 状态: SYN_SENT
    
    Note over Client,Server: 第二次握手
    Server->>Client: SYN+ACK (ack=x+1, seq=y)
    Note over Server: 状态: SYN_RECEIVED
    
    Note over Client,Server: 第三次握手
    Client->>Server: ACK (ack=y+1, seq=x+1)
    Note over Client,Server: ✅ 连接建立 (ESTABLISHED)

详细过程

步骤方向报文内容状态变化
第一次客户端 → 服务器SYN=1, Seq=x客户端: SYN_SENT
第二次服务器 → 客户端SYN=1, ACK=1, Seq=y, Ack=x+1服务器: SYN_RECEIVED
第三次客户端 → 服务器ACK=1, Seq=x+1, Ack=y+1双方: ESTABLISHED

关键参数

  • Seq(序列号):随机生成,标识字节序号
  • Ack(确认号):期望收到的下一个字节序号(Ack = 收到的 Seq + 1

3. 关键参数说明

序列号(Sequence Number)

作用

  • 实现数据按序重组
  • 丢包检测重传

示例

客户端发送:
第 1 次: Seq=1000, 长度=500  → 覆盖字节 1000~1499
第 2 次: Seq=1500, 长度=300  → 覆盖字节 1500~1799

服务器确认:
Ack=1800  → "我已收到 1799 及之前所有字节"

为什么随机?

  • 🔒 安全性:防止预测序列号进行会话劫持
  • 🎲 避免冲突:不同连接不会重叠

窗口大小(Window Size)

作用:流量控制,告诉对方"我还能接收多少数据"。

动态调整

  • 接收快 → 窗口增大
  • 接收慢 → 窗口减小
  • 缓冲区满 → 窗口为 0(停止发送)

MSS(Maximum Segment Size)

定义:TCP 报文段的最大数据长度(不含头部)。

计算

以太网 MTU = 1500 字节
IP 头部 = 20 字节
TCP 头部 = 20 字节
MSS = 1500 - 20 - 20 = 1460 字节

三、常见问题与优化

1. SYN Flood 攻击

攻击原理:恶意发送大量 SYN 但不完成握手,耗尽服务器资源。

mermaid
sequenceDiagram
    participant Attacker as 攻击者
    participant Server as 服务器
    
    loop 大量伪造请求
        Attacker->>Server: SYN (伪造 IP)
        Server->>Attacker: SYN+ACK
        Note over Attacker: 不回复 ACK
        Note over Server: ⏳ 等待超时,占用资源
    end
    
    Note over Server: ❌ 半连接队列满了!

防御措施

① SYN Cookie

bash
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
  • 不立即分配资源
  • 根据客户端信息计算 Cookie
  • 收到正确 ACK 才分配资源

② 增加半连接队列

bash
echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog

③ 防火墙限流

bash
iptables -A INPUT -p tcp --syn -m limit --limit 1/s -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP

2. TIME_WAIT 状态

定义:主动关闭方发送最后一个 ACK 后进入的状态,持续 2MSL(通常 60 秒)。

为什么需要 TIME_WAIT?

mermaid
sequenceDiagram
    participant Client as 客户端
    participant Server as 服务器
    
    Client->>Server: FIN
    Server->>Client: ACK
    Server->>Client: FIN
    Client->>Server: ACK
    Note over Client: 进入 TIME_WAIT(等待 2MSL)
    
    Note over Client,Server: 场景 1: 最后的 ACK 丢失
    Note over Server: 重传 FIN
    Client->>Server: ACK(重新发送)
    
    Note over Client,Server: 场景 2: 旧报文到达
    Note over Client: 丢弃过期报文 ✓

原因

  1. 确保最后一个 ACK 到达:如果丢失,服务器会重传 FIN
  2. 防止旧报文干扰新连接:等待网络中所有旧报文消失

问题:高并发下大量 TIME_WAIT 占用端口资源。

解决方案

① 启用端口复用

bash
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse

② 使用连接池

  • HTTP Keep-Alive
  • HTTP/2 多路复用
  • 数据库连接池

⚠️ 注意:不要随意缩短 TIME_WAIT 时间!


四、四次挥手详解

1. 为什么是四次?

原因:TCP 是全双工的,两个方向需分别关闭。

客户端 → 服务器:需要关闭
服务器 → 客户端:也需要关闭

总共需要 4 次交互

2. 四次挥手流程

mermaid
sequenceDiagram
    participant Client as 客户端(主动关闭)
    participant Server as 服务器(被动关闭)
    
    Note over Client,Server: 第一次挥手
    Client->>Server: FIN (seq=u)
    Note over Client: 状态: FIN_WAIT_1
    
    Note over Client,Server: 第二次挥手
    Server->>Client: ACK (ack=u+1)
    Note over Server: 状态: CLOSE_WAIT(还可发送数据)
    Note over Client: 状态: FIN_WAIT_2
    
    Note over Client,Server: 服务器处理完剩余数据
    
    Note over Client,Server: 第三次挥手
    Server->>Client: FIN+ACK (seq=v, ack=u+1)
    Note over Server: 状态: LAST_ACK
    
    Note over Client,Server: 第四次挥手
    Client->>Server: ACK (ack=v+1)
    Note over Client: 状态: TIME_WAIT(等待 2MSL)
    Note over Server: 状态: CLOSED

3. 各状态说明

状态说明持续时间
FIN_WAIT_1已发送 FIN,等待 ACK短暂
FIN_WAIT_2已收到 ACK,等待对方 FIN可能较长
CLOSE_WAIT已收到 FIN,等待应用层关闭取决于应用
LAST_ACK已发送 FIN,等待最后 ACK短暂
TIME_WAIT已发送最后 ACK,等待 2MSL60 秒

五、性能优化实践

1. 减少握手次数

① HTTP Keep-Alive

http
Connection: keep-alive

# 多个请求复用同一个 TCP 连接
GET /index.html
GET /style.css
GET /script.js
# ↑ 只需 1 次握手,而不是 3 次

② HTTP/2 多路复用

  • 单个连接并发处理多个请求
  • 彻底消除多次握手开销

③ TCP Fast Open(TFO)

bash
echo 3 > /proc/sys/net/ipv4/tcp_fastopen
  • 首次连接:仍需 3 次握手
  • 后续连接:在 SYN 中携带数据,节省 1 个 RTT

2. 内核参数优化

bash
# 1. 增加半连接队列
echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog

# 2. 启用 SYN Cookie
echo 1 > /proc/sys/net/ipv4/tcp_syncookies

# 3. 启用端口复用
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse

# 4. 调整 TIME_WAIT 回收时间(谨慎使用)
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout

# 5. 增加本地端口范围
echo 1024 65535 > /proc/sys/net/ipv4/ip_local_port_range

3. 监控 TCP 连接

bash
# 统计各状态连接数
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

# 输出示例:
# ESTABLISHED 120
# TIME_WAIT 45
# CLOSE_WAIT 3

六、面试高频问题

Q1: 为什么 TCP 握手需要三次?

两次不够

  • 无法防止历史连接的重复初始化
  • 服务器可能单方面建立连接,浪费资源
  • 无法确保双方收发能力都正常

三次刚好

  • 第一次:客户端表明"我想建立连接"
  • 第二次:服务器回应"好的,我也想"
  • 第三次:客户端确认"我收到了你的回应"
  • ✅ 双方都确认了对方的收发能力
  • ✅ 同步了初始序列号
  • ✅ 防止了旧连接干扰

Q2: 为什么挥手需要四次?

因为 TCP 是全双工的,两个方向需分别关闭:

  1. 第一次:客户端说"我不再发送数据了"(FIN)
  2. 第二次:服务器说"我知道了"(ACK),但可能还有数据要发送
  3. 第三次:服务器处理完后说"我也不再发送了"(FIN)
  4. 第四次:客户端说"我知道了"(ACK)

关键点:第二次和第三次不能合并,因为服务器可能需要时间处理剩余数据(CLOSE_WAIT 状态允许继续发送)。


Q3: TIME_WAIT 为什么要等待 2MSL?

MSL(Maximum Segment Lifetime):报文段在网络中的最长生存时间(通常 30 秒)。

等待 2MSL 的原因

  1. 确保最后一个 ACK 到达:如果丢失,服务器会在 1MSL 内重传 FIN,客户端需在 2MSL 内收到并重发 ACK
  2. 防止旧报文干扰新连接:等待 2MSL 确保网络中所有旧报文消失

公式

2MSL = 发送方最大等待时间 + 接收方最大等待时间
     = 30s + 30s = 60s

Q4: 如何解决大量 TIME_WAIT 连接?

方案一:启用端口复用

bash
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse

允许新连接复用 TIME_WAIT 状态的端口。

方案二:使用连接池

  • HTTP Keep-Alive 复用连接
  • 数据库连接池
  • Redis 连接池

方案三:升级协议

  • HTTP/2 多路复用,减少连接数
  • WebSocket 长连接

⚠️ 注意:不要随意缩短 TIME_WAIT 时间,可能导致旧报文干扰!


Q5: SYN Flood 如何防御?

多层防御策略

  1. 内核层面

    • 启用 SYN Cookie:tcp_syncookies = 1
    • 增加半连接队列:tcp_max_syn_backlog = 2048
    • 缩短超时时间:tcp_synack_retries = 3
  2. 防火墙层面

    bash
    iptables -A INPUT -p tcp --syn -m limit --limit 1/s -j ACCEPT
    iptables -A INPUT -p tcp --syn -j DROP
  3. 应用层面

    • 使用负载均衡器(Nginx、HAProxy)
    • 部署 WAF
    • 接入云防护(阿里云 DDoS、Cloudflare)
  4. 监控告警

    • 监控 SYN_RECV 状态连接数
    • 异常时自动触发防护

七、知识扩展

QUIC 协议(HTTP/3 基础)

QUIC 基于 UDP 实现可靠传输

优势

  • 0-RTT 建连:利用之前连接信息直接发送数据
  • 无队头阻塞:多路复用在传输层实现
  • 连接迁移:切换网络(WiFi → 4G)无需重连
  • 内置加密:强制使用 TLS 1.3

对比 TCP

TCP + TLS 1.3: 握手耗时 2-3 RTT
QUIC:          首次 1 RTT,后续 0 RTT ✓

八、总结记忆口诀

🤝 三次握手建连接
   一同二应三确认
   
👋 四次挥手断连接
   一请二应三请四应
   
⏱️  TIME_WAIT 等 2MSL
   防丢包来防干扰
   
🛡️  SYN Flood 要防御
   Cookie 限流加监控
   
⚡ 性能优化有妙招
   复用连接减握手

核心要点

  • 三次握手:同步序列号、确认收发能力、防止旧连接
  • 四次挥手:全双工需分别关闭、CLOSE_WAIT 允许继续发送
  • TIME_WAIT:等待 2MSL 确保 ACK 到达、清理旧报文
  • 性能优化:Keep-Alive、HTTP/2、连接池、TFO
  • 安全防护:SYN Cookie、限流、监控告警
最近更新