💻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

详细过程:

  1. 服务端调用 listen(),进入 LISTEN 状态
  2. 客户端调用 connect(),发送 SYN,进入 SYN_SENT
  3. 服务端收到 SYN,回复 SYN+ACK,进入 SYN_RCVD
  4. 客户端收到 SYN+ACK,发送 ACK,进入 ESTABLISHED
  5. 服务端收到 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 · 计算机网络