目录

1. UDP的设计哲学

2. 报文结构与无连接语义

2.1 8字节的固定头部

2.2 无连接状态的数据收发

3. UDP的使用场景与工程局限

3.1 实时通信的适应性

3.2 无流控的隐蔽风险

4. QUIC的改造路径

4.1 在UDP之上重建可靠传输

4.2 0-RTT握手:消除建连往返

4.3 流多路复用:结构性地消除队头阻塞

5. 从内核到用户态:端到端原则的再发现

6. 结语

参考文献


1. UDP的设计哲学

TCP的复杂性源于其对可靠性的承诺——连接管理、序号跟踪、确认与重传、流量控制、拥塞控制,共计数千行的内核态C代码,RFC 793定义了协议行为,后续数十个RFC陆续叠加扩展。UDP则只做两件事:通过端口号将主机到主机的IP交付扩展为进程到进程的交付,通过校验和对数据做概率性差错检测。

除此以外,UDP不建立连接、不维护状态、不保证交付、不对乱序做重排、不控制发送速率。应用层调用sendto()后,数据加上8字节UDP头部立即进入IP层发送,没有任何确认机制。这种极简设计使UDP成为应用层传输协议创新的最佳载体——应用层可以在UDP基础之上按需构建可靠性、拥塞控制和多路复用,而不被操作系统内核中固化的TCP协议行为约束。

UDP的缺陷与其优点一体两面:不控制发送速率意味着高带宽用满UDP可能将链路压垮;不处理丢包意味着实时应用中丢失的数据需要应用层自行判废或用FEC(前向纠错)补偿;无连接意味着传统负载均衡设备的NAT穿越和会话保持需要额外处理。


2. 报文结构与无连接语义

2.1 8字节的固定头部

UDP头部仅包含四个字段:16位源端口、16位目标端口、16位长度(包括头部和数据的总字节数)、16位校验和。没有序号——收发双方的段边界完全独立;没有确认号——发送方无从知晓数据是否到达;没有窗口字段——接收方无法向发送方反馈缓冲区状态;没有标志位——没有SYN/FIN/RST这样的连接控制语义。

在IPv4中,UDP校验和为可选字段,若发送方将其置零则接收方跳过校验。IPv6强制要求校验和启用,因为IPv6头部本身没有校验。UDP校验和的覆盖范围包括整个UDP段加上一个来自IP层的伪头部——源IP、目标IP、协议号和UDP长度——防止因为IP层的交付错误导致数据被递交到错误进程。

2.2 无连接状态的数据收发

UDP套接字通过bind()绑定端口后即可任何时刻收发,没有connect()和accept()的概念。在无连接状态下,每个UDP数据报都有自己的目标地址和端口,发送不同目标时仅需在sendto()中修改目标参数。

UDP的无状态性使其成为DNS查询的理想传输协议——DNS的查询和应答各为一个独立的数据报,任何传输可靠性由应用层通过查询ID匹配和计时器重传解决。若TCP承载DNS,每个查询都需三次握手建立连接、传输后四次挥手关闭,在根服务器每秒处理数十万查询的场景下,连接管理的开销远大于UDP应用层自行维护的轻量计时器。


3. UDP的使用场景与工程局限

3.1 实时通信的适应性

语音通话中,200毫秒延迟意味着对话节奏被打乱,后端网络路径上如果TCP在一个丢包处停滞等待超时重传,之后的语音帧全部阻塞等待。UDP允许应用层对丢失的语音帧做最终决策——一个丢失的20毫秒语音帧用静音或预测填充,优于等待它重传达到造成会话延迟。

在线游戏中,每个更新帧携带了当前时刻的游戏状态,一旦过期便无用——玩家已经移动到新位置。UDP允许游戏应用层自行决定哪些状态更新不重要可以丢弃,哪些需要可靠传输(如玩家登录的验证报文)在应用层自行重传。这种按报文定制可靠性的能力是TCP无法提供的——TCP的可靠性是全或无的字节流可靠性,无法在同一个连接内对不同数据赋予不同的可靠性要求。

3.2 无流控的隐蔽风险

UDP最容易忽视的风险是发送速率失控。一个TCP流在路径拥塞时会收到来自网络的信号并自动收缩窗口,而UDP应用如果为了追求低延迟以恒定高码率发送,在瓶颈链路上直接挤满路由器缓冲区,冲掉其他TCP流的同时也使自身大量包被丢弃。

RFC 8085为UDP应用开发者提供了拥塞控制指南,强调UDP应用应当加入类似TCP友好速率调节机制。QUIC在设计中采纳了这一点——在UDP之上实现了完整的拥塞控制,使QUIC流能够与TCP流在瓶颈链路上公平竞争,而非作为无管制的UDP洪流。


4. QUIC的改造路径

4.1 在UDP之上重建可靠传输

QUIC的核心洞察是:TCP的可靠性、拥塞控制、安全传输三项功能可以在用户态独立实现,而UDP的原生无状态性恰恰为这种"用户态TCP"提供了基础。

QUIC连接有自己独立的序号空间。但与TCP不同的是,QUIC的序号是单调递增的数据包编号,与数据段的字节偏移无关——丢包重传时使用的是新的包编号,原始包编号和重传包编号不同,消除了TCP中重传歧义问题。QUIC在所有包头部显式携带独立的时间戳和ACK信息,结合单调递增的包编号,发送端可以精确计算每个包的单独RTT,不依赖累计确认和Karn算法的歧义排除。

4.2 0-RTT握手:消除建连往返

TCP+TLS组合在建立首个连接时需要至少两次往返——TCP三次握手一次,TLS握手一次半到两次。用户看到的是页面经过数百毫秒后才开始加载。

QUIC将传输握手和加密握手融合为一次交互。首次连接时,客户端发送一个包含密钥交换的初始包到服务器,服务器回应包含自己的密钥交换与证书的包。加密通道在一个往返内建立。若客户端之前与服务器通信过,存储了服务器的配置参数,它可以直接在首个QUIC包中发送应用数据——0-RTT。这些应用数据用之前协商的密钥加密,在服务器收到首包后即可解密处理。

0-RTT的安全风险是重放攻击——中间人录制0-RTT数据包后重新发送,服务器可能将过期请求当作新请求处理。QUIC服务器需根据应用层语义决定0-RTT请求的幂等性安全边界——GET请求可允许0-RTT重放,POST请求应等待握手完成再接受。

4.3 流多路复用:结构性地消除队头阻塞

HTTP/2在TCP之上已经实现了字节流的多路复用——一个TCP连接上并发传输多个HTTP请求的资源。但HTTP/2的队头阻塞源自TCP的底层语义:一个TCP段丢失,所有后续到达的段都被阻塞在接收缓冲区等待丢失段重传完成,即使它们属于不同的HTTP流。

QUIC将这个瓶颈从传输层移入应用层内部。QUIC在一个连接的包流中承载多条独立的逻辑流,每条流有自己的流ID和排序空间。丢包只阻塞了所属流的流内排序,不影响其他流的数据递交。一个QUIC包丢失导致该包中承载的若干流的数据需要重传,但其他流的数据包仍持续抵达并可立即交付应用层。

从结构等效的角度,QUIC本质上在UDP之上将TCP的字节流可靠性分解为独立的每流可靠性。这一分解彻底消除了传输层队头阻塞,代价是应用层需要维护多流的流控和多流丢包状态,而不是TCP在内核中提供的统一缓冲管理。


5. 从内核到用户态:端到端原则的再发现

TCP实现在操作系统内核中已近四十年。内核中TCP协议栈的改进受到严重制约——服务器操作系统更新内核的周期以年计,即使实验室有更好拥塞控制算法,也无法在短期内到达最终用户。不同操作系统内核对TCP新扩展的支持进度不一,导致协议创新实际上被操作系统的更新周期锁定。

QUIC将传输协议从内核空间迁移至用户空间,使协议迭代周期从数年缩短至数周。浏览器每六周发布一个新版本,可以搭载新版QUIC实现。服务器端的QUIC库可以在应用更新时一同更新,无需操作系统升级。用户态协议栈的部署灵活性使得传输协议终于可以像应用层协议一样快速演进。

这实质上是Saltzer等1984年提出的端到端原则的又一次印证。将隐含在中间节点(操作系统内核作为"中间层")的功能提升至通信端点(应用进程作为真正的"端点")自主实现,是对变化响应速度的结构性提升。UDP作为传输层最小合约,恰好为这种提升提供了载体。


6. 结语

UDP的轻量级设计在传统教学中通常被描述为"不可靠"的传输方式,是TCP的对立面和替补。但从协议演进的视角,UDP的无连接语义使它成为应用层协议创新的基础设施。QUIC在UDP之上重建了一套可靠传输体系,同时消除TCP在队头阻塞和建连延迟上的结构性缺陷。

这种设计路径为未来的传输协议创新提供了一个开放模板——任何新的传输层机制可以在UDP之上以应用进程级别快速试验和部署,而不需要说服操作系统厂商将新协议合并到内核中。


参考文献

[1] Postel, J. RFC 768: User Datagram Protocol. IETF, 1980.

[2] Iyengar, J., & Thomson, M. RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport. IETF, 2021.

[3] Langley, A., et al. The QUIC transport protocol: Design and Internet-scale deployment. ACM SIGCOMM, 2017.

[4] Eggert, L., Fairhurst, G., & Shepherd, G. RFC 8085: UDP Usage Guidelines. IETF, 2017.

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐