網絡實驗遇到的問題(4)

遇到的問題真不少,但大多都是舊知識忘記了,現在重新拾起來。

1.關於原碼,反碼和補碼(轉)

 原碼錶示法

 原碼錶示法是機器數的一種簡單的表示法。其符號位用0表示正號,用:表示負號,數值一般用二進制形式表示。設有一數爲x,則原碼錶示可記作[x]原。

 例如,X1= +1010110

            X2= 一1001010

 其原碼記作:

            [X1]原=[+1010110]原=01010110

            [X2]原=[-1001010]原=11001010

 原碼錶示數的範圍與二進制位數有關。當用8位二進制來表示小數原碼時,其表示範圍:

      最大值爲0.1111111,其真值約爲(0.99)10

      最小值爲1.1111111,其真值約爲(一0.99)10

當用8位二進制來表示整數原碼時,其表示範圍:

      最大值爲01111111,其真值爲(127)10

      最小值爲11111111,其真值爲(-127)10

在原碼錶示法中,對0有兩種表示形式:

          [+0]原=00000000

           [-0]  原=10000000

 

補碼錶示法

機器數的補碼可由原碼得到。如果機器數是正數,則該機器數的補碼與原碼一樣;如果機器數是負數,則該機器數的補碼是對它的原碼(除符號位外)各位取反,並在未位加1而得到的。設有一數X,則X的補碼錶示記作[X]補。

例如,[X1]=+1010110

            [X2]= 一1001010

            [X1]原=01010110

            [X1]補=01010110

即      [X1]原=[X1]補=01010110

            [X2]原= 11001010

            [X2]補=10110101+1=10110110

補碼錶示數的範圍與二進制位數有關。當採用8位二進制表示時,小數補碼的表示範圍:

      最大爲0.1111111,其真值爲(0.99)10

      最小爲1.0000000,其真值爲(一1)10

採用8位二進制表示時,整數補碼的表示範圍:

      最大爲01111111,其真值爲(127)10

      最小爲10000000,其真值爲(一128)10

      在補碼錶示法中,0只有一種表示形式:

        [+0]補=00000000

        [+0]補=11111111+1=00000000(由於受設備字長的限制,最後的進位丟失)

所以有[+0]補=[+0]補=00000000

 

反碼錶示法

機器數的反碼可由原碼得到。如果機器數是正數,則該機器數的反碼與原碼一樣;如果機器數是負數,則該機器數的反碼是對它的原碼(符號位除外)各位取反而得到的。設有一數X,則X的反碼錶示記作[X]反。

 例如:X1= +1010110

          X2= 一1001010

        [X1]原=01010110

         [X1]反=[X1]原=01010110

         [X2]原=11001010

         [X2]反=10110101

反碼通常作爲求補過程的中間形式,即在一個負數的反碼的未位上加1,就得到了該負數的補碼。

例1. 已知[X]原=10011010,求[X]補。

分析如下:

由[X]原求[X]補的原則是:若機器數爲正數,則[X]原=[X]補;若機器數爲負數,則該機器數的補碼可對它的原碼(符號位除外)所有位求反,再在未位加1而得到。現給定的機器數爲負數,故有[X]補=[X]原十1,即

          [X]原=10011010

          [X]反=11100101

        十)         1    

 

          [X]補=11100110

 

例2. 已知[X]補=11100110,求[X]原。

         分析如下:

     對於機器數爲正數,則[X]原=[X]補

     對於機器數爲負數,則有[X]原=[[X]補]補

現給定的爲負數,故有:

            [X]補=11100110

        [[X]補]反=10011001

                十)         1  

 

        [[X]補]補=10011010=[X]原

 

2.  求校驗和(轉)

IP/ICMP/IGMP/TCP/UDP 等協議的校驗和算法都是相同的

以下以IP包爲例

在IP 數據包發送端,首先將校驗和字段置爲0,然後將IP 數據包頭按16比特分成多個單元,如果包頭長度不是16 比特的倍數,則用0 比特填充到16 比特的倍數,其次對各個單元採用反碼加法運算(即高位溢出位會加到低位,通常的補碼運算是直接丟掉溢出的高位),將得到的和的反碼填入校驗和字段,最後發送數據。


在IP 數據包接收端, 首先將IP 包頭按16 比特分成多個單元,如果包頭長度不是16 比特的倍數,則用0 比特填充到16 比特的倍數,其次對各個單元採用反碼加法運算,檢查得到的和是否符合全是1(有的實現可能對得到的和取反碼,然後判斷最終的值是否爲0),如果符合全是1(取反碼後是0),則進行數據包的下一步處理,如果不符合,則丟棄該數據包。

在這裏大家要注意,反碼和是採用高位溢出加到低位上。接下來以一張從網上找的一張IP 數據包頭圖片來加以說明以上的算法。大家應該記得一個公式,即兩數據的反碼和等於兩數據和的反碼,把它推廣到n 個數據同樣適用,公式:~[X]+~[Y]=~[X+Y]

 

例如:

ip首部如下(16進制):

45 00 00 29 44 F1 40 00 80 06 61 8D C0 A8 01 AE 4A 7D 47 7D

奇校驗和爲0X618D

 

發送端:
步驟如下:
首先,將checksum 字段設爲0,那麼將得到IP 數據包頭的分段信息
如下
1. 0x4500
2. 0x0029
3. 0x44F1
4. 0x4000
5. 0x8006
6. 0x0000 ------->這個爲Header Checksum 的值,我們前面將其重置爲0 了
7. 0xC0A8
8. 0x01AE
9. 0x4A7D
+10. 0x477D
結果爲:0x29E70
注意要將溢出位加到低位,即0x29E70 的溢出位爲高位2,將它加到低位上,即0x9E70+0x2=0x9E72
0x9E72 二進制爲:1001 1110 0111 0010
反碼爲:0110 0001 1000 1101
0110 0001 1000 1101 的16 進製爲:0x618D(這就是我們的校驗和)

接收端:
當我們收到該數據包時,它的分段信息將是如下信息:
1. 0x4500
2. 0x0029
3. 0x44F1
4. 0x4000
5. 0x8006
6. 0x618D ------->這個爲Header Checksum 的值
7. 0xC0A8
8. 0x01AE
9. 0x4A7D
+10. 0x477D
結果爲:0x2FFFD
該數值的溢出位爲高位2,把它加到底位D 上,即0xFFFD+0x2=0xFFFF
0xFFFF 二進制爲:1111 1111 1111 1111
1111 1111 1111 1111 反碼爲:0

 

3.那麼怎麼來寫這個checksum算法呢

在接收端:

bool getchecksum(char *pBuffer){
	int i;
	int checksum;
	for(i=0;i<20;i=i+2){
		int num1;
		if(pBuffer[i]>=0)num1=(unsigned int)pBuffer[i];
		else num1=256+(unsigned int)pBuffer[i];
		int num2;
		if(pBuffer[i+1]>=0) num2=(unsigned int)pBuffer[i+1];
		else num2=256+(unsigned int)pBuffer[i+1];
		checksum+=(num1<<8)+num2;
	}
	checksum = (checksum>>16)+(checksum&0x0000ffff);

	cout<<checksum<<endl;
	//if(checksum!=0xffff)checksum>>32
	if(checksum==0xffff) return true;
	else return false;
}

其中pBuffer指向ip首部。若校驗和正確返回true,否則返回false

 

在發送端

int getchecksum1(char *pBuffer){
	int i;
	int checksum=0;
	for(i=0;i<20;i=i+2){
		int num1;
		if(pBuffer[i]>=0)num1=(unsigned int)pBuffer[i];
		else num1=256+pBuffer[i];
	      int num2;
		if(pBuffer[i+1]>=0) num2=(unsigned int)pBuffer[i+1];
		else num2=256+pBuffer[i+1];
		checksum+=(num1<<8)+num2;
		printf("%x %x =%x, ",num1,num2,checksum);
		
	}
	printf("\n");
	checksum = (checksum>>16)+(checksum&0x0000ffff);
	checksum = ((~((short int)checksum))&0xffff); 
	printf("%x",checksum);

	return checksum;
}

pBuffer爲指向校驗和置爲0的其他首部選項已設置完成的首部的指針

返回值爲主機字節序的int型checksum,將其轉化爲網絡字節序,然後添加到首部checksum中即可。

 

4. 網絡字節序,主機字節序(轉)

不同的CPU有不同的字節序類型 這些字節序是指整數在內存中保存的順序 這個叫做主機序
最常見的有兩種
1. Little endian:將低序字節存儲在起始地址
2. Big endian:將高序字節存儲在起始地址

LE little-endian
最符合人的思維的字節序
地址低位存儲值的低位
地址高位存儲值的高位
怎麼講是最符合人的思維的字節序,是因爲從人的第一觀感來說
低位值小,就應該放在內存地址小的地方,也即內存地址低位
反之,高位值就應該放在內存地址大的地方,也即內存地址高位

BE big-endian
最直觀的字節序
地址低位存儲值的高位
地址高位存儲值的低位
爲什麼說直觀,不要考慮對應關係
只需要把內存地址從左到右按照由低到高的順序寫出
把值按照通常的高位到低位的順序寫出
兩者對照,一個字節一個字節的填充進去

例子:在內存中雙字0x01020304(DWORD)的存儲方式

內存地址
4000 4001 4002 4003
LE 04 03 02 01
BE 01 02 03 04

例子:如果我們將0x1234abcd寫入到以0x0000開始的內存中,則結果爲
      big-endian  little-endian
0x0000  0x12      0xcd
0x0001  0x23      0xab
0x0002  0xab      0x34
0x0003  0xcd      0x12
x86系列CPU都是little-endian的字節序.

網絡字節順序是TCP/IP中規定好的一種數據表示格式,它與具體的CPU類型、操作系統等無關,從而可以保證數據在不同主機之間傳輸時能夠被正確解釋。網絡字節順序採用big endian排序方式。

爲了進行轉換 bsd socket提供了轉換的函數 有下面四個
htons 把unsigned short類型從主機序轉換到網絡序
htonl 把unsigned long類型從主機序轉換到網絡序
ntohs 把unsigned short類型從網絡序轉換到主機序
ntohl 把unsigned long類型從網絡序轉換到主機序

在使用little endian的系統中 這些函數會把字節序進行轉換
在使用big endian類型的系統中 這些函數會定義成空宏

同樣 在網絡程序開發時 或是跨平臺開發時 也應該注意保證只用一種字節序 不然兩方的解釋不一樣就會產生bug.

注:
1、網絡與主機字節轉換函數:htons ntohs htonl ntohl (s 就是short l是long h是host n是network)
2、不同的CPU上運行不同的操作系統,字節序也是不同的,參見下表。
處理器    操作系統    字節排序
Alpha    全部    Little endian
HP-PA    NT    Little endian
HP-PA    UNIX    Big endian
Intelx86    全部    Little endian <-----x86系統是小端字節序系統
Motorola680x()    全部    Big endian
MIPS    NT    Little endian
MIPS    UNIX    Big endian
PowerPC    NT    Little endian
PowerPC    非NT    Big endian  <-----PPC系統是大端字節序系統
RS/6000    UNIX    Big endian
SPARC    UNIX    Big endian
IXP1200 ARM核心    全部    Little endian

 

5. 將ip地址用點分十進制輸出

 

	struct in_addr addr;
 
	addr.s_addr = htonl(srcAddr);

	printf("%s\n", inet_ntoa(addr));

其中srcAddr爲網絡字節序的ip地址。

 

6. 最後一點

做事細心一點,一個變量沒初始化,找了一下午。

 

發佈了24 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章