传输层:TCP 与 UDP(面试第一大难点)
3.1 UDP:用户数据报协议
特点:
- 无连接:发送前不需要握手建连;
- 不可靠:不保证到达、不保证顺序、不确认、不重传;
- 面向报文:应用层交给 UDP 多长的报文,UDP 就照样发送,保留报文边界(一次 send 对应一次接收);
- 首部开销小:仅 8 字节(源端口、目的端口、长度、校验和);
- 支持一对多:支持单播、组播、广播;
- 实时性好:没有拥塞控制、重传的等待,延迟稳定。
适用场景:DNS 查询、DHCP、音视频直播(RTP)、网络游戏、SNMP、以及 QUIC(HTTP/3 的底层传输)。
3.2 TCP:传输控制协议
特点:
- 面向连接:通信前必须三次握手建立连接;
- 可靠传输:确认应答、超时重传、数据校验、去重与按序交付;
- 面向字节流:数据被当成无边界的字节流(需要应用层自己处理粘包/拆包);
- 全双工:双方都可同时收发,两端各有发送/接收缓冲区;
- 流量控制 + 拥塞控制:防止淹没接收方、防止压垮网络;
- 首部 20~60 字节。
TCP 靠什么保证可靠(靠机制和字段)——面试标准答法:
- 序列号 + 确认应答(ACK):每个字节编号,接收方确认已收到的数据边界;
- 超时重传:发出后启动定时器,超时未收到 ACK 就重传(RTO 根据往返时延 RTT 自适应计算);
- 快速重传:收到 3 个重复 ACK 立即重传,不必等超时;
- 校验和:检测首部和数据在传输中的比特差错,出错直接丢弃;
- 去重与排序:按序列号重组乱序到达的段,丢弃重复段;
- 流量控制(滑动窗口):不淹没接收方;
- 拥塞控制:不压垮网络。
3.3 三次握手(必考)
流程(初始双方处于 CLOSED / LISTEN):
- 第一次:客户端 → 服务端发 SYN 报文,
seq = x(客户端初始序列号 ISN),客户端进入 SYN_SENT; - 第二次:服务端 → 客户端发 SYN + ACK 报文,
seq = y(服务端 ISN),ack = x + 1,服务端进入 SYN_RCVD; - 第三次:客户端 → 服务端发 ACK 报文,
ack = y + 1,双方进入 ESTABLISHED。
第三次握手理论上可以携带应用数据(前两次不行)。
为什么是三次,不是两次?
- 核心原因:防止历史(失效)连接请求建立连接,同步双方初始序列号。 两次握手下,若客户端一个旧的 SYN 迟到,服务端回 SYN+ACK 后就直接建连并占用资源等待数据,而客户端根本不理会,服务端白白等待(旧连接的坑);
- 三次握手让双方都确认了「自己的发送、接收能力与对方的发送、接收能力都正常」:
- 客户端:收到 SYN+ACK → 我的发送、接收 OK,对方发送、接收 OK;
- 服务端:收到第三次 ACK → 我的发送、接收 OK,对方发送、接收 OK;
- 两次握手无法让服务端确认「客户端能收到自己的报文」;
- 初始序列号 ISN 随机生成的原因:防止上一次相同四元组连接的历史报文被误当作本次数据;增加外部猜测序列号的难度(防伪造注入)。
扩展:SYN Flood 攻击——攻击者伪造源 IP 大量发 SYN 不回 ACK,服务端 SYN_RCVD 半连接队列被占满。防御:SYN Cookie(不保存半连接状态,把信息编码进 SYN+ACK 的序列号)、调大 backlog、防火墙限速。
3.4 四次挥手(必考)
流程(假设客户端主动关闭):
- 第一次:客户端发 FIN(seq = u),进入 FIN_WAIT_1;
- 第二次:服务端回 ACK(ack = u + 1),进入 CLOSE_WAIT;客户端收到后进入 FIN_WAIT_2——此时连接处于半关闭,服务端仍可向客户端发数据;
- 第三次:服务端数据发完,发 FIN(seq = w),进入 LAST_ACK;
- 第四次:客户端回 ACK(ack = w + 1),进入 TIME_WAIT,等待 2MSL 后关闭;服务端收到 ACK 即关闭。
为什么挥手四次,握手三次?
- 握手时 SYN 和 ACK 可以合并成一个 SYN+ACK 报文;
- 挥手时 TCP 是全双工,两个方向要分别关闭:被动方收到 FIN 时可能还有数据没发完(半关闭状态),所以先回 ACK,等数据发完再发 FIN——ACK 与 FIN 通常无法合并(除非数据恰好发完,此时部分实现会合并成 FIN+ACK)。
TIME_WAIT 详解(高频):
- 为什么等待 2MSL(MSL = 报文最大生存时间):
- 保证最后的 ACK 可达:若第四次 ACK 丢失,服务端会重传 FIN;客户端在 TIME_WAIT 期间还能重发 ACK。若直接关闭,服务端将收不到 ACK 而无法正常关闭;
- 让本连接的所有旧报文在网络中自然消亡,防止延迟的报文串进相同四元组的新连接。
- TIME_WAIT 过多的危害:出现在主动关闭方(通常是客户端或负载均衡器)。高并发短连接场景会占用大量端口/内存,客户端源端口耗尽(可用端口约 6 万个)后无法建新连接。
- 优化:使用长连接 / 连接池复用;
tcp_tw_reuse(仅客户端、需时间戳支持)复用 TIME_WAIT 端口;so_reuseaddr允许监听复用。
CLOSE_WAIT 详解(高频):
- 出现在被动关闭方:收到对方 FIN、回完 ACK 后进入 CLOSE_WAIT,等待本方应用程序调用 close()。
- 大量 CLOSE_WAIT 堆积 = 代码 Bug:服务端收到 FIN 后一直不调用 close(),连接泄漏。常见原因:异常分支漏关连接、连接池归还逻辑缺陷、线程池打满导致处理不过来、响应完请求没有正确关闭。
- 处理方式:
ss -tanp | grep CLOSE_WAIT定位进程 → 代码审查连接释放逻辑(try/finally 关闭)→ 修复后重启(需要开发一起处理)。
TCP 状态机速记:服务端两大堆积异常——大量 SYN_RCVD(SYN Flood)、大量 CLOSE_WAIT(程序没关连接);客户端常见 TIME_WAIT 堆积(主动关短连接)。
3.5 TCP 流量控制:滑动窗口
- 目的:匹配收发双方的处理速度,不淹没接收方。
- 机制:接收方在每个 ACK 中用 rwnd(接收窗口) 字段通告自己接收缓冲区的剩余空间;发送方未确认的在途数据量不得超过 rwnd。
- 发送窗口内部分为四段:已发送已确认 | 已发送未确认(可发送)| 允许发送未发送 | 不可发送。
- 零窗口:接收方缓冲区满时通告 rwnd=0,发送方暂停发送并周期性发窗口探测报文,防止死锁;接收方腾出空间后通告新窗口。
- 糊涂窗口综合征:接收方每次只腾出很小的窗口导致效率极低,通过延迟通告 + Nagle 算法缓解。
3.6 TCP 拥塞控制(必考,讲现象,不用推公式)
- 目的:防止过多数据注入网络,不压垮网络(与流量控制的区别:流量控制是端到端、保护接收方;拥塞控制是全局性、保护网络)。
- 发送方维护拥塞窗口 cwnd,实际发送窗口 = min(rwnd, cwnd)。
- 四个算法阶段(以拥塞窗口随时间变化描述):
① 慢启动(Slow Start):连接建立初期 cwnd 从小值(如 1~10 MSS)开始,每收到一个 ACK,cwnd 增加;效果是每个 RTT 翻倍(指数增长),直到达到阈值 ssthresh。
② 拥塞避免(Congestion Avoidance):cwnd ≥ ssthresh 后,每个 RTT 只 +1 MSS(线性增长),小心试探网络上限。
③ 快重传(Fast Retransmit):接收方收到失序报文段时重复确认最后有序的 ACK;发送方连续收到 3 个重复 ACK,判定该报文丢失,不等超时立即重传。
④ 快恢复(Fast Recovery):配合快重传使用——发生 3 个重复 ACK 时,令 ssthresh = cwnd / 2,cwnd = 新 ssthresh,直接进入拥塞避免线性增长(不回到慢启动)。
超时触发(重度拥塞):RTO 超时说明网络很糟糕,ssthresh = cwnd / 2,cwnd = 1 MSS,重新慢启动。
一句话总结:丢包轻微(3 个重复 ACK)→ 减半后线性涨;丢包严重(超时)→ 归 1 指数涨。
3.7 TCP vs UDP 对比与选型
| 维度 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(确认、重传、有序) | 尽力而为,不可靠 |
| 数据单位 | 面向字节流(无边界) | 面向报文(有边界) |
| 首部开销 | 20~60 字节 | 8 字节 |
| 拥塞/流量控制 | 有 | 无 |
| 传输效率 | 较低(机制开销) | 高、实时性好 |
| 通信模式 | 一对一 | 单播/组播/广播 |
| 典型应用 | HTTP/HTTPS、SSH、MySQL、邮件 | DNS、DHCP、直播、游戏、QUIC |
选型建议:要完整正确(文件传输、交易、命令执行)→ TCP;要实时低延迟、可容忍丢包(音视频、游戏)→ UDP;要「快且可靠」→ 基于 UDP 在应用层自建可靠机制(QUIC 就是这个思路)。
3.8 面试真题
Q1:为什么握手 3 次、挥手 4 次? 见 3.3 / 3.4——握手可合并(SYN+ACK),挥手因全双工半关闭一般不能合并;三次是双方收发能力都确认 + 防历史连接的最少次数。
Q2:TCP 怎么保证可靠传输? 见 3.2 的七点机制(确认应答、超时/快速重传、校验和、去重排序、流量控制、拥塞控制——靠机制和字段)。
Q3:TIME_WAIT 和 CLOSE_WAIT 分别出现在哪一方、什么原因? 见 3.4:TIME_WAIT 在主动关闭方、等 2MSL;CLOSE_WAIT 在被动关闭方、应用没调 close()。