校驗和計算原理
校驗和思路
首先,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校驗和。
為什麼使用二進位制反碼迴圈移位加法呢?
我們知道,計算機中有原碼,反碼,補碼,為什麼要使用二進位制反碼來計算校驗和呢,而不是直接使用原碼或者是補碼呢?
二進位制反碼迴圈移位加法求和優點
不依賴系統是大端小端。即無論你是傳送方計算機或者接收方檢查校驗和時,都不要呼叫htons或者ntohs,直接通過上面的演算法就可以得到正確的結果。這個問題你可以自己舉個例子,用反碼求和時,交換16位數的位元組順序,得到的結果相同,只是位元組順序相應地也交換了;而如果使用原碼或者補碼求和,得到的結果可能就不同。
計算和驗證校驗和比較簡單、快遞。
舉例:
對於一串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");
}
執行結果
參考部落格:傳送門