跳转到内容

传输层:TCP 与 UDP

TCP 在不可靠的 IP 之上重建了三个承诺:可靠(不丢)、按序、流量控制——外加最重要的拥塞控制(对网络友好)。本章讲三次握手、滑动窗口、重传与拥塞控制,以及 QUIC 为什么把传输层搬进用户态(→ I/O 模型与事件驱动)。

UDP 只加两件事:端口(多路分解)与校验和——不保证到达、不保证顺序、不重传。它的用武之地:延迟优先于完整性(实时音视频、游戏)、应用自己重建可靠性(QUIC)、以及广播/多播。

序列号 + 确认(ACK):每个字节有编号,接收方确认收到的位置;超时重传(RTO 估计自 RTT)与快速重传(三个重复 ACK 即重传,不等超时)。按序由接收方重组缓冲保证。

滑动窗口:接收方通告剩余缓冲(rwnd),发送方不超过——防止快发送方淹没慢接收方(这是端系统的自我保护,与拥塞控制不同)。

SYN → SYN-ACK → ACK,双方交换初始序列号(ISN)并确认——两次不安全(旧的重复 SYN 会建立幽灵连接),四次冗余。挥手(FIN)四次因为半关闭(一方还能发)。

拥塞控制防止发送方淹没网络——发送方探测可用带宽,丢包即减速。拥塞控制是互联网没有崩溃的原因:没有它,端系统会把路由器的缓冲打满、丢包率爆炸、 everyone 变慢(拥塞崩溃)。

经典算法(TCP Reno):

  1. 慢启动:窗口从 1 MSS 指数增长到阈值;
  2. 拥塞避免:线性增长(每 RTT +1);
  3. 丢包反应:超时(窗口归 1)或快速重传(减半)——AIMD(加性增、乘性减)。

AIMD 的哲学:用丢包作为拥塞信号——路由器缓冲满 = 丢包 = 减速。问题有二:

  • 长肥管道(带宽 × RTT 大)上,从"丢包"到"知道"要一个 RTT,窗口震荡大——为什么 TCP 在长肥管道上慢;
  • 缓冲膨胀(bufferbloat):大缓冲让丢包来得太晚,延迟先爆炸(排队延迟数百 ms)。

现代算法:

  • Cubic(Linux 默认):用三次函数增长,丢包后快速恢复——对长肥管道友好;
  • BBR(Google):以带宽与 RTT 建模而非丢包——直接估计 BDP(带宽×延迟),把发送速率控制在瓶颈附近,不依赖丢包信号,bufferbloat 网络上延迟低一个量级。

队头阻塞(head-of-line blocking):TCP 保证字节流按序——一个包丢了,后面所有已到的字节都要等它重传,应用层看到的延迟是整个重传周期。HTTP/2 的多路复用(→ HTTP/1.1、HTTP/2 与 HTTP/3)把多个流塞进一条 TCP 连接,反而放大了队头阻塞:一条流的丢包卡住所有流。

QUIC 的对策:把传输层搬到用户态以便快速演进——UDP 之上重建可靠传输,每个流独立重传(流内按序、流间独立),消除队头阻塞。顺带:TLS 1.3 内建(握手与传输合并)、连接迁移(连接 ID 不绑定 IP,换网不断线)、0-RTT 恢复。HTTP/3 = HTTP over QUIC。

为什么放用户态:内核 TCP 改一次要十年(内核版本、部署面);用户态协议随应用发布——演进速度成为设计目标(与端到端原则一脉相承,→ 分层模型与 IP)。

  • UDP 极简:延迟优先或自建可靠(QUIC)时用它
  • TCP 三承诺:可靠(序列号+重传)、按序(重组)、流量控制(rwnd);三次握手安全在序列号交换
  • 拥塞控制是互联网没有崩溃的原因;AIMD 用丢包信号,Cubic 适配长肥管道
  • BBR 以带宽与 RTT 建模而非丢包,bufferbloat 上延迟低一个量级
  • 队头阻塞:TCP 按序让一个丢包包住后面所有字节;QUIC 每流独立重传消除它
  • QUIC 把传输层搬到用户态以便快速演进