udp

05. UDP

0. 本章先解决什么问题

很多人第一次学网络时会觉得:TCP 可靠,所以 TCP 更高级;UDP 不可靠,所以 UDP 像是残缺版本。

这个理解会误导你。UDP 的价值不在于“做不到可靠”,而在于它只提供最小的传输层能力,把是否可靠、如何重传、能否丢弃、怎样控制延迟这些策略交给应用自己决定。

本章要解决三个问题:

  1. UDP 到底提供了什么,又明确不提供什么。
  2. 为什么很多场景宁愿用 UDP,也不愿直接用 TCP。
  3. 如果应用选择 UDP,它自己必须承担哪些设计责任。

先看 UDP 的关键边界:每次发送都是独立数据报,网络不替你保证到达、顺序和唯一。

UDP 数据报边界与应用责任

这张图怎么读

先看边界:一次 send 对应一个 UDP datagram,接收方看到的也是一条条消息,而不是 TCP 那样的连续字节流。再看责任划分:UDP 只加端口和校验等最小传输信息,不替应用处理重传、乱序、去重、拥塞和会话状态。凡是“少一包会怎样”“乱序能不能接受”“重复消息能不能幂等”,都要回到应用协议自己设计。

1. UDP 的本质:带端口的独立数据报

IP 能把包送到某台主机,但主机上可能有很多进程。传输层至少要解决:

到达这台机器以后,应该交给哪个进程?

UDP 用端口解决这个问题:

源 IP:源端口 -> 目标 IP:目标端口

它的数据单位是 datagram,也就是数据报。一次发送通常对应一个独立数据报,接收方也是按数据报接收。

这和 TCP 的字节流不同:

对比UDPTCP
数据形态一条一条数据报连续字节流
消息边界保留不保留
连接建立无需握手需要握手
可靠性不保证保证有序可靠到达
拥塞控制协议本身不强制协议内置
应用自由度低一些

2. UDP 报文头为什么很小

UDP 头部只有几个核心字段:

字段作用
源端口发送方进程入口
目标端口接收方进程入口
长度UDP 头部和数据的总长度
校验和检测传输中是否损坏

可以把 UDP 看成:

IP 提供主机到主机
UDP 加上端口,提供进程到进程

除此之外,它几乎不多管。

3. UDP 不提供哪些保证

UDP 的“不可靠”要拆开看。它不保证:

不保证具体含义
到达包可能在任何中间节点被丢弃
顺序后发的包可能先到
唯一网络异常时可能看到重复数据
完整会话没有协议级连接状态
自动重传丢了不会由 UDP 自己补发
流量控制不知道接收方应用是否处理得过来
拥塞控制不会自动根据网络拥塞降速

所以 UDP 应用必须接受一个事实:

代码块JAVA · 2 行收起展开
发出去了,不代表对方一定收到;
收到了,不代表顺序正确;

暂时没收到,不代表永远收不到。

机制深挖:无连接不等于系统里没有状态

UDP 常被说成“无连接”,这句话只是在传输层协议语义上成立:UDP 本身没有 TCP 那样的握手、序号确认、连接状态机。它不代表整条路径完全无状态。

真实系统里,状态可能出现在这些地方:

位置可能保存什么状态过期或不一致时的现象
应用协议会话 token、序号、重试窗口服务端认为请求过期或重复
NAT 设备内网地址端口到外网地址端口的映射一段时间无流量后回包找不到入口
防火墙最近允许的流量方向和端口首包被丢、回包被挡
负载均衡四元组到上游实例的映射后续包打到不同实例
接收端 socket端口绑定和接收缓冲区包到了主机但无人接收或被丢弃

这能解释一个非常实际的现象:

应用以为 UDP 不需要连接维护
但中间 NAT/防火墙会悄悄维护映射
映射过期后,外部回包就可能丢失

所以很多 UDP 应用仍然要做心跳或 keepalive。目的不是维持 UDP 连接,而是维持应用会话和路径中间设备的状态。

4. 为什么还要用 UDP

因为“可靠”本身也有成本。

TCP 为了可靠会重传、排序、控制窗口。对文件传输来说这很好,因为旧字节不能丢。但对实时语音、视频、游戏状态、监控采样等场景,过期数据可能已经没有价值。

例如视频会议中,某一帧丢了:

如果等待重传:
画面更完整,但延迟增加

如果直接跳过:
画面有轻微损伤,但实时性更好

很多实时系统宁愿接受少量丢包,也不愿为了补齐旧包让整体延迟升高。

UDP 的优势通常来自:

优势解释
无握手首包更快发出
保留消息边界应用天然按包处理
策略可自定义应用决定哪些包值得重传
避免字节流队头阻塞一个旧包不一定拖住后续语义
适合轻量查询请求小、响应小、可重试

5. UDP 的典型使用场景

场景为什么适合 UDP
DNS 查询请求/响应小,失败后可以重试
实时音视频低延迟通常比补齐旧数据更重要
在线游戏状态同步位置状态会不断刷新,旧状态价值低
监控/遥测上报少量丢失可接受,持续采样更重要
局域网发现广播/组播场景常用
QUIC/HTTP/3在 UDP 上实现自定义可靠传输和连接迁移

注意:使用 UDP 不等于完全不要可靠性。很多系统是在 UDP 之上自己实现一套更适合业务的可靠策略。

6. 应用如果选择 UDP,需要自己设计什么

如果你的应用不能接受乱序、丢包、重复,就要在应用层处理。

常见设计包括:

设计解决的问题
序号判断顺序、检测丢包、丢弃旧包
ACK确认对方收到哪些数据
超时重发丢包后补发
去重表防止重复包被重复处理
心跳判断对方是否还活着
滑动窗口控制同时在途的数据量
速率限制防止自己把网络打爆
校验/签名防止错误数据或伪造数据

一个简化的可靠 UDP 思路:

发送方:
给每个消息编号
保存未确认消息
超时没有 ACK 就重发

接收方:
检查编号
处理新消息
丢弃重复消息
回复 ACK

这看起来像 TCP,但应用可以做不同取舍:

  • 重要控制消息:必须 ACK,超时重发
  • 实时位置消息:新消息来了就丢弃旧消息
  • 语音帧:可接受丢失,不等待补齐

应用层协议模板:用 UDP 时至少写清 8 个字段

如果你要在 UDP 上设计自己的消息协议,不要只定义 payload。至少要考虑这些字段或语义:

字段/语义解决什么问题
version协议升级和兼容
type区分心跳、数据、ACK、控制消息
sequence判断顺序、丢包、重复
timestamp判断过期、估算延迟
session/token区分会话,避免把旧包当新包
payload length防止解析越界或粘错数据
checksum/auth识别损坏或伪造
flags标记是否需要 ACK、是否可丢弃、是否分片

一个简化头部可以长这样:

[version | type | flags | sequence | timestamp | session | length | payload]

这不是鼓励你重复造 TCP,而是提醒:UDP 给你的自由必须用协议规则补回来。你可以选择“不重传旧位置包”,但你不能不定义“什么叫旧”。

7. UDP 和 MTU:不要随便发超大包

UDP 数据报过大时,底层 IP 可能分片。分片带来两个问题:

  1. 任意一个分片丢失,整个 UDP 数据报都无法重组。
  2. 分片增加中间网络处理成本,也更容易被设备过滤。

所以很多 UDP 应用会控制单个包大小,尽量避开路径 MTU 问题。

直觉上:

小包:
头部开销占比更高,但丢失影响小

大包:
头部开销占比更低,但分片风险和丢失代价更高

这也是为什么网络设计不能只看“吞吐”,还要看丢包、延迟、路径 MTU、应用语义。

反例:应用层切片不等于自动可靠

有人看到“大 UDP 包容易分片”,就想:那我把一条大消息拆成多个小 UDP 包不就好了?这只解决了单包过大的问题,没有自动解决可靠性。

假设一条消息被拆成 5 片:

msg=42, part=1/5
msg=42, part=2/5
msg=42, part=3/5
msg=42, part=4/5
msg=42, part=5/5

你至少还要定义:

问题不定义会怎样
分片编号接收方不知道顺序
总片数或结束标记接收方不知道何时完整
超时丢弃缺一片时内存一直占着
重复片处理重传导致同一片被算多次
最大重组大小恶意或异常输入耗尽内存
完整性校验拼出的消息可能损坏

应用层切片是一个小型协议。只要你开始切片,就必须同时设计重组、超时、内存上限、去重和校验。否则你只是把 IP 分片的问题搬到了应用层,而且更难排查。

8. UDP 的安全问题

UDP 无连接,服务端在收到第一个包前通常没有确认对方身份。攻击者可能伪造源地址,诱导服务器把更大的响应发给受害者,形成放大攻击。

典型风险:

风险解释
源地址伪造UDP 没有握手确认来源
反射放大小请求触发大响应,响应打到伪造源
资源消耗大量无状态请求消耗 CPU/带宽
数据伪造没有额外认证时,接收方难判断包来源

防护方向:

限制响应大小
验证令牌或会话
做速率限制
过滤异常来源
不要对未验证请求返回过大数据

9. “连接”的 UDP 是什么意思

有些系统 API 允许对 UDP socket 调用 connect。它并不会像 TCP 那样建立握手连接,通常只是给这个 UDP socket 记录默认对端。

好处包括:

好处解释
发送时不用每次指定对端API 更方便
过滤其他来源的数据只接收默认对端
可能接收部分错误反馈某些系统会把 ICMP 错误映射给该 socket

但协议层面仍然是 UDP:

不会保证到达
不会保证顺序
不会自动重传
不会变成字节流

10. 联系实际:怎样判断一个系统该不该用 UDP

可以从四个问题判断:

问题更偏向 TCP更偏向 UDP
数据能不能丢不能丢可丢少量或可重试
旧数据是否有价值有价值,必须按顺序处理旧状态可能可丢弃
延迟是否极敏感可接受重传等待延迟比完整性更关键
应用是否愿意自管可靠性不想自己实现愿意定制策略

实际排障时,也要按 UDP 的特性看问题:

请求发出了但没响应:
可能是包丢了
可能是目标没监听
可能是防火墙丢弃
可能是响应包回不来
可能是应用没有设计重试

UDP 问题经常不会给你一个清晰的“连接失败”错误,很多时候应用只看到超时。

设计卡:选择 UDP 后必须补上的四个问题

UDP 把很多保证留给应用自己做。只要你打算用 UDP,就必须把下面四件事写清楚:

问题如果不设计会怎样常见设计
消息丢失接收方永远等不到某个关键状态序号、确认、重试、过期丢弃
消息乱序新状态被旧状态覆盖版本号、单调序号、只接受更新状态
消息重复同一动作执行多次幂等键、去重窗口
消息过大分片丢失导致整条消息不可用控制大小、应用层切片、路径 MTU 估计

练习:假设你要周期性发送一个传感器读数。哪些包丢了可以接受?哪些必须重发?旧读数到达时要不要覆盖新读数?这类问题回答清楚,才算真的理解 UDP 的自由和代价。

11. 学完本章你能解决什么问题

学完本章,你应该能:

  1. 解释 UDP 提供的是进程到进程的数据报传输,而不是可靠字节流。
  2. 判断某个场景为什么可能选择 UDP,而不是默认追求 TCP 的可靠性。
  3. 设计最基本的 UDP 应用层可靠机制,包括序号、ACK、超时、去重。
  4. 理解 UDP 包过大为什么会带来分片和丢失风险。
  5. 排查 UDP 请求无响应时,从监听、防火墙、丢包、回包路径、应用重试等方向定位。
  6. 识别 UDP 反射放大和源地址伪造等安全风险。

延伸阅读