转载请注明,原文地址:

http://www.lgygg.wang/lgyblog/2019/10/15/校验和/

1.什么是校验和

检验和(checksum),在数据处理和数据通信领域中,用于校验目的地一组数据项的和。它通常是以十六进制为数制表示的形式。如果校验和的数值超过十六进制的FF,也就是255. 就要求其补码作为校验和。通常用来在通信中,尤其是远距离通信中保证数据的完整性和准确性。
在源端,计算校验和并将其设置在标头中作为字段。在目标端,再次计算校验和,并与标头中的现有校验和值进行交叉校验,以查看数据包是否正常。

2.校检和计算步骤

我们知道,校检和是用来查看数据包是否完整和准确的。所有会先在发送端(即源端,发送数据的一方)计算出校检和,然后把这个校检和也放到发送的数据里,传递给接收端(即目标端,接收数据的一方)。接收方收到数据后,也会再计算一次校检和,然后和发送端发过来的校检和进行对比。如果一致,则证明接收端收到的数据是完整准确的。
下面介绍一下校验的步骤:
发送方生成检验和
1)将发送的进行检验和运算的数据分成若干个16位的位串,每个位串看成一个二进制数,这里并不管字符串代表什么,是整数、浮点数还是位图都无所谓。
2)将IP、UDP或TCP的PDU首部中的检验和字段置为0,该字段也参与检验和运算。
3)对这些16位的二进制数进行1的补码和(one’s complement sum)运算,累加的结果再取反码即生成了检验码。将检验码放入检验和字段 中。这里需要注意的是,如果发生了进位溢出,校检和就等于数据累加结果的反码。如果没有发生溢出,那么校检和就等于数据累加的结果。
其中1的补码和运算,即带循环进位(end round carry)的加法,最高位有进位应循环进到最低位。反码即二进制各位取反,如0111的反码为 1000。
接收方校验检验和
1)接收方将接收的数据(包括检验和字段)按发送方的同样的方法进行1的补码和运算,累加的结果再取反码(必须取反码)。
2)校验,如果上步的结果为0,表示传输正确;否则,说明传输有差错。

3.例子解析

下面会通过三个例子来介绍校验和的计算过程。

1)计算没有发生溢出字串的校检和

题目:计算下面十六进制串的校检和
十六进制串: 0102030405060708
这是百度百科的题目,它给的答案是24。那么答案是怎么来的?
下面是它给出的计算过程,我们来分析一下计算步骤。

#include<stdio.h> int main() { int a[8]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08}; int i,sum=0; for (i=0;i<8;i ) sum =a[i];//将每个数相加 if(sum>0xff) { sum=~sum; sum =1;   } sum=sum&0xff; printf("0x%x\n",sum); }

首先,他将十六进制串分为8个部分,分别是{01,02,03,04,05,06,07,08},然后将它们全部转换为16进制数,得到a[8]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08};
然后将这8个数字进行累加,得到36(这是一个十进制数),其实这里我们也可以不将它转换为16进制数再进行累加,完全可以将它们进行十进制计算后,再将结果转为16进制,判断结果是否超过十六进制的FF(也就是十进制的255)。


最后,如果累加的结果超过十六进制的FF(也就是十进制的255),就取它的补码作为校检和。否则就直接取累加的记过作为校检和。我们看到上面累加的结果是36(这是一个十进制数),并没有超过255,所有,校检和结果就是36?这是不对的,因为在计算机数据传输中,校检和是一个16进制数,所有我们要将十进制的36转为16进制,结果为24,所有最终结果为0x24。


2)计算发生溢出的校检和

在计算机中,大多数情况下,校检和都是会发生溢出的。下面这个例子也是百度百科里的。下图中,
(a)所示为发送方的运算,①、②、③是3个数据,④是检验和,先置0,也参加检验和运算。⑤是它们的一的补码和,⑥是⑤的反码。发送方将⑥放到检验和字段和数据一起发出。
(b)所示为接收方的运算,如果没有传输差错,最后结果应为0。

校验和验证的区别(什么是校验和)(1)


如图所示,(a)是发送方,④是检验和,先将它置0,也参加检验和运算。然后对①、②、③进行累加,如下图中的红框部分进行累加

校验和验证的区别(什么是校验和)(2)


然后对①、②相加的结果是:
1 01110110 10100010
发生了溢出,所以要在尾部加1,得到结果为
01110110 10100011
再将结果与数据③相加,得到结果
1 00100011 11011000
因为溢出了,所以尾部加1,得到
00100011 11011001
再加上00000000 00000000
最后得到的结果为:
00100011 11011001
上面的计算过程中,发生了溢出,所以最终的校验和为00100011 11011001的反码,即
11011100 00100110。
而接收方都也差不多,也是对①、②、③进行累加,得到
00100011 11011001
再将结果和校检和11011100 00100110相加,结果为
11111111 11111111
这里虽然没有溢出,但是接收方不管有没有溢出最后累加结果都必须取反码,
00000000 00000000
结果为0代表传输数据完整。

3)一个完整的IP头校验和计算

在IP,UDP和TCP等数据包中,都有进行数据和校验的过程。下面这个例子是参考文章里的例子。下图是IP数据包的报头结构

校验和验证的区别(什么是校验和)(3)


假设这是目的地接收到的IP数据包中的IP标头:

4500 003c 1c46 4000 4006 b1e6 ac10 0a63 ac10 0a0c

首先让这些值与IP报头的字段对应

“ 45”对应于标题中的前两个字段,即“ 4”对应于IP版本,“ 5”对应于标题长度。由于标头长度以4字节字描述,因此实际标头长度为5×4 = 20字节。 “ 00”对应于TOS或服务类型。TOS的该值表示正常运行。 “ 003c”对应于IP报头的总长度字段。因此,在这种情况下,IP数据包的总长度为60。 “ 1c46”对应于标识字段。 '4000'可以分为两个字节。这两个字节(分别分为3位和13位)对应于IP头字段的标志和片段偏移。 “ 4006”可以分为“ 40”和“ 06”。第一个字节“ 40”对应于TTL字段,而字节“ 06”对应于IP标头的协议字段。'06'表示协议是TCP。 'be16'对应于在源端(发送数据包)设置的校验和。请注意,如前所述,在计算目标端的校验和时,此字段将设置为零。 下一组字节“ ac10”和“ 0a0c”对应于IP标头中的源IP地址和目标IP地址。

让我们将所有这些值转换为二进制:

4500-> 0100010100000000 003c-> 0000000000111100 1c46-> 0001110001000110 4000-> 0100000000000000 4006-> 0100000000000110 0000-> 0000000000000000 //注意,由于我们在目标端计算校验和,因此校验和设置为零 ac10-> 1010110000010000 0a63-> 0000101001100011 ac10-> 1010110000010000 0a0c-> 0000101000001100 现在让我们将这些二进制值一一添加: 4500-> 0100010100000000 003c-> 0000000000111100 453C-> 0100010100111100 ///第一个结果 453C-> 0100010100111100 //第一个结果加上下一个16位字。 1c46-> 0001110001000110 6182-> 0110000110000010 //第二个结果。 6182-> 0110000110000010 //第二个结果加上下一个16位字 4000-> 0100000000000000 A182-> 1010000110000010 //第三个结果。 A182-> 1010000110000010 //第三个结果加上下一个16位字。 4006-> 0100000000000110 E188-> 1110000110001000 //第四个结果。 E188-> 1110000110001000 //第四个结果加上下一个16位字。 AC10-> 1010110000010000 18D98-> 11000110110011000 //一个奇数位(进位),将该奇数位添加到结果中,因为我们需要将校验和保持在16位中。 18D98-> 11000110110011000 8D99-> 1000110110011001 //第五结果 8D99-> 1000110110011001 //第五个结果加上下一个16位字 0A63-> 0000101001100011 97FC-> 1001011111111100 //第六结果 97FC-> 1001011111111100 //第六个结果加上下一个16位字。 AC10-> 1010110000010000 1440C-> 10100010000001100 //再次为进位,因此我们将其添加(如前所述) 1440C-> 10100010000001100 440D-> 0100010000001101 //这是第七个结果 440D-> 0100010000001101 //第七个结果加上下一个16位字 0A0C-> 0000101000001100 4E19-> 0100111000011001 //最终结果。 因此,现在0100111000011001是我们对标头中所有16位字求和的最终结果。作为最后一步,我们只需要对其进行补充即可获得校验和。 4E19-> 0100111000011001 B1E6-> 1011000111100110 // CHECKSUM

现在,如果将此校验和与从数据包中获得的校验和进行比较,您会发现两者完全相同,因此IP报头的完整性不会丢失。

4.参考文章

ip-header-checksum

,