1. 程式人生 > >網路傳輸過程中的位元組序列問題

網路傳輸過程中的位元組序列問題

一、大端儲存和小端儲存

  1、大端儲存:多於一個位元組的資料,把高位元組部分儲存在低地址,把低位元組部分儲存在高地址。

       例:0x12345678這個資料,我們一般認為左邊是高位元組部分,右邊是低位元組部分,那麼在採用大端儲存的計算機內部的儲存則為下面這樣

               低地址:     0x12(高位元組)

               >>>>>:     0x34

               >>>>>:     0x56

               高地址:     0x78(低位元組)

 即一個整型資料的首地址=高位部分(首地址=低地址)


        目前使用大端儲存的廠商主要有:IBMMotorolaSun Microsystems公司大多數微處理器採用大端儲存法。

  2、小端儲存:多於一個位元組的資料,把高位元組部分儲存在高地址,把低位元組部分存放在低地址。

      例:0x12345678這個資料,我們一般認為左邊是高位元組部分,右邊是低位元組部分,那麼在採用小端儲存的計算機內部的儲存則為如何下這樣

               低地址:     0x78(低位元組)

               >>>>>:     0x56

               >>>>>:     0x34

               高地址:     0x12(高位元組)

 即首地址部分=低位部分(首地址=低地址)

        目前使用小端儲存的廠商主要有:intel

        其他的一些廠商如ARMMIPSMotorola的PowerPC等,可以通過晶片上電啟動時確定的 位元組儲存順序規則,來選擇儲存模式。

二、網路位元組序

 1、目前網路中的體系採用TCP/IP協議,TCP/IP協議規定對於多位元組資料,採用大端儲存。

    另外,TCP/IP協議規定:接收方接收到的第一個位元組是高位元組,儲存到低地址。那麼由於網路TCP/IP協議 從低地址部分開始傳送位元組資料,所以可以知道,網路位元組序列     採用的大端儲存。

 2、同一儲存模式之間傳送資料

    (1)、同為小端儲存的雙方通訊

           小端儲存,傳送方從低地址開始傳送(低地址實際上是低位元組部分,按照網路位元組序來說,這個取的高位元組),然後接收方把接收到的資料儲存在低地址部分(接            收到的直接是資料的低位元組部分,並存儲在低地址上,符合小端儲存。但是網路位元組序來說,作為高位元組),通訊雙方並沒有把本機位元組序轉為網路位元組序,但是            依然完成了資料的正確發收。大端儲存也一樣。可見同一儲存模式的雙方並不需要進行位元組序列的轉換。

    (2)、小端和大端模式之間的通訊

           小端儲存作為傳送方,若直接傳送,則把資料的低地址傳送出去(實際傳送的是資料的低位元組),大端儲存接收,把這個資料儲存在低地址,(接收到的是低字              節,但是由於是大端儲存模式,所以平臺認為是高位元組),這樣就導致了資料的錯誤收發。那麼在不同儲存模式之間進行通訊,我們就需要進行位元組序列的轉換,            通常,只需要一次轉換即可,一般都是將小端轉換為大端,因為網路位元組序採用大端模式。

    (3)、通訊雙方是否應該轉換

           由於我們不清楚收發雙方是什麼儲存模式,所以在傳送時,都應該將本機位元組序轉為網路位元組序,然後,作為接收方,都應該將網路位元組序轉為本機位元組序列然後            在進行儲存。

 三、如何判斷機器的大端和小端?

主要原理:我們知道大端儲存和小端儲存主要是針對超過一個位元組的資料而言的,那麼對於位元組型資料在各平臺的儲存都是一樣的,都是按照地址進行順序排列儲存的,一般都是從低地址往高地址進行排列。同時,我們說通過指標獲取一個數據型別的地址,通常也都是獲取的該資料的起始地址,即低地址。

以下實驗在我的x86機器上進行實驗,所以應該是

方法一、採用不同指標型別unsigned int和char指標型別指向同一整數型別

//寫法一:往一整型資料0的起始地址寫入一位元組的資料,如果為小端,則該位元組資料應該等於該整型資料。如果為大端,則該位元組的資料應該剛好為該整型資料的高位部分。如寫入22,如果為小端,則該整型資料應該等於22。如果為大端,則該整型資料應該等於22000000。                                                                                                         #include "stdafx.h"
#include <iostream>


int main()
{
	unsigned int a=0;
	unsigned int * p = &a;
	unsigned char* p1 = (unsigned char *)p;
	*p1 = 22;
	switch (a)
	{
	case 22: std::cout << "小端" << std::endl;
		break;
	case 22000000:std::cout << "大端" << std::endl;
	default:std::cout << "不能識別該平臺儲存模式" << std::endl;
		break;
	}
	system("pause");

    return 0;
}

 
 //寫法二:讀出整數型別的起始地址的一位元組資料,如果為小端,則該位元組應該等於該整數的低位部分。如果為大端,則該位元組應該等於該整數的高位部分。                                                                                                 如22,如果為小端,則該位元組應該等於22。                                                                                        ruguo果為大端,則該位元組應該等於0。                                                                                                                  #include "stdafx.h"
#include <iostream>


int main()
{
	unsigned int a=22;
	unsigned int * p = &a;//獲取a的首地址
	unsigned char* p1 = (unsigned char *)p;
	switch (*p1)
	{
	case 22: std::cout << "小端" << std::endl;
		break;
	case 0:std::cout << "大端" << std::endl;
	default:std::cout << "不能識別該平臺儲存模式" << std::endl;
		break;
	}
	system("pause");

    return 0;
}


方法二、利用聯合體的共享記憶體的方式來判定
#include "stdafx.h"
#include <iostream>

union MyUnion
{
	char str;
	unsigned int data;
};  //str和data共享記憶體,其中str與data的低地址的第一個位元組相同,即str=data的首地址。
int main()
{
	union MyUnion a;
	a.data = 0x12345678;
	switch (a.str)  //獲取到了data的首地址部分的值
	{
	case 0x78: std::cout << "小端" << std::endl;    //首地址=低位部分,則為小端
		break;
	case 0x12:std::cout << "大端" << std::endl;     //首地址=高位部分,則為大端
	default:std::cout << "不能識別該平臺儲存模式" << std::endl;
		break;
	}
	system("pause");

    return 0;
}


方法三、LINUX核心開發者的程式碼,原理也是利用union的共享記憶體機制

1  static union { char c[4]; unsigned long mylong; } endian_test = {{ 'l', '?', '?', 'b' } };
2     #define ENDIANNESS ((char)endian_test.mylong)  //強制轉換,截斷,取mylong的低位部分。如果是小端,則低位部分應該為‘l’。如果為大端,則低位為‘b’。
3     cout<<ENDIANNESS<<endl;                                                                                      未驗證過,思想也和方法二一樣                                                                                                           
記憶體分佈(小端):  高地址  >>>>>>>  b    高位部分
                                             >>>>>>>  ?
                                             >>>>>>>  ?
                                 低地址  >>>>>>>  l    低位部分
記憶體分佈(大端):  高地址  >>>>>>>  b    低位部分
                                             >>>>>>>  ?
                                             >>>>>>>  ?
                                 低地址  >>>>>>>  l    高位部分

最後總結一下需要進行大小端轉換的一些資料型別:

我們知道大小端儲存問題只會出現在超過一個位元組的資料型別之中,那麼針對常用資料型別

以下資料型別需要進行轉換:

整數型別、浮點型

注意:字元和字串型別不需要進行轉換,因為他們是單位元組編碼的資料型別,在計算機中都是一個位元組為單位進行存放的。

另外unicode的字元如果使用utf-8編碼則不需要轉換,因為UTF-8編碼也是單位元組編碼。如果是utf-16編碼則根據big和Little的來確定是否需要轉換,如果是Big則不需要轉換,如果是little則需要轉換。在網路傳輸中,我們最好採用utf-8編碼,這樣可以完全保證傳輸的正確性。

大小端轉換隻在跨大小端平臺進行傳輸的時候,我們才需要進行轉換,如果不跨平臺,則不需要轉換,目前來說,Client端基本都是x86體系,如果伺服器也是x86體系的,不用轉換也不會出現問題。