🔗 TCP 协议
三次握手 · 四次挥手 · 可靠传输 · 流量控制 · 拥塞控制
1. TCP 为什么是三次握手?两次不行吗?
流程:
- 客户端 → 服务端:SYN=1, seq=x(我要连接)
- 服务端 → 客户端:SYN=1, ACK=1, seq=y, ack=x+1(收到,我也要连)
- 客户端 → 服务端:ACK=1, seq=x+1, ack=y+1(收到,连接建立)
为什么两次不行:
- 防失效的连接请求:客户端第一次 SYN 因网络阻塞迟到,客户端重发 SYN 建立连接并传输数据、释放。此时旧 SYN 到达服务端——若两次握手,服务端直接建立连接,浪费资源且数据错乱。三次握手时,服务端收到旧 SYN 回复 SYN+ACK,但客户端不会回 ACK(没这个连接),服务端收不到确认就不会建立。
- 确认双方收发能力:三次握手双方都确认了"我能发、你能收"。
🎯 面试要点
- SYN 洪泛攻击:攻击者只发 SYN 不回 ACK,占满服务端半连接队列 → 缓解:SYN Cookie、缩短超时
- 三次握手时客户端可以携带数据(第三次),服务端不能(防止 SYN 洪泛利用)
- ISN(初始序列号)随机化防伪造
2. TCP 四次挥手?为什么是四次?TIME_WAIT 为什么等 2MSL?
流程(客户端主动关闭为例):
- 客户端 → 服务端:FIN(我不再发了)
- 服务端 → 客户端:ACK(收到,但我可能还有数据要发)——此时进入半关闭
- 服务端数据发完 → 客户端:FIN(我也不发了)
- 客户端 → 服务端:ACK,然后进入 TIME_WAIT 状态等 2MSL
为什么四次:TCP 是全双工,两个方向的关闭独立。服务端收到 FIN 后可能还有数据要发,所以 ACK 和 FIN 分开发。
为什么 TIME_WAIT 等 2MSL(最大报文段生存时间 ×2):
- 保证最后一个 ACK 送达:ACK 丢了,服务端会重发 FIN,客户端要有时间回应(1MSL 内收到重发 FIN)
- 让旧连接的报文在网络中消亡:防止新连接复用端口时收到旧连接的迟到报文(2MSL 保证所有报文过期)
🎯 面试要点
- 大量 TIME_WAIT 的排查与解决(见真题页):多出现在服务端主动关闭 + 短连接场景
- CLOSE_WAIT 堆积:服务端收到 FIN 但应用没调 close(代码没释放)——典型线上问题
- netstat 查看:netstat -ant | grep TIME_WAIT
3. TCP 如何保证可靠传输?
- 校验和:数据段校验,损坏则丢弃(触发重传)
- 确认应答(ACK):收到数据回 ACK,未确认的重传
- 超时重传(RTO):发送后超时未 ACK 则重发;RTO 动态计算(RTT 平滑估计)
- 快速重传:收到 3 个重复 ACK 立即重传(不等超时)
- 滑动窗口:允许发送方连续发多个报文(流水线),接收方按序缓存乱序包
- 序号机制:每个字节一个序号,接收方按序号重组
🎯 面试要点
- SACK(选择性确认):告知发送方哪些段丢了,只重传缺失段——高带宽长链路必备
- 重传与滑动窗口结合:窗口内可连续发送,提高吞吐
4. 流量控制和拥塞控制的区别?
| 对比 | 流量控制 | 拥塞控制 |
|---|---|---|
| 目的 | 防止"发送方太快压垮接收方"(端到端) | 防止"发送太快导致网络中间设备拥塞"(全网) |
| 实现 | 滑动窗口(接收方通告 rwnd) | 拥塞窗口 cwnd + 四个算法 |
拥塞控制四算法:
- 慢启动:cwnd 从 1 个 MSS 开始(教科书定义;现代实现初始窗口为 10 个 MSS,RFC 6928),每 RTT 翻倍(指数)直到慢启动阈值 ssthresh
- 拥塞避免:超过 ssthresh 后线性增长(每 RTT +1)
- 快重传:收到 3 个重复 ACK 立即重传
- 快恢复:快重传后 ssthresh 减半,cwnd 从新 ssthresh 线性增长(不回到 1)
发生超时则回到慢启动(cwnd=1)。有效发送窗口 = min(rwnd, cwnd)。
🎯 面试要点
- "重传是拥塞的信号":网络拥塞表现为丢包 → 拥塞控制就是根据丢包调整速率
- 现代变体:BBR(Google,基于带宽探测而非丢包),提一句显深度