网络传输协议之二进制反码校验和剖析

什么是二进制反码求和?

对一个无符号数,先求其反码(按位取反之意,与有符号数中定义的反码表示是不相同的),然后从低位到高位,按位相加,等于2则进1,若最高位有进位,需要向最低位加1。

在这个定义中,与补码加法有些区别的是,若最高位有进位,需要向最低位进位。理解起来有些抽象,以4bit为例加以说明。

假设0010是补码表示,则其反码是各2进制位按位取反1101,同理补码1010的反码表示为0101。

反码相加,根据定义有1101 + 0101 = 10010 + 0001(最高位进位,需要向最低位加1)  = 10011 ,如果这两个操作数作为补码加法,则1101 + 0101 = 10010。根据二进制反码

的运算规则,可以看出当发生溢出时,实际溢出的数值是10000 - 0001 (最高进位,最低位加1)= 1111,而补码加法溢出的数值为10000。也就是可以认为,任意4位反码是对1111

求余,也就是实际表示范围是0000~1110,根据这个定义,1111对1111求余,则为0000,因此1111与0000在反码计算中是等效的,由此可以的出一个结论是任意一个反码与全1

或全0的数相加还是等于其本身。显然任意4位补码是对10000求余,也就是实际表示范围是0000~1111。

目前在网络协议中二进制反码校验和算法几乎都是先计算无符号补码和,并对高16位与低16位相加,再对计算结果取反。看上去与我上面的定义有些区别,其实二进制反码运

算是先取反再相加,还是先相加再取反,是没有区别的。下面我给出证明:

假设有两个16位无符号补码表示的数m、n,则~m、~n表示其反码形式。这里的+表示反码加操作(最高位进位时,最低位加1)。则有m + ~m = 0xffff ,n + ~n = 0xffff。

~m + ~n = (0xffff - m ) + (0xffff - n) = ( (0xffff - m) + (0xffff - n) ) mod 0xffff = -(m + n) = 0xffff + -(m + n) = ~(m + n),也就是~m + ~n = ~(m + n),完成证明。这里运用了任意一个数

与全1的数进行反码运算等于本身,以及任意一个数与自身的反码运算等于全1的数的理论。

以下是UNIX网络编程第28章中对16位二进制反码校验和的实现

uint16_t
in_cksum(uint16_t *addr, int len)
{
    int nleft = len;
    uint32_t sum = 0;
    uint16_t *w = addr;
    uint16_t answer = 0;
    
    //our algorithm is simple,using a 32 bit accumulator(sum),we add
    // sequential 16 bit words to it, and at the end, fold back all the
    //carry bits from the top 16 bits into the lower 16 bits
    while(nleft > 1)
    {
        sum += *w++;
        nleft -= 2;
    }
    
    //mop up an odd byte, if nessary
    if (nleft == 1)
    {
        *(unsigned char *)(&answer) = *(unsigned char *)w;
        sum += answer;
    }
    //add back carry outs from top 16 bits to low 16 bits
    sum = (sum >> 16) + (sum & 0xffff);//add hi 16 to low 16
    sum += (sum >> 16); // add carry
    answer = ~sum; //truncate to 16 bits
    
    return (answer);

}

对以上程序的说明,再次重申,根据二进制反码校验和的定义,如果最高位有进位,则最低位加1。整个计算过程中,之所以需要保留所有进位,是因为在在累加的过程中一直未

处理如果最高位有进位,则最低位加1的情况,把所有进位的信息记录下来,方便后面去统一计算,因而要把计算结果存入32位无符号数中。可以把32位无符号数的高16位,理解

为整个运算过程中,产生的进位次数。如果将高16位加到低16位,则完成了对最高位有进位,则最低位加1的处理,不过这时计算出的结果可能仍会产生进位,因此需要再加一

次,来完全消除进位的可能。最后对结果取反,则完成了对二进制反码和的计算。根据前面的证明,可以得出,该算运过程与先取反再相加等效,不过效率更高。


最后再来看看在网络协议中如何去运用二进制反码校验和?

在发送数据时,为了计算数据包的校验和,应该按如下步骤:

  1. 把校验和字段置为0(必须)
  2. 把需要校验的数据看成以16位位单位的无符号数组成(如果是奇数,则需要在末尾补零),依次进行二进制反码求和
  3. 把得到的结果存入校验和字段中

分析如下,除校验和字段之外的其他字段的16位2进制反码校验和为m,由于校验和字段值为0,则 0 + m = m,而m被存放在校验和字段中

在接收数据时,计算数据包的校验和的步骤:

  1. 把首部看成以16位为单位的无符号数组成,依次进行反码求和(包括校验和字段)
  2. 检查计算出的校验和的结果对0xffff求余,看是否为0(0xffff mod 0xffff = 0)
  3. 如果等于0,说明校验和是正确的,否则校验和是错误的,这样的数据包直接被丢弃

分析如下,除校验和字段之外的其他字段的16位2进制反码校验和,如果正确则仍然为m,而校验和字段的反码,如果正确则为~m,那么m + ~m = 0xffff,计算结果对

0xffff求余 ,则为0,说明校验和正确,否则不正确。






展开阅读全文

没有更多推荐了,返回首页