UDP(User Datagram Protocol)是一种无连接、面向数据报的传输层协议,它在 IP 之上增加 端口、长度和校验和,但不提供连接建立、可靠重传、顺序保证或拥塞控制。
核心问题
有些应用不想要 TCP 的连接状态、重传等待和有序队列,而是希望“我给你一个报文,你尽快发出去;丢了、乱序了、重复了,由应用自己决定怎么办”。UDP 解决的是这种低开销、保留消息边界、由应用自定义可靠性的传输需求。
UDP 并不是“不可靠所以没用”。它更准确的边界是:内核只提供最小传输封装,可靠性、重试、排序、拥塞控制和加密通常放到应用协议或上层库里。
核心机制
UDP 的通信单位是数据报。一次 sendto() 对应一个 UDP datagram,接收方一次 recvfrom() 通常取出一个完整数据报;这和 TCP 的字节流不同。
UDP 头部很小:
源端口 | 目的端口 | 长度 | 校验和核心对象是:
| 对象 | 含义 |
|---|---|
| 源端口 | 发送方用于接收响应的端口,某些场景可以为 0 |
| 目的端口 | 接收方应用绑定的端口 |
| 长度 | UDP 头部加负载的长度 |
| 校验和 | 检测头部、负载和伪首部中的传输错误 |
| 数据报负载 | 应用交给 UDP 的一整块消息 |
UDP 没有三次握手。典型流程是:
服务端:socket(AF_INET, SOCK_DGRAM) -> bind(IP, port) -> recvfrom()
客户端:socket(AF_INET, SOCK_DGRAM) -> sendto(data, server_ip, server_port)操作系统收到 UDP 包后,会根据协议、目的 IP 地址 和目的 端口 找到绑定的 Socket,把整个数据报放入接收队列。如果队列满了,包可能直接被丢弃。
UDP 也可以调用 connect(),但这不是 TCP 式握手。对 UDP 来说,connect() 通常只是给 socket 记录默认对端,并让内核过滤非该对端的数据报,使应用可以使用 send() / recv()。
工程用途
- DNS 查询:小请求小响应适合 UDP,必要时再回退或升级到 TCP。
- 实时音视频和游戏:旧数据过期后重传价值低,应用更关注延迟和抖动。
- QUIC / HTTP/3:QUIC 在 UDP 之上自己实现连接、可靠性、拥塞控制和加密。
- 服务发现和监控上报:允许少量丢包,追求简单和低开销。
观察指标包括丢包率、乱序率、数据报大小、接收队列溢出、内核 UDP drops、应用重试率、NAT 映射超时和路径 MTU 问题。
边界与常见坑
- UDP 保留消息边界:一次数据报是一个消息单位;但超过路径 MTU 可能被 IP 分片,分片丢一个就会导致整个数据报不可用。
- UDP 不保证到达:发送成功只表示交给本机内核或网络栈,不表示对端收到。
- UDP 不保证顺序:应用如果关心顺序,需要自己带序号和重排逻辑。
- UDP 不自带拥塞控制:大流量 UDP 应用必须自己限速,否则可能伤害网络并导致大量丢包。
- UDP 不是天然低延迟万能药:如果应用层补了复杂重传和排序,也可能重新引入延迟。
- 防火墙和 NAT 更敏感:很多网络设备会更快回收 UDP 映射,需要心跳或重建逻辑。