阅读前请看一下:我是一个热衷于记录的人,每次写博客会反复研读,尽量不断提升博客质量。文章设置为仅粉丝可见,是因为写博客确实花了不少精力。不用担心你关注我而我却不关注你,因为我是个诚信互关的人!!互相进步谢谢!!
文章目录
- 阅读前请看一下:我是一个热衷于记录的人,每次写博客会反复研读,尽量不断提升博客质量。文章设置为仅粉丝可见,是因为写博客确实花了不少精力。不用担心你关注我而我却不关注你,因为我是个==诚信互关==的人!!互相进步谢谢!!
- 1、背景介绍
- 2、校验和
- 3、运算机制(教材)
- 4、运算机制(总结)力荐力荐力荐!!!
- 5、校验和计算例子(力荐力荐力荐!!!)
- 6、编程之求校验和(力荐力荐力荐!!!)
1、背景介绍
Windows下进行socket编程时遇到的关于校验和的问题,先记录如下。
2、校验和
TCP/IP 体系中有两种校验机制:CRC 校验与校验和,用于保证消息的完整性。CRC 校验用于整个以太网帧的校验,32 位的校验码被添加到以太网帧的最后四个字节。更为常用的是校验和机制,被用于 IP,ICMP,TCP,UDP 等三四层协议中。
IP、ICMP、TCP、UDP等协议的校验和算法都是相同的,采用的都是将数据流视为16位整数流进行重复叠加计算。
3、运算机制(教材)
网上版本绕来绕去,不要去看,直接看谢希仁的《计算机网络》教材,上图。
图1,文字描述
图2,流程图描述
4、运算机制(总结)力荐力荐力荐!!!
在发送数据时,为了计算数据包的检验和。应该按如下步骤:
1、把校验和字段设置为0;
2、把需要校验的数据看成以16位为单位的数字组成,依次进行二进制反码算术运算相加(网上很多说“反码求和”,不准确!原因见下方说明);
3、得到的和取反,再存放至校验和字段。
在接收数据时,计算数据包的检验和相对简单,按如下步骤:
1、把首部看成以16位为单位的数字组成,用反码算术运算求和(包括校验和字段);
2、和取反;
3、检查计算出的校验和的结果是否为0。如果等于0,校验和正确。否则,校验和就是错误的,协议栈要抛弃这个数据包。
说明:
-
1、要掌握反码算术运算的概念:
以反码算术运算的加法为例。
0和0相加是0,0和1相加是1,1和1相加是0但要产生一个进位1,加到下一列。若最高位相加后产生进位,则最后得到的结果要加上溢出的进位1(可能是多个1)。
而补码相加的话溢出位不会去考虑!!!
-
2、“利用反码算术运算求和 ,再取反” == “反码求和”:
计算校验和的第二步,网上提到很多“反码求和”,包括教材上解释 “反码算术运算” 也提到了反码求和。这点很容易绕,我的理解如下。
利用反码算术运算求和,再取反。其实是直接拿计算机内部存储的数字(补码),把补码直接利用反码算术运算相加,再把和取反;
二进制反码求和。就是先把这数(此时以补码存储)取反,然后求和,如果最高位有进位,则向低位进1;
先取反后相加,先相加后取反,得到的结果是一样的。因此“利用反码算术运算求和 ,再取反” == “反码求和”。而往往后者电脑执行更快,所以实现代码都是先相加,最后再取反,这也就是书上讲后者的原因了,因为是对照代码来的。
-
3、计算机内部存储,原码、反码、补码:
上面谈到了计算机内部数字的存储,见《原码、反码、补码:概念、计算机内部表示、实例、运算及转换规则、使用原因》。所以验证了,书上给的计算校验和步骤,其实是利用补码进行反码算术运算的相加,再取反得到的。
-
4、接收方校验的原理:
这一点网上有两个版本,“全0正确” or “全1正确”。其实这与实现方式有关。书上既然以“全0正确”,那就以这个为标准咯。
-
5、为什么接收方计算出来全0才正确?
理解之前,需要知道如下运算法则
A + ~A = 全1,例如 01101001 + 10010110 —————————————— 11111111
为方便看,假设报文头被分为4个16位,值分别是A、B、C、D,其中第二个16位设为校验和字段。这里假设没有溢出位,有溢出位的也可自行验证符合。
发送方计算校验和 第一步:校验和字段置0,并依次求和。 sum = A + 0 + C + D 第二步:取反。 check_sum = ~(A + C + D)= B 接收方校验 第一步:全部求和 A + B + C + D = (A + C + D ) + [ ~(A + C + D)] = 全1 第二步:取反 ~全1 = ~全0
5、校验和计算例子(力荐力荐力荐!!!)
wireshark抓到的ICMP报文如下
08 00
4d 5a
00 01 00 01 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 61 62 63 64 65 66 67 68 69
其中校验和是“4d 5a”
1、将校验和字段置为0
08 00
00 00
00 01 00 01 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 61 62 63 64 65 66 67 68 69
2、把它们两个字节(16bit)组成一组,依次以二进制相加(求和)直至得到结果,这里我就不用二进制表示了,直接用十六进制好看,但要清楚计算机内部是利用二进制处理的。
0800 + 0000 + 0001 + 0001 + 6162 + 6364 + 6566 + 6768 + 696a + 6b6c + 6d6e + 6f70 + 7172 + 7374 + 7576 + 7761 + 6263 + 6465 + 6667 + 6869 = 6b29f = b2a5(处理溢出位,即b29f + 6 = b2a5)
3、取反
~b2a5 & 0xffff --> 4d5a
6、编程之求校验和(力荐力荐力荐!!!)
这里以c语言为例。
版本1
//计算校验和 版本1 参考微软Ping源码
/*
步骤:
1、将报文分成两个字节一组,如果总字节数为奇数,则在末尾追加一个零字节;
2、对所有 双字节 进行按位求和;
3、将高于 16 位的进位取出相加,直到没有进位;
4、将校验和按位取反;
心得:
1、先取反后相加,先相加后取反,得到的结果是一样的。后者更高效,因此实现代码都是先相加,最后再取反。
2、关于求校验和时处理溢出位。可以在循环里面加进位(版本2),也可以等全部循环结束了再加(版本1);
3、处理进位时是通过双目运算符<<或>>操作进行的;
4、反码算术运算可参加计网教材
*/
USHORT checksum(USHORT *buffer, int size) {
unsigned long cksum=0;
while(size >1) {
cksum+=*buffer++;
size -=sizeof(USHORT);
}
if (size) //buffer中的数据是奇数个字节
cksum += *(UCHAR*)buffer;
cksum = (cksum >> 16) + (cksum & 0xffff); //完成了所有数的累加后,开始处理进位。将所有数完成求和后,统一将高 16 位(溢出数)加到低 16 位(求和数)
cksum += (cksum >>16); //在完成上一次操作后,可能又发生了溢出,再执行一次同样的操作。有溢出,加的是溢出数,没有溢出,加0000,相当于没有加
return (USHORT)(~cksum); //取反
}
/*
这里存在两个问题:
首先16bit 数累加时,如果有 2^16 个数累加,那么会使 32 位数本身发生溢出,
但好在目前人类还没提出这么长的协议,所有不用担心 32 位数的溢出问题。
其次,如果将溢出数与结果数累加后,有可能再次溢出 1 ,所以在完成第一次高 16 位与低 16 位的运算后,需要再进行一次该运算,
第二次运算不可能产生溢出。(可以用最极端的情况考虑下 16bit 全1 与 16bit 全1 进行运算)
*/
版本2
//计算校验和 版本2 参考https://fasionchan.com/network/icmp/ping-c/
/*
心得:
这里移位操作可能看不懂,其实就是左移右移来进行倍数的扩大,从而拼接起来,实现8位变16位
*/
uint16_t calculate_checksum(unsigned char* buffer, int bytes) {
uint32_t checksum = 0;
unsigned char* end = buffer + bytes; //buffer相当于指向头,end相当于指向尾
// 奇数字节加上最后一个字节并重置结束
if (bytes % 2 == 1) {
end = buffer + bytes - 1;
checksum += (*end) << 8; //最后一个字节左移8位,从而实现最后一个字节后面补0,再加上去
}
// 逐个相加,这里处理进位就是在循环里处理,当然也可以在循环外
while (buffer < end) {
checksum += (buffer[0] << 8) + buffer[1];//第一个字节左移8位,再加上第二个字节,从而实现两个8位变16位
// 添加进位(如果有)
uint32_t carray = checksum >> 16; //右移16位,相当于把进位单独拎出来了,从而加上去
if (carray != 0) {
checksum = (checksum & 0xffff) + carray;
}
buffer += 2;
}
// 取反
checksum = ~checksum;
return checksum & 0xffff;
}
说明:
双目运算符移位操作符不懂的看之前写的笔记《从代码角度理解:C语言中的 “左移<<“ 与 “右移>>“》。
参考链接:
《深入LwIP(一):校验和机制》
《小菜学网络:用C语言开发ping命令》
《用C语言实现ping命令》
《如何计算icmp校验和》
码字不易,谢谢点赞!诚信互关,诚信互关,诚信互关!!!
码字不易,谢谢点赞!诚信互关,诚信互关,诚信互关!!!
码字不易,谢谢点赞!诚信互关,诚信互关,诚信互关!!!