校验和——IPv4、TCP、UDP、ICMP&&CRC&&奇偶检验(纠错)

校验和

协议 校验范围 谁负责计算? 备注
IPv4 仅 Header 发送方 + 所有路由器 因为 TTL 变了,路由器必须更新校验和 (Increment Update)。
IPv6 为了提速,移除了校验和,依赖 TCP/UDP 和 链路层校验。
UDP Header + Data + 伪首部 仅终端 (端到端) 可选的(但在 IPv6 中是强制的)。
TCP Header + Data + 伪首部 仅终端 (端到端) 强制的,保证数据可靠性。
Ethernet 整个帧 网卡硬件 使用 CRC-32 算法,硬件实现,速度极快。

使用16位反码求和算法的IPv4、TCP、UDP、ICMP

都使用同一种算法,称为 “16位反码求和” (16-bit One’s Complement Sum)

计算步骤(发送方):

  1. 分块: 把需要校验的内容(Header 或 Header+Data)看成是由许多个 16位(2字节) 的整数组成的序列
  2. 补零: 如果数据字节数是奇数,最后补一个字节的 0,凑成偶数**(16位对齐)→一般不会出现发送单个bit的情况**
  3. 求和: 把这些 16 位的整数进行二进制相加
  4. 回卷(Wrap Around): 如果加法产生了最高位的进位(溢出,超过 16 位),把进位加回到结果的最低位(这就是“反码加法”的特性)
  5. 取反: 最后把求和的结果按位取反(0变1,1变0)
  6. 填入: 将这个结果放入 Checksum 字段中

验证步骤(接收方):

  1. 接收方收到数据后,把所有数据(包括 Header、Data 和 接收到的 Checksum 字段本身)按同样的规则切分成 16 位整数
  2. 将它们全部相加(同样包含进位回卷)
  3. 结果判断:
    • 如果结果是 全 1 (1111 1111 1111 1111,即 -0),说明传输正确
    • 否则,说明传输出错,丢弃该包

例子:对于两个数11100111 00010001和01101110 00111011来说

  • 字1 (Word 1): 11100111 00010001
  • 字2 (Word 2): 01101110 00111011

接收方操作:

第一步:把收到的 数据 (字1 + 字2) 和 校验和 全部加起来

字1 + 字2 = 1 01010101 01001100 -> 回卷后 -> 01010101 01001101

		01010101 01001101
  + 10101010 10110010
  -------------------
    11111111 11111111

第二步:判断正确与否——全 1 代表校验通过,数据未损坏

发送方操作:

第一步:二进制相加 (求和)

																	11100111 00010001  (1)
																+ 01101110 00111011  (2)
																-------------------
	**注意:这里产生了进位** (Overflow)->1 01010101 01001100

第二步:回卷 (Wrap Around)

		01010101 01001100  (去掉溢出位后的结果)
  +                 1  (把溢出的 1 加回来)
  -------------------
    01010101 01001101  <-- 这是“回卷”后的和

第三步:取反 (One’s Complement) —— 得到校验和

01010101 01001101
------------------
10101010 10110010

链路层的 循环冗余校验 (Cyclic Redundancy Check)

链路层(比如 Ethernet 以太网Wi-Fi)通常使用 CRC,而不是简单的加法校验,比刚才讨论的“IP/TCP 校验和(反码求和)”更强大、更严格

通常由网卡硬件直接完成,不占用 CPU。检错能力极强,是数据链路层的保镖

CRC 的除法和我们需要借位的普通除法不同,它使用的是 模2运算 (Modulo-2 Arithmetic)

简单说就是:不进位,也不借位,其实就是 XOR (异或) 运算

  • 0 XOR 0 = 0
  • 1 XOR 1 = 0
  • 1 XOR 0 = 1
  • 0 XOR 1 = 1

  1. 约定规则: 发送方和接收方提前商定一个除数(称为生成多项式,Generator Polynomial)
  2. 发送方: 拿着要发送的数据,在后面补上几个 0,然后用这个数据去除以那个约定的除数。除完之后,会得到一个余数
    • 发送方把这个余数贴在数据的尾巴上发出去
  3. 接收方: 收到数据(含尾巴)后,用同样的除数去这个数据
    • 如果余数是 0,说明数据是完美的
    • 如果余数不为 0,说明数据在路上坏了,直接丢弃
  • 要发送的数据 (D): 101001
  • 约定的除数 (G): 1101 (在多项式中表示 x3+x2+1x^3 + x^2 + 1x3+x2+1)
    • 注意:除数是 4 位,所以我们要在数据后面补 3个零 (即发送数据的位数-1)

步骤一:补零

数据变成:101001 + 000 = 101001000

步骤二:进行模2除法 (XOR 除法)

								 1 1 0 1 0 1   <--(我们不关心商)
          _________________
除数 1101 ) 1 0 1 0 0 1 0 0 0   <-- 被除数 (数据+补零)
            1 1 0 1           <-- 1.XOR 运算 (1-1=0, 0-1=1, 1-0=1, 0-1=1)
            -------
              1 1 1 0         <-- 余下部分,拉下一位
              1 1 0 1         <-- 2.XOR
              -------
                0 1 1 1       <-- 余下部分,拉下一位
                0 0 0 0       <-- (首位是0,商0,下面全写0)
                -------
                  1 1 1 0     <-- 余下部分,拉下一位
                  1 1 0 1     <-- 3.XOR
                  -------
                    0 1 1 0   <-- 余下部分,拉下一位
                    0 0 0 0   <-- (首位是0)
                    -------
                      1 1 0 0 <-- 余下部分,拉下一位
                      1 1 0 1 <-- 4.XOR
                      -------
                        0 0 1 <-- 余数 (Remainder)

余数是 001。这个余数就是 CRC 校验码(也叫 FCS,帧校验序列)

步骤三:发送数据

发送方实际发送的数据是:原始数据 + **余数,**即:101001 + 001 = 101001001

接收方收到了 101001001,它用同样的除数 1101 再做一次除法

											1 1 0 1 0 1      <--(这一行我们其实不关心)
              ___________________
 除数 1 1 0 1 )  1 0 1 0 0 1 0 0 1  <-- 被除数 (收到的完整数据)
                1 1 0 1            <-- 【第1步】首位是1,用 1101 做异或
                -------
                  1 1 1 0          <-- 结果 (1010^1101=0111),拉下下一位 0
                  1 1 0 1          <-- 【第2步】首位是1,用 1101 做异或
                  -------
                    0 1 1 1        <-- 结果 (1110^1101=0011),拉下下一位 1
                    0 0 0 0        <-- 【第3步】首位是0,不够除,用 0000 做异或
                    -------
                      1 1 1 0      <-- 结果 (0111^0000=0111),拉下下一位 0
                      1 1 0 1      <-- 【第4步】首位是1,用 1101 做异或
                      -------
                        0 1 1 0    <-- 结果 (1110^1101=0011),拉下下一位 0
                        0 0 0 0    <-- 【第5步】首位是0,不够除,用 0000 做异或
                        -------
                          1 1 0 1  <-- 结果 (0110^0000=0110),拉下下一位 1 (最后一位)
                          1 1 0 1  <-- 【第6步】首位是1,刚好用 1101 做异或
                          -------
                            0 0 0  <-- 【最终结果】余数为 0

计算出的最终余数是 **0,**这意味着:

  1. 接收方确认发送方用的除数也是 1101
  2. 传输过程中,没有任何一个比特发生翻转(数据完好无损)

CRC 不能用于验证数据的完整性(防篡改)。如果需要防篡改,必须使用 Hash 算法(如 SHA-256) 或 数字签名

CRC使用软件实现起来很慢:

  • 硬件实现(快):在网卡、硬盘控制器里,CRC 是通过移位寄存器和异或门电路实现的,速度极快,几乎没有延迟。
  • 软件实现(慢):如果让 CPU 用代码去跑 CRC 的除法运算,效率是比较低的(涉及大量的位移和异或操作)。
    • 对比:TCP/IP 协议栈之所以在网络层和传输层使用简单的 Checksum(反码求和) 而不用 CRC,就是为了减轻 CPU 的负担(虽然 Checksum 检错能力不如 CRC,但 CPU 算加法非常快)。

CRC 的效果好不好,完全取决于那个生成多项式(Generator Polynomial)选得好不好

  • 工业界规定了几个特定的“黄金多项式”,比如:
    • CRC-32 (以太网、ZIP压缩包使用)
    • CRC-16-CCITT (蓝牙、SD卡使用)
  • 问题:如果你在设计协议时没有使用标准多项式,而是自己瞎编了一个,很可能导致某些特定的错误模式(比如连续翻转2位)无法被检测出来。

奇偶校验

奇偶校验的核心思想是**“凑数”:**

  • 偶校验(Even Parity):添加一个校验位,使得整个数据(包括校验位)中 1 的个数是偶数
  • 奇校验(Odd Parity):添加一个校验位,使得整个数据(包括校验位)中 1 的个数是奇数

首先,它们在检错能力上是完全一样的,它们都只能检测出奇数个比特翻转(比如变了1位、3位),如果同时变了2位,它们都会失效。

这个奇偶检验说的是1的个数是奇数还是偶数,不是说数据长度是奇数还是偶数!!!

奇校验(Odd Parity) 有一个偶校验无法比拟的优势,那就是检测“全0”故障

假设你有一根传输线,因为硬件故障(断路、没电、接口松动),这根线被拉低电平,导致接收端收到的全是 0如果用偶校验:数据是 00000000,校验位也是 0(因为0个1被认为是偶数)

接收端误以为收到了一段有效的“空数据”→后果:无法发现线路已经断了

如果用奇校验:正常情况下,如果数据是 00000000,校验位必须是 1(凑成1个1),现在线路坏了,收到的是全0。→接收端收到一串0,数了一下1的个数是0(非奇数),校验失败!→接收端立刻知道出问题了,要么是数据错了,要么是线路断了

在网络层(如TCP/IP、以太网)主要用更高级的 CRC(循环冗余校验),但在底层硬件中,奇偶校验依然存在

单比特奇偶校验 (Single Bit Parity)

  • 原理:发送方在数据末尾加 1位 校验位
    • 如果原始数据里有奇数个1→校验位填 1(凑成偶数)
    • 如果原始数据里有偶数个1→校验位填 0(保持偶数)

假设我们要发送数据 10110(这里有3个1)→3个1,有奇数个1→校验位填1

  • 发送端:为了凑成偶数个1,校验位设为 1。发送的数据变为 10110 + 1 = 101101(共4个1)
  • 接收端:收到数据后数一下1的个数
    • 情况A:收到 101101 →4个1(偶数)认为没问题
    • 情况B(出错):收到 100101 →3个1(奇数)检测出错误!
  • 缺点:
    • 只能检测奇数个比特错误:如果同时坏了2个比特(比如 101101 变成了 000101),1的个数从4个变成了2个,依然是偶数。接收方会误以为数据是正确的。
    • 无法纠错:接收方只知道“出错了”,但不知道是哪一位错了(是第1位还是第5位?),所以只能要求重传。

二维奇偶校验 (Two-Dimensional Bit Parity)

为了解决单比特校验“太弱”的问题,二维校验引入了矩阵的概念。它不仅能检测错误,还能纠正错误

  • 原理:将要发送的数据切分成若干段,排成一个矩阵(行和列)
    • 行校验:对每一行计算一个校验位
    • 列校验:对每一列计算一个校验位
    • 最后还会有一个右下角的总校验位

如果矩阵中某一个数据位发生了翻转(0变1):

  1. 它所在的,1的个数会变,导致行校验失败
  2. 它所在的,1的个数也会变,导致列校验失败
  3. 行错误和列错误的交汇点,就是出错的那个比特!→这就是为什么可以进行纠错的原因(定位到了错误的bit就可以直接进行翻转纠错了)

**A. 发送端(计算校验位):**假设数据是 10110 和 01110

数据位 d1 数据位 d2 数据位 d3 数据位 d4 数据位 d5 行校验位
第一行 1 0 1 1 0 1 (凑4个1)
第二行 0 1 1 1 0 1 (凑4个1)
列校验位 1 1 0 0 0 0

B. 传输过程中出错:假设第一行、第二列的那个 0 变成了 1

数据位 d1 数据位 d2 数据位 d3 数据位 d4 数据位 d5 行校验位
出错行 1 1(错) 1 1 0 1 有5个1,不是偶数→出错
第二行 0 1 1 1 0 1
列校验 1 1 0 0 0 0
有3个1不是偶数→出错

C. 接收端(检测与纠正):

  1. 检查行:第一行现在有5个1(奇数),报错!
  2. 检查列:第二列现在有3个1(奇数),报错!
  3. 定位:错误出现在 第一行 和 第二列 的交叉点。
  4. 纠正:既然该位是 1 且它是错的,那正确的值一定是 0(因为二进制只有0和1)。直接反转该位,无需重传。
  • 优点:可以检测并纠正单个比特错误;可以检测**(但不能纠正)**两个比特的错误。
  • 缺点:开销比单比特校验大(需要传输更多的校验位)。如果错误太多(例如一个矩形区域的4个角都错了),它也无能为力。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐