TCP 与 UDP
问题
TCP 和 UDP 有什么区别?TCP 三次握手和四次挥手的过程是什么?
答案
TCP vs UDP 对比
| 维度 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(重传、确认、排序) | 不可靠(尽最大努力交付) |
| 传输方式 | 字节流 | 数据报 |
| 有序性 | 保证顺序 | 不保证顺序 |
| 流量控制 | 滑动窗口 | 无 |
| 拥塞控制 | 有(慢启动等) | 无 |
| 头部开销 | 20 字节 | 8 字节 |
| 速度 | 较慢 | 快 |
| 典型应用 | HTTP、FTP、SMTP | DNS、视频直播、游戏 |
三次握手
- 第一次握手:客户端发送 SYN,进入 SYN_SENT 状态
- 第二次握手:服务端收到 SYN,回复 SYN+ACK,进入 SYN_RCVD 状态
- 第三次握手:客户端收到 SYN+ACK,回复 ACK,双方进入 ESTABLISHED 状态
为什么是三次而不是两次?
两次握手无法防止历史连接的干扰。假设客户端发了一个旧的 SYN(网络延迟),两次握手时服务端收到后直接建立连接,浪费资源。三次握手中客户端收到 SYN+ACK 后能判断是否是过期连接,不回复 ACK 即可拒绝。
四次挥手
- 第一次挥手:客户端发送 FIN,表示不再发送数据
- 第二次挥手:服务端回复 ACK,确认收到(此时服务端可能还有数据要发)
- 第三次挥手:服务端发送 FIN,表示数据发送完毕
- 第四次挥手:客户端回复 ACK,进入 TIME_WAIT 等待 2MSL 后关闭
为什么需要 TIME_WAIT(2MSL)?
- 确保最后的 ACK 能到达服务端:如果服务端没收到 ACK 会重发 FIN,TIME_WAIT 期间可以重新回复
- 确保旧连接的报文段在网络中消失:防止和新连接混淆
可靠传输机制
| 机制 | 说明 |
|---|---|
| 序号与确认 | 每个字节编号,接收方确认收到的序号 |
| 超时重传 | 超过 RTO 未收到 ACK,重新发送 |
| 滑动窗口 | 控制发送速率,接收方通告可用窗口大小 |
| 拥塞控制 | 慢启动 → 拥塞避免 → 快重传 → 快恢复 |
常见面试问题
Q1: 为什么挥手要四次而不是三次?
答案:
握手时服务端可以把 SYN 和 ACK 合并在一次发送。但挥手时,服务端收到 FIN 后可能还有数据没发完,所以先回 ACK(确认收到关闭请求),等数据发完再发 FIN。ACK 和 FIN 不能合并,所以需要四次。
但如果服务端没有数据要发了,TCP 延迟确认机制可能会把 ACK 和 FIN 合并,实际抓包可能只看到三次挥手。
Q2: 大量 TIME_WAIT 怎么处理?
答案:
高并发短连接场景(如 HTTP 1.0)主动关闭方会产生大量 TIME_WAIT,占用端口资源。
解决方案:
tcp_tw_reuse:允许复用 TIME_WAIT 的端口- 使用长连接(HTTP Keep-Alive)减少连接关闭次数
- 让服务端主动关闭(让客户端承担 TIME_WAIT)
Q3: 大量 CLOSE_WAIT 说明什么?
答案:
CLOSE_WAIT 出现在被动关闭方(收到 FIN 但还没发 FIN)。大量 CLOSE_WAIT 说明应用程序没有正确关闭连接——收到对方关闭请求后,自己忘了调用 close()。通常是代码 Bug(如连接/流没有在 finally 中关闭)。
Q4: TCP 粘包/拆包是什么?
答案:
TCP 是字节流协议,不保留消息边界。发送方的多次 write 可能被合并为一次发送(粘包),或一次 write 被拆成多次发送(拆包)。
解决方案:
- 固定长度:每个消息固定长度
- 分隔符:用特殊字符分隔消息(如
\r\n) - 长度前缀:消息头部包含消息体长度(最常用)
Q5: UDP 有什么优势场景?
答案:
- DNS 查询:一问一答,不需要建立连接
- 视频/音频直播:实时性要求高,丢几帧可以接受
- 游戏:低延迟优先,自行在应用层处理可靠性
- QUIC(HTTP/3):基于 UDP 实现可靠传输,兼顾性能和可靠性