热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

python累加算法_如何理解这段校验和算法Python代码?

这段代码我可以跟你解释,是个经典代码了,刚好我也刚看过,我这是长这个样子的:defchecksum(data):iflen(

这段代码我可以跟你解释,是个经典代码了,刚好我也刚看过,我这是长这个样子的:

def checksum(data):

if len(data) % 2:

data += b'\x00'

s = sum(array.array('H',data))

s = (s & 0xffff) + (s >> 16)

s += (s >> 16)

return _socket.ntohs(~s & 0xffff)我们首先要理解RFC当中对checksum的定义:

Header Checksum: 16 bits

A checksum on the header only. Since some header fields change

(e.g., time to live), this is recomputed and verified at each point

that the internet header is processed.

The checksum algorithm is:

The checksum field is the 16 bit one's complement of the one's

complement sum of all 16 bit words in the header. For purposes of

computing the checksum, the value of the checksum field is zero.

这段话中,one's complement是反码的意思。也就是说:

首先将checksum字段置为0

将所有的数据理解成一系列的反码word(双字节数)

将所有的反码求和,再取反,得到checksum

反码是指一种表示正负数的方式:最高位表示正或者负,正数用原码,负数用绝对值取反。我们通常使用的正负数表示系统为补码,双字下,1对应0x0001,-1对应0xffff;而反码下,1对应0x0001,-1对应0xfffe。把反码相加,最后的结果再次取反,就是校验和。

那么你一定要问,为什么程序会变成这个奇怪的样子呢?

由于我们的CPU使用的是补码,我们需要考虑反码求和与补码求和运算的不同之处。反码和原码有类似的溢出规则,但是反码有两个0的表示方式(0x0000和0xffff),所以在产生溢出时有一些差异:如果是两个正数,由于都是原码表示,反码相加与补码相加相同。

一正一负,结果为正时,例如-2 + 3,用反码表示为0xfffd和3,按无符号规则相加原本应等于0,由于绕过了0,因此需要额外加1来处理这个溢出的情况,也就是0x0001

一正一负,结果为负时,例如-3 + 2,用反码表示为0xfffc和2,相加为0xfffe,为-1,与无符号相加相同

两负,例如(-2) + (-3),用反码表示为0xfffd和0xfffc,按无符号相加本来应该是0xfff9,同样由于经过了0xffff,需要额外加1,也就是0xfffa,反码的-5。

我们可以把反码相加的规则总结如下:

把反码当作无符号16位整数相加, 如果产生了进位,则需要额外加1

这就是我们有时会在别的地方看到的反码求和的定义:无符号相加,将最高位的进位补到低位再次相加。

所以我们相应应该写出的程序是:

def checksum(data):

data_array = array.array('H',data)

s = 0

for d in data_array:

s += d

s = (s & 0xffff) + (s >> 16)

return (~s & 0xffff)

注意任意两个无符号16位整数的和,在产生进位的情况下,最大的结果是0xfffe(也就是0xffff加上0xffff),因此补加上额外的进位时,不会再次产生进位。

等等!我们并没有讨论大端小端的问题。

所谓大端,是指CPU处理多字节数据的时候,认为高位在前,低位在后。比如0x1234,表示成两个字节,就是0x12 0x34。而小端格式,在处理多字节数据的时候,低位在前,高位在后,0x1234会被表示成0x34 0x12。网络包规定的是按照大端格式解析,而你这句

array.array('H',data)

在Intel的CPU上,明明就是按小端解析的,算出来的结果能正确吗?

有意思的是&#xff0c;反码求和时&#xff0c;我们不需要考虑这些word是按大端存储的还是按小端存储的。我们按大端去读取求出的反码和&#xff0c;交换两个字节&#xff0c;正好与我们按小端去读取求出的反码和相等。证明很简单&#xff0c;考虑两个word相加&#xff0c;第一个word可以写作(a <<8) &#43; b&#xff0c;第二个word写作(c <<8) &#43; d&#xff0c;

则求和为(a &#43; c) <<8 &#43; (b &#43; d)

额外的进位为a &#43; c的8位加法产生的进位&#xff0c;会由反码求和规则加到低8位上&#xff1b;同时b &#43; d的8位加法会加到高8位上。也就是说&#xff0c;恰好低8位的进位加到了高8位上&#xff0c;而高8位的进位加到了低8位上。

如果按小端格式解析&#xff0c;两个数会变成(b <<8) &#43; a和(d <<8) &#43; c&#xff0c;不难发现高位变成了b &#43; d加低位的进位&#xff0c;而低位为a &#43; c加高位的进位&#xff0c;恰好是刚才的结果交换高8位与低8位。

正是因为反码加法的这个特性所以将这个算法选为了checksum的算法&#xff0c;这样无论是大端格式的CPU还是小端格式的CPU&#xff0c;都可以有效计算出相同的checksum。不过由于规定网络包中的数值按照大端格式存储&#xff0c;我们通常将算出的checksum统一转换为大端格式&#xff0c;也就是前面代码中最后的ntohs的作用。

接下来的问题是&#xff0c;为什么我们写的程序不是前面第二段&#xff0c;而是最早的第一段呢&#xff1f;

我们来重新考虑这个加上进位的操作&#xff0c;由于加法是可以交换的&#xff0c;我们可以把进位单独求和&#xff0c;然后最后一起加到16位数上&#xff1a;

def checksum(data):

data_array &#61; array.array(&#39;H&#39;,data)

s &#61; 0

s2 &#61; 0

for d in data_array:

s &#43;&#61; d

s2 &#43;&#61; (s >> 16)

s &#61; (s & 0xffff)

s &#43;&#61; s2

s &#61; (s & 0xffff) &#43; (s >> 16)

return (~s & 0xffff)将进位单独求和然后再加回16位数的过程也适用反码加法的规则&#xff0c;由于这个加法有可能产生新的进位&#xff0c;因此有一次额外的将进位累加回来的操作。当我们累加的word的数量小于65536时&#xff0c;进位单独累加的范围也在16位之内。由于限制IP报文的长度小于65536个字节&#xff0c;IP头更是需要小于64个字节&#xff0c;这个条件是很容易满足的。

我们用一个更聪明的办法&#xff0c;直接用32位无符号整数的高16位来存储这个进位的累加&#xff0c;这样只需要将16位的无符号加法直接变成32位的无符号加法&#xff0c;则我们刚才的s2就刚好是结果中32位的高16位&#xff0c;代码就可以进一步改写成&#xff1a;

def checksum(data):

data_array &#61; array.array(&#39;H&#39;,data)

s &#61; 0

for d in data_array:

s &#43;&#61; d

s &#61; (s & 0xffff) &#43; (s >> 16)

s &#61; (s & 0xffff) &#43; (s >> 16)

return (~s & 0xffff)考虑到最后取反的时候已经有截取16位整数的操作了&#xff0c;最后的第二次累积进位操作中可以不把高位置成0&#xff0c;留给最后的截取16位整数处理(在C中这个通过强制将32位数转换为16位数完成)&#xff1a;

def checksum(data):

data_array &#61; array.array(&#39;H&#39;,data)

s &#61; 0

for d in data_array:

s &#43;&#61; d

s &#61; (s & 0xffff) &#43; (s >> 16)

s &#43;&#61; (s >> 16)

return (~s & 0xffff)把for循环用sum函数改写&#xff0c;再加上额外单字节的处理和大端小端的处理&#xff0c;就成了最早的那段代码。仔细观察就会发现你这段代码除了笨重一点以外&#xff0c;和我这段代码的功能是相同的。

&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;&#61;

其实有个RFC专门写了这个算法

RFC 1071 - Computing the Internet checksum



推荐阅读
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 360SRC安全应急响应:从漏洞提交到修复的全过程
    本文详细介绍了360SRC平台处理一起关键安全事件的过程,涵盖从漏洞提交、验证、排查到最终修复的各个环节。通过这一案例,展示了360在安全应急响应方面的专业能力和严谨态度。 ... [详细]
  • 本文探讨了如何在给定整数N的情况下,找到两个不同的整数a和b,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
  • 深入解析JVM垃圾收集器
    本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版,详细探讨了JVM中不同类型的垃圾收集器及其工作原理。通过介绍各种垃圾收集器的特性和应用场景,帮助读者更好地理解和优化JVM内存管理。 ... [详细]
  • 非公版RTX 3080显卡的革新与亮点
    本文深入探讨了图形显卡的进化历程,重点介绍了非公版RTX 3080显卡的技术特点和创新设计。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 本文基于刘洪波老师的《英文词根词缀精讲》,深入探讨了多个重要词根词缀的起源及其相关词汇,帮助读者更好地理解和记忆英语单词。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 机器学习中的相似度度量与模型优化
    本文探讨了机器学习中常见的相似度度量方法,包括余弦相似度、欧氏距离和马氏距离,并详细介绍了如何通过选择合适的模型复杂度和正则化来提高模型的泛化能力。此外,文章还涵盖了模型评估的各种方法和指标,以及不同分类器的工作原理和应用场景。 ... [详细]
  • 本教程涵盖OpenGL基础操作及直线光栅化技术,包括点的绘制、简单图形绘制、直线绘制以及DDA和中点画线算法。通过逐步实践,帮助读者掌握OpenGL的基本使用方法。 ... [详细]
  • 基因组浏览器中的Wig格式解析
    本文详细介绍了Wiggle(Wig)格式及其在基因组浏览器中的应用,涵盖variableStep和fixedStep两种主要格式的特点、适用场景及具体使用方法。同时,还提供了关于数据值和自定义参数的补充信息。 ... [详细]
  • 基于KVM的SRIOV直通配置及性能测试
    SRIOV介绍、VF直通配置,以及包转发率性能测试小慢哥的原创文章,欢迎转载目录?1.SRIOV介绍?2.环境说明?3.开启SRIOV?4.生成VF?5.VF ... [详细]
  • Splay Tree 区间操作优化
    本文详细介绍了使用Splay Tree进行区间操作的实现方法,包括插入、删除、修改、翻转和求和等操作。通过这些操作,可以高效地处理动态序列问题,并且代码实现具有一定的挑战性,有助于编程能力的提升。 ... [详细]
  • 本文探讨了《魔兽世界》中红蓝两方阵营在备战阶段的策略与实现方法,通过代码展示了双方如何根据资源和兵种特性进行战士生产。 ... [详细]
author-avatar
能P开普票j专G票q903095933
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有