TCP 三次握手与四次挥手
📚 相关文档
- 从 URL 到页面渲染 - 完整流程概览
一、TCP 协议概述
TCP(Transmission Control Protocol) 是核心传输层协议。
核心特性:
- ✅ 面向连接:通信前必须建立连接
- ✅ 可靠传输:不丢失、不重复、按序到达
- ✅ 全双工:双方可同时收发
- ✅ 流量控制:防止发送过快
- ✅ 拥塞控制:避免网络过载
对比 UDP:
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠 | 不可靠 |
| 速度 | 较慢 | 快 |
| 应用 | 网页、文件、邮件 | 视频直播、DNS、游戏 |
二、三次握手详解
1. 为什么需要三次?
目标:
- ✅ 双方都具备收发能力
- ✅ 同步初始序列号(ISN)
- ✅ 防止历史连接干扰
两次不够的原因:
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. 三次握手流程
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 但不完成握手,耗尽服务器资源。
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
echo 1 > /proc/sys/net/ipv4/tcp_syncookies- 不立即分配资源
- 根据客户端信息计算 Cookie
- 收到正确 ACK 才分配资源
② 增加半连接队列
echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog③ 防火墙限流
iptables -A INPUT -p tcp --syn -m limit --limit 1/s -j ACCEPT
iptables -A INPUT -p tcp --syn -j DROP2. TIME_WAIT 状态
定义:主动关闭方发送最后一个 ACK 后进入的状态,持续 2MSL(通常 60 秒)。
为什么需要 TIME_WAIT?
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: 丢弃过期报文 ✓原因:
- ✅ 确保最后一个 ACK 到达:如果丢失,服务器会重传 FIN
- ✅ 防止旧报文干扰新连接:等待网络中所有旧报文消失
问题:高并发下大量 TIME_WAIT 占用端口资源。
解决方案:
① 启用端口复用
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse② 使用连接池
- HTTP Keep-Alive
- HTTP/2 多路复用
- 数据库连接池
⚠️ 注意:不要随意缩短 TIME_WAIT 时间!
四、四次挥手详解
1. 为什么是四次?
原因:TCP 是全双工的,两个方向需分别关闭。
客户端 → 服务器:需要关闭
服务器 → 客户端:也需要关闭
总共需要 4 次交互2. 四次挥手流程
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: 状态: CLOSED3. 各状态说明
| 状态 | 说明 | 持续时间 |
|---|---|---|
| FIN_WAIT_1 | 已发送 FIN,等待 ACK | 短暂 |
| FIN_WAIT_2 | 已收到 ACK,等待对方 FIN | 可能较长 |
| CLOSE_WAIT | 已收到 FIN,等待应用层关闭 | 取决于应用 |
| LAST_ACK | 已发送 FIN,等待最后 ACK | 短暂 |
| TIME_WAIT | 已发送最后 ACK,等待 2MSL | 60 秒 |
五、性能优化实践
1. 减少握手次数
① HTTP Keep-Alive
Connection: keep-alive
# 多个请求复用同一个 TCP 连接
GET /index.html
GET /style.css
GET /script.js
# ↑ 只需 1 次握手,而不是 3 次② HTTP/2 多路复用
- 单个连接并发处理多个请求
- 彻底消除多次握手开销
③ TCP Fast Open(TFO)
echo 3 > /proc/sys/net/ipv4/tcp_fastopen- 首次连接:仍需 3 次握手
- 后续连接:在 SYN 中携带数据,节省 1 个 RTT
2. 内核参数优化
# 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_range3. 监控 TCP 连接
# 统计各状态连接数
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 是全双工的,两个方向需分别关闭:
- 第一次:客户端说"我不再发送数据了"(FIN)
- 第二次:服务器说"我知道了"(ACK),但可能还有数据要发送
- 第三次:服务器处理完后说"我也不再发送了"(FIN)
- 第四次:客户端说"我知道了"(ACK)
关键点:第二次和第三次不能合并,因为服务器可能需要时间处理剩余数据(CLOSE_WAIT 状态允许继续发送)。
Q3: TIME_WAIT 为什么要等待 2MSL?
答:
MSL(Maximum Segment Lifetime):报文段在网络中的最长生存时间(通常 30 秒)。
等待 2MSL 的原因:
- 确保最后一个 ACK 到达:如果丢失,服务器会在 1MSL 内重传 FIN,客户端需在 2MSL 内收到并重发 ACK
- 防止旧报文干扰新连接:等待 2MSL 确保网络中所有旧报文消失
公式:
2MSL = 发送方最大等待时间 + 接收方最大等待时间
= 30s + 30s = 60sQ4: 如何解决大量 TIME_WAIT 连接?
答:
方案一:启用端口复用
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse允许新连接复用 TIME_WAIT 状态的端口。
方案二:使用连接池
- HTTP Keep-Alive 复用连接
- 数据库连接池
- Redis 连接池
方案三:升级协议
- HTTP/2 多路复用,减少连接数
- WebSocket 长连接
⚠️ 注意:不要随意缩短 TIME_WAIT 时间,可能导致旧报文干扰!
Q5: SYN Flood 如何防御?
答:
多层防御策略:
内核层面:
- 启用 SYN Cookie:
tcp_syncookies = 1 - 增加半连接队列:
tcp_max_syn_backlog = 2048 - 缩短超时时间:
tcp_synack_retries = 3
- 启用 SYN Cookie:
防火墙层面:
bashiptables -A INPUT -p tcp --syn -m limit --limit 1/s -j ACCEPT iptables -A INPUT -p tcp --syn -j DROP应用层面:
- 使用负载均衡器(Nginx、HAProxy)
- 部署 WAF
- 接入云防护(阿里云 DDoS、Cloudflare)
监控告警:
- 监控 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、限流、监控告警