💻CS 计算机网络
TCP 三次握手与四次挥手
难度:⭐ | 高频指数:🔥🔥🔥
面试回答
常见问法
- TCP 三次握手的过程是什么?
- 为什么是三次握手,不是两次?
- 四次挥手的过程是什么?
- TIME_WAIT 状态的作用是什么?
- 服务器出现大量 TIME_WAIT 怎么处理?
- 三次握手中如果丢包了会怎样?
回答
三次握手(建立连接):
客户端 服务端
│ │
│── SYN(seq=x) ────────→│ 第一次:客户端发送 SYN
│ │
│←── SYN+ACK(seq=y, │ 第二次:服务端回复 SYN+ACK
│ ack=x+1) ─────────│
│ │
│── ACK(ack=y+1) ──────→│ 第三次:客户端发送 ACK
│ │
│ 连接建立,可以传数据 │
- 第一次:客户端发 SYN,表示”我要建立连接”,携带初始序列号 x
- 第二次:服务端回 SYN+ACK,表示”同意连接”,携带自己的初始序列号 y,确认号 x+1
- 第三次:客户端发 ACK,确认号 y+1,连接建立
为什么不能两次:
两次握手无法确认客户端的接收能力。如果客户端的一个旧 SYN 延迟到达服务端,服务端回复 SYN+ACK 后就认为连接建立了,但客户端不会理会这个过期连接,导致服务端资源浪费。三次握手让服务端收到第三次 ACK 后才确认连接,避免了这个问题。
四次挥手(断开连接):
主动关闭方 被动关闭方
│ │
│── FIN(seq=u) ────────→│ 第一次:主动方发 FIN
│ │
│←── ACK(ack=u+1) ──────│ 第二次:被动方回 ACK
│ │
│ (被动方可能还有数据要发)│
│ │
│←── FIN(seq=w) ─────────│ 第三次:被动方发 FIN
│ │
│── ACK(ack=w+1) ──────→│ 第四次:主动方回 ACK
│ │
│ 等待 2MSL(TIME_WAIT) │
追问
1. 为什么挥手要四次,不能三次?
因为 TCP 是全双工的,每个方向的关闭是独立的。被动方收到 FIN 后可能还有数据没发完,所以先回 ACK 表示”我知道你要关了”,等自己数据发完再发 FIN。如果被动方没有数据要发,可以把 ACK 和 FIN 合并(延迟确认),变成三次。
2. TIME_WAIT 的作用?
- 确保最后一个 ACK 能到达对方(如果丢了,对方会重发 FIN)
- 让本次连接的所有报文在网络中消失,避免影响新连接
3. TIME_WAIT 持续多久?
2 × MSL(Maximum Segment Lifetime),Linux 默认 MSL = 60 秒,所以 TIME_WAIT 持续 2 分钟。
原理展开
1. 三次握手的状态变迁
客户端状态:CLOSED → SYN_SENT → ESTABLISHED
服务端状态:CLOSED → LISTEN → SYN_RCVD → ESTABLISHED
详细过程:
- 服务端调用 listen(),进入 LISTEN 状态
- 客户端调用 connect(),发送 SYN,进入 SYN_SENT
- 服务端收到 SYN,回复 SYN+ACK,进入 SYN_RCVD
- 客户端收到 SYN+ACK,发送 ACK,进入 ESTABLISHED
- 服务端收到 ACK,进入 ESTABLISHED
2. 为什么需要初始序列号(ISN)
- ISN 是随机生成的,不是从 0 开始
- 防止旧连接的延迟报文被新连接误收
- 防止 TCP 序列号预测攻击
- Linux 使用基于时间和连接四元组的算法生成 ISN
3. SYN Flood 攻击
攻击者伪造大量不同源 IP 的 SYN 包 → 服务端为每个 SYN 分配资源(半连接队列)
→ 服务端回复 SYN+ACK 但收不到第三次 ACK → 半连接队列被占满 → 正常连接无法建立
防御措施:
- SYN Cookie:不在半连接队列中保存状态,把状态编码到 SYN+ACK 的序列号中
- 增大半连接队列(backlog)
- 减小 SYN+ACK 重试次数
- 防火墙限流
4. 四次挥手的状态变迁
主动关闭方:ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED
被动关闭方:ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED
关键状态:
- CLOSE_WAIT:被动方收到 FIN 但还没发自己的 FIN。如果服务器大量 CLOSE_WAIT,说明应用层没有及时 close()。
- TIME_WAIT:主动关闭方发完最后一个 ACK 后等待 2MSL。
5. 大量 TIME_WAIT 的问题和处理
为什么会出现大量 TIME_WAIT:
- 服务器主动关闭连接(如 HTTP 短连接服务器)
- 高并发短连接场景
影响:
- 占用端口资源(源端口有限)
- 占用内存(每个 TIME_WAIT 约 200 字节)
处理方法:
# 1. 开启端口复用(最常用)
sysctl -w net.ipv4.tcp_tw_reuse=1
# 2. 增大端口范围
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
# 3. 减小 TIME_WAIT 超时(不推荐直接改)
# Linux 的 TIME_WAIT 是固定 60 秒,不能通过 sysctl 修改
更好的方案:
- 使用长连接(HTTP Keep-Alive)减少连接建立/断开
- 让客户端主动关闭(TIME_WAIT 在客户端)
- 使用连接池
6. 三次握手中的丢包处理
| 丢包位置 | 处理方式 |
|---|---|
| 第一次 SYN 丢失 | 客户端超时重传 SYN(指数退避) |
| 第二次 SYN+ACK 丢失 | 服务端超时重传 SYN+ACK |
| 第三次 ACK 丢失 | 服务端超时重传 SYN+ACK;客户端已经 ESTABLISHED,发数据时会带 ACK |
7. 半连接队列和全连接队列
SYN 到达 → 放入半连接队列(SYN Queue)
→ 收到第三次 ACK → 移入全连接队列(Accept Queue)
→ 应用调用 accept() → 从全连接队列取出
- 半连接队列满:丢弃新 SYN 或启用 SYN Cookie
- 全连接队列满:丢弃连接或发 RST
listen(fd, backlog)的 backlog 控制全连接队列大小
8. TCP 连接的异常关闭
RST 报文:
- 连接不存在时收到数据
- 进程崩溃(内核发 RST)
- 设置 SO_LINGER 后 close(立即发 RST,跳过四次挥手)
// 设置 SO_LINGER,close 时立即发 RST
struct linger lg = {1, 0}; // l_onoff=1, l_linger=0
setsockopt(fd, SOL_SOCKET, SO_LINGER, &lg, sizeof(lg));
close(fd); // 立即发 RST,不走四次挥手
易错点
- 说”三次握手是为了确认双方都能收发”——更准确的说法是防止历史连接的 SYN 导致服务端误建连接。
- 混淆 TIME_WAIT 和 CLOSE_WAIT——TIME_WAIT 在主动关闭方,CLOSE_WAIT 在被动关闭方。
- 说”tcp_tw_recycle 可以解决 TIME_WAIT”——这个选项在 NAT 环境下有严重问题,Linux 4.12 已移除。
- 忘记说 ISN 是随机的——如果从 0 开始,旧报文可能被新连接误收。
- 说”四次挥手一定是四次”——如果被动方没有数据要发,可以合并为三次(FIN+ACK 合并)。
- 大量 CLOSE_WAIT 和大量 TIME_WAIT 混淆——CLOSE_WAIT 是应用层没 close,TIME_WAIT 是正常的等待。
记忆技巧
- 三次握手口诀:SYN → SYN+ACK → ACK(请求、同意、确认)
- 四次挥手口诀:FIN → ACK → FIN → ACK(我关、知道、我也关、知道)
- TIME_WAIT 两个作用:等最后 ACK 重传 + 让旧报文消失
- 状态记忆:主动方走 FIN_WAIT,被动方走 CLOSE_WAIT
- 为什么三次一句话:防止历史 SYN 建立错误连接
面试速答版
TCP 三次握手:客户端发 SYN,服务端回 SYN+ACK,客户端发 ACK。三次而不是两次是为了防止历史连接的延迟 SYN 导致服务端误建连接。四次挥手:主动方发 FIN,被动方回 ACK,被动方发完数据后发 FIN,主动方回 ACK 并进入 TIME_WAIT。TIME_WAIT 持续 2MSL(Linux 约 2 分钟),作用是确保最后 ACK 到达和让旧报文消失。大量 TIME_WAIT 的处理:开启 tcp_tw_reuse、使用长连接、让客户端主动关闭。大量 CLOSE_WAIT 说明应用层没有及时 close。
Related · 计算机网络