1. 程式人生 > >校驗和計算原理

校驗和計算原理

校驗和思路

首先,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(填充位元組只是為了計算校驗和,可以不被傳送)。

校驗和計算方法:

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

特點:關於二進位制反碼迴圈移位求和運算需要說明的一點是,先取反後相加與先相加後取反,得到的結果是一樣的。

可結合性和可交換性

用A,B,C,D,E,F分別表示一個8位的二進位制數(一個位元組),用[A, B]這樣的形式表示A*256+B,那麼16位校驗和可以用個如下形式給出

sum = [A,B]+’[C,D]+’[E,F];

其中, +’被表示二進位制迴圈移位加法

可結合性:[A,B]+’[C,D]+’[E,F] = [A,B]+’([C,D]+’[E,F])
可交換性:[A,B]+’[C,D]+’[E,F] = [C,D]+’[A,B]+’[E,F]

位元組自主性

[A,B]+’[C,D]+’[E,F] = [B,A]+’([D,C]+’[F,E])

由於若最高位有進位,則向最低位進1 這樣的特性,第15位到0位進位,第7位向第8位進位,所以,整個求和結果是一樣的。

平行計算

有些機器的字處理長度是16的倍數,這樣可以提高他的計算速度,由於可結合行,那麼32位機器可以[A,B,C,D]+’…進行32校驗和。

為什麼使用二進位制反碼迴圈移位加法呢?

我們知道,計算機中有原碼,反碼,補碼,為什麼要使用二進位制反碼來計算校驗和呢,而不是直接使用原碼或者是補碼呢?

二進位制反碼迴圈移位加法求和優點

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

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

舉例:

對於一串16進位制資料:0001f203f4f5f6f7

這裡寫圖片描述

正常順序是將高8位*256之後,即將高位向左移動8位,加上低8位,交換順序反之

可以明顯得到,二進位制反碼迴圈移位加法與位元組序無關。

程式碼:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>

using namespace std;

void main(int argc, char *argv[])
{
    FILE *fp;
    char ch;
    unsigned char LowChar, HighChar;
    unsigned int count = 0, checksum = 0, byte = 0;
    unsigned long int sum = 0;


    //開啟檔案文字
    if ((fp = fopen("I:\\1.txt", "r")) == NULL)
    {
        printf("File cannot be opened!");
        system("pause");
        return;
    }
    printf("4位  --      sum\n");
    //從文字中讀取字元
    while(1)
    {
        if ((ch = fgetc(fp))!=EOF)
        {
            count++;                        //從1開始計數
            if (ch != ' ')
            {
                //將一個16進位制字元轉化為整形
                if (ch >= '0' && ch <= '9')
                {
                    ch -= '0';
                }
                else if (ch >= 'a' && ch <= 'f')
                {
                    ch = ch-'a'+10;
                }
                else if(ch >= 'A' && ch <= 'F')
                {
                    ch = ch-'A'+ 10;
                }


                //計算8位元組的累加值
                if (count%2 == 1)
                {
                    HighChar = ch << 4;     //作為一個位元組的高四位
                }
                else
                {
                    LowChar = ch & 0x0f;    //作為一個位元組的低四位
                    byte = HighChar|LowChar;//構成一個位元組

                    //16位平行計算
                    if (count % 4 == 2)     //高8位與sum相加
                    {
                        sum += byte << 8;   
                    }
                    else if(count % 4 == 0) //低8位與sum相加
                    {
                        sum += byte;
                    }
                    printf("%04x -- %8lx    \n", byte, sum);
                }
            }
            else 
            {
                count--;
            }
        }
        else
        {
            break;
        }
    }

    //如果16位sum產生進位,將進位移加到低位
    if ( sum >> 16)
    {
        checksum = ~(long(sum>>16)+long(sum&0x0000ffff));
    }
    printf("\n移位後sum: %x", (long(sum>>16)+long(sum&0x0000ffff)));
    printf("\nCheckSum1: %x", checksum);
    //避免再次進位 
    checksum = checksum&0x0000ffff;

    //輸出校驗和
    printf("\nCheckSum: %x\n", checksum);

    system("pause");
}

執行結果

這裡寫圖片描述

參考部落格:傳送門