校驗和算法


轉自校驗和算法 


經常看計算機網絡相關的書時,每次看到關於IP或者是UDP報頭校驗和時,都是一笑而過,以為相當簡單的東西,不就是16bit數據的相加嗎!最近在學習Ping命令的源待時,看到里面有關於校驗和的算法。一頭霧水,后來查找資料,看到校驗和是16bit字的二進制反碼和。總是覺得很奇怪,為什么會用反碼和,而不是直接求和呢?或者是補碼和呢?因為在計算機里面數據是以補碼的形式存在啊!經過看書查資料,下面總結一些這個校驗和算法具體是怎么實現的。

    首先,IP、ICMP、UDP和TCP報文頭都有檢驗和字段,大小都是16bit,算法基本上也是一樣的。

    在發送數據時,為了計算數據包的檢驗和。應該按如下步驟:

    1、把校驗和字段設置為0;

    2、把需要校驗的數據看成以16位為單位的數子組成,依次進行二進制反碼求和;

    3、把得到的結果存入校驗和字段中

    在接收數據時,計算數據包的檢驗和相對簡單,按如下步驟:

    1、把首部看成以16位為單位的數字組成,依次進行二進制反碼求和,包括校驗和字段;

    2、檢查計算出的校驗和的結果是否為0;

    3、如果等於0,說明被整除,校驗和正確。否則,校驗和就是錯誤的,協議棧要拋棄這個數據包。

 

    雖然說上面四種報文的校驗和算法一樣,但是在作用范圍存在不同:IP校驗和只校驗20字節的IP報頭;而ICMP校驗和覆蓋整個報文(ICMP報頭+ICMP數據);UDP和TCP校驗和不僅覆蓋整個報文,而且還有12個字節的IP偽首部,包括源IP地址(4字節)、目的IP地址(4字節)、協議(2字節)、TCP/UDP包長(2字節)。另外UDP、TCP數據報的長度可以為奇數字節,所以在計算校驗和時需要在最后增加填充字節0(填充字節只是為了計算校驗和,可以不被傳送)。

    在UDO傳輸協議中,校驗和是可選的,當校驗和字段為0時,表明該UDP報文未使用校驗和,接收方就不需要校驗和檢查了!那如果UDP校驗和的計算結果是0時怎么辦?書上有一句話:“如果校驗和的計算結果為0,則存入的值為全1(65535),這在二進制反碼計算中是等效的”

 

那么校驗和到底怎么計算了?

1、什么是二進制反碼求和

    對一個無符號的數,先求其反碼,然后從低位到高位,按位相加,有益處則向高位進1(和一般的二進制法則一樣),若最高位有進位,則向最低位進1.

    首先這里的反反碼好像和以前學的有符號反碼不一樣,這里不分正負數,直接每個為都取反。

    上面加粗的那句話和我們平時的加法法則不一樣,最高位有進位,則向最低位進1。確實有些疑惑,為什么要這樣呢?自習分析一下,上面的這種操作,使得在發送加法進位溢出時,溢出值並不是10000,而是1111.也即是當相加結果滿1111時溢出,這樣也可以說明為什么0000和1111都表示0了。

    下面是兩種二進制反碼求和的運算:

    原碼加法運算:3(0011)+5(0101)=8(1000)

                  8(1000)+9(1001)=1(0001)

    反碼加法運算:3(1100)+5(1010)=8(0111)

                  8(0111)+9(0110)=2(1101)

    從上面的例子中,當加法未發生溢出時,原碼與反碼加法運算結果一樣;當有溢出時,結果就不一樣了,原碼是滿10000溢出,而反碼是滿1111溢出,所以相差正好是1.

    另外,關於二進制反碼求和運算需要說明的一點是,先取反后相加與先相加后取反,得到的結果是一樣的。

2、校驗和算法實現

    代碼如下:

  USHORT checksum (USHORT *buffer,int size)
    {
        Unsigned long cksum=0;
        While (size>1)
        {
            Cksum +=*buffer++;
            size -=sizeof(USHORT);
        }
        If (size)
        {
            Cksum +=*(UCHAR *) buffer;
        }
        //將32位轉換為16位
        While (cksum>>16)
            Cksum = (cksum>>16) + (cksum & 0xffff);
        Return (USHORT) (~cksum);
    }

    buffer是指向需要校驗數據緩沖區的指針,size是需要檢驗數據的總長度(字節為單位)。

    4-13行代碼是對數據按16bit累加求和,由於最高位的進位需要加在最低位上,所以cksum必須是32位的unsigned long型,高16bit用於保存累加過程中的進位;另外代碼10~13行是對size為奇數情況的處理。

    14~16行代碼的作用是將cksum高16bit的值加到低16bit上,即把累加中最高位的進位加到最低位上。這里使用了while循環,判斷cksum高16bit是否非零,因為第16行代碼執行的時候,還是可能向cksum的高16bit進位。

    有些地方是通過下面兩條代碼實現的:

    Cksum = (cksum >> 16) + (cksum & 0xffff);

    Cksum = (cksum >> 16);

    這里只進行了兩次相加,即可保證相加后cksum的高16位為0,兩種方式的效果是一樣,事實上,上面的循環也最多執行兩次!

    17行代碼即對16bit數據累加的結果取反,得到二進制反碼求和的結果,然后函數返回該值。

    3、為什么使用二進制反碼求和呢?

    為什么要使用二進制反碼來計算校驗和呢,而不是直接使用原碼或者是補碼呢?

    在谷歌上找到一篇相關的文章:

    

    上面是原文的一部分,說明在TCP/IP校驗和中使用反碼求和的一些優點:

a、 不依賴系統是大端小端。即無論你是發送方計算機或者接收方檢查校驗和時,都不要調用htons或者ntohs,直接通過上面的算法就可以得到正確的結果。這個問題你可以自己舉個例子,用反碼求和時,交換16位數的字節順序,得到的結果相同,只是字節順序相應地也交換了;而如果使用原碼或者補碼求和,得到的結果可能就不同。

b、 計算和驗證校驗和比較簡單、快遞。



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM