UDP 校验和:反码求和、回卷进位与「全 1 即通过」
2026/11/6...大约 3 分钟
UDP 校验和
核心结论
UDP 只做一件事来保证数据完整性:把伪首部 + UDP 首部 + 数据全部当成 16 位字做反码求和,把结果取反写进校验和字段。接收端用同样的方式把整包(含校验和)再加一遍 —— 结果应当是 16 个 1,否则丢弃。
反码求和的关键在两点:溢出回卷(第 17 位不进位到更高处,而是加回最低位)和取反码(1 变 0、0 变 1)。
参与计算的内容
校验和不是只算 UDP 首部和数据,还要带一个伪首部,把 IP 层的信息也纳入校验:
| 字段 | 说明 |
|---|---|
| 发送端 IP 地址 | 伪首部 |
| 接收端 IP 地址 | 伪首部 |
| 协议号(17) | 伪首部,表示 UDP |
| UDP 网段总长度 | 伪首部 + UDP 首部各出现一次 |
| UDP 源端口号 / 目的端口号 | UDP 首部 |
| 校验和数值 | 计算时先置 0,算完回填 |
| 数据 | 按 16 位对齐 |
把 IP 地址纳入校验,意味着目的 IP 被改错时校验会失败,包不会被误投到别的应用;这是伪首部存在的理由。
构造校验和
- 把待发送数据按 16 位(2 字节)切分;
- 两两相加;
- 每次相加后只保留低 16 位,多出来的位数回卷加到最低位;
- 全部加完后取反码(
~sum & 0xFFFF); - 把结果填进校验和字段。
如果数据长度不是 16 位的整数倍,需要后置补零凑齐一个字。例如 UDP 长度 15 字节(首部 8 + 数据 7),数据只占 7 字节,需补 1 字节零。
JavaScript 实现的骨架(原文给了完整可运行的位数组版本):
function checksum(data) {
let s = 0;
for (let i = 0; i < data.length; i++) {
s += data[i];
s = (s & 0b1111111111111111) + (s >> 16); // 回卷:高位加回低位
}
return (~s & 0b1111111111111111); // 取反码
}(s & 0xFFFF) + (s >> 16) 这一行就是「回卷进位」的全部实现:把溢出到第 17 位以上的部分右移回来再相加。
校验收到的包
接收端不改校验和字段,直接用同样的反码求和把整包(伪首部 + UDP 首部 + 数据,校验和保留原值)加一遍:
- 结果是 16 个 1(
0xFFFF)→ 校验通过; - 否则 → 认为报文出错,丢弃(UDP 不重传,把恢复交给上层)。
原理很直白:发送端写进去的是 ~sum,接收端再累加一次相当于 sum + ~sum = 0xFFFF(全 1)。也正因如此,校验函数在「计算」和「校验」两种用途下只差是否取反。
边界与实务
- IPv4 里 UDP 校验和是可选的(置 0 表示不校验),IPv6 里是强制的。
- 全 0 的校验和与「计算结果本就是 0」冲突,所以反码系统里 0 要用全 1 表示(
0x0000的负零问题)。 - UDP-Lite 允许只覆盖部分负载(
checksum coverage),用于容忍误码的实时流。 - 抓包排障时的经验:如果
udp.checksum报错但业务正常,先确认网卡校验和卸载(checksum offload) 是否让抓包看到的是「未计算」的 0 —— 这是本地抓包最常见的假故障。
来源
- 原文:如何计算 UDP 校验和(含完整位数组示例与运算表图)
- 教材:Computer Networking: A Top-Down Approach(Kurose & Ross, 7th Ed.)第 3.3 节
