天天看點

網絡通訊中的位元組序

程式間的通信,說到底便是發送和接收資料流。我們一般把位元組(byte)看作是資料的最小機關。當然,其實一個位元組中還包含8位(即bit位)。32位的處理器中“字長”為32個bit,也就是4個byte。在這樣的CPU中,總是以4位元組對齊的方式來讀取或寫入記憶體,那麼同樣這4個位元組的資料是以什麼順序儲存在記憶體中的呢? 這就是位元組序的問題。

一、位元組序

顧名思義位元組的順序,再多說兩句就是大于一個位元組類型的資料在記憶體中的存放順序(一個位元組的資料當然就無需談順序的問題了)。

下面羅列常見的資料類型及其長度對照表:

Wtypes.h 中的非托管類型 非托管C 語言類型 .net托管類名 長度

HANDLE void* System.IntPtr 32 bit位

BYTE unsigned char System.Byte 8 位

SHORT short System.Int16 16 位

WORD unsigned short System.UInt16 16 位

INT int System.Int32 32 位

UINT unsigned int System.UInt32 32 位

LONG long System.Int32 32 位

BOOL long System.Int32 32 位

DWORD unsigned long System.UInt32 32 位

ULONG unsigned long System.UInt32 32 位

CHAR char System.Byte 8 位。

FLOAT Float System.Single 32 位

DOUBLE Double System.Double 64 位

二、位元組序分類

有三種:Big-Endian、Little-Endian以及Middle-Endian。常見的主要是Big-Endian和Little-Endian,中文叫高位元組序和低位元組序(或者是大位元組序和小位元組序)。引用标準的Big-Endian和Little-Endian的定義如下:

a) Little-Endian就是低位位元組排放在記憶體的低位址端,高位位元組排放在記憶體的高位址端。

b) Big-Endian就是高位位元組排放在記憶體的低位址端,低位位元組排放在記憶體的高位址端。

同時引進主機位元組序和網絡位元組序概念。網絡位元組序肯定是Big-Endian高位元組序,而主機位元組序基本上是Little-Endian低位元組序,但是主機位元組序跟處理器即CPU類型有關,CISC架構的CPU如:X86(包括大部分的intel、AMD等PC處理器)是低位元組序,而在嵌入式廣泛應用的RISC架構的CPU如:ARM, PowerPC, Alpha, SPARC V9, MIPS等是高位元組序的。

三、位元組序說明

以c中定義一個16進制的變量為例:unsigned int value = 0x6a7b8c9d,根據上述類型對照表得知,c的unsigned int的value變量為32bit即4個byte。這裡的value也等價于:

unsigned char buf[4]= { 0x6a,0x7b,0x8c,0x9d}://文法有問題,就是相當于這麼個初始化

現在分别按轉高位元組序和低位元組序解釋下,加入這個buf變量的記憶體初始位址是a:

a) Little-Endian: 低位址存放低位 :

(高位址)--------------------------

a+3----- buf[3] (0x6a) -- 高位(相當于10進制的千位)

a+2----- buf[2] (0x7b)

a+1----- buf[1] (0x8c)

a-------- buf[0] (0x9d) -- 低位(相當于10進制的個位)

(低位址)--------------------------

b) Big-Endian: 低位址存放高位:

a+3----- buf[3] (0x9d) -- 高位(相當于10進制的千位)

a+2----- buf[2] (0x8c)

a+1----- buf[1] (0x7b)

a-------- buf[0] (0x6a) -- 低位(相當于10進制的個位)

一般的處理器高位址對應的是棧底,低位址對應的是棧頂。

四、高/低位元組序轉換

如果需要在不同的作業系統,或者是基于不同的語言,如c/c++和c#之間不同主機位元組序之的網絡通信,就需要考慮如何轉換位元組序。

如果不轉換可能遇到問題,這裡舉個C的用戶端和伺服器端通信的例子

用戶端(低位元組序的主機,如intel 奔騰系列處理器)定義: short x=1 為2個byte,在記憶體中為[1][0](低位址在前面),發送給伺服器端。這伺服器端(高位元組序的主機,如嵌入式的ARM處理器)接收到的為:[1][0](低位址在前面),此時按照高位元組序的"低位址存放高位"原則解析的x=256。于實際情況不符。

是以針對這樣的情況,編寫跨平台或者是跨語言的程式是需要考慮位元組序的轉換。一般資料發送出去之前需要将主機位元組序Little-Endian轉換為網絡位元組序Big-Endian,接收之前需要将Big-Endian轉為Little-Endian,下面介紹小.net和C/C++常見方法。

4.1、 .net中:

主機位元組序到網絡位元組序:short/int/long IPAddress.HostToNetworkOrder(short/int/long)

網絡位元組序到主機位元組序:short/int/long IPAddress.NetworkToHostOrder(short/int/long)

4.2 、C/C++根據類型的不同有如下方法:

ntohs =net to host short int 16位

htons=host to net short int 16位

ntohl =net to host long int 32位

htonl=host to net long int 32位

簡單的說明其中一個方法吧。

将一個無符号短整形數從網絡位元組順序轉換為主機位元組順序。

#include <winsock.h>

u_short PASCAL FAR ntohs( u_short netshort);

netshort:一個以網絡位元組順序表達的16位數。

注釋:

本函數将一個16位數由網絡位元組順序轉換為主機位元組順序。

傳回值:

ntohs()傳回一個以主機位元組順序表達的數。

五、實際轉換過程需要注意點及問題集。

5.1:C++中的unsigned char類型等同于C#的什麼類型?

答:C#中的char是16bits的Unicode字元,而一般C++中的字元則是8位的,是以C++中的“unsigned char”在C#中要麼轉換成char,要麼使用Byte類型來代替,前者适用于存放字元型的unsigned char,後者适用于整數型的unsigned char。具體的程式,具體的方法。 比如:c++中申明變量,unsigned char para=0x4a(表示十六進制=2的4次方 ×2的4次方,即8位。 uchar的的範圍為0-0xff,即0-255);unsigned char para[4] = 0x6789abcd,表示32位。

5.2:為什麼在網絡程式設計中,即需要考慮位元組序的問題時。對于double、float以及字元串等資料類型不需要考慮主機序列和網絡序列之間的轉換?

答:至于float和double,與CPU無關。一般編譯器是按照IEEE标準解釋的,即把float/double看作4/8個字元的數組進行解釋。是以,隻要編譯器是支援IEEE浮點标準的,就不需要考慮位元組順序。

5.3: BinaryWriter和BinaryReader

BinaryReader和BinaryWriter使用小位元組序(即低位元組序)讀寫資料。

如例子:

var stream = new MemoryStream(new byte[] { 4, 1, 0, 0 }); //相當于申請了byte[4],而每個byte相當于256進制

var reader = new BinaryReader(stream);

int i = reader.ReadInt32(); // i == 260

//因為BinaryReader是按照低位元組序讀取的,所有i=4+256×1=260;

5.4、BitConverter和ASCIIEncoding.ASCII.GetBytes

BitConverter主要是用于.net中byte[]和其他類型的轉換,不涉及字元串資料類型。網絡通信會經常用到。

ASCIIEncoding.ASCII通常用于字元串和byte[]之間的轉換。

5.x: 關于“同樣4個位元組的資料,我們可以把它看作是1個32位整數、2個Unicode、或者字元4個ASCII字元。”引申:

unicode和utf-8之間最大的差別就是在存儲上。unicode是寬字元存儲(字元都是2個位元組或4個位元組來存儲),而utf-8是多位元組存

儲,字元的個數是不确定的(比如英文字元是1個位元組表示,漢字可以是2個到6個來表示),其字元的首位元組的前幾位表明了它的位元組

個數。比如某個3位元組漢字的uft-8編碼(二進制)如下:

1110xxxx 10xxxxxx 10xxxxxx

首位元組中1的個數為3表明該漢字用3個位元組來表示。

utf-16固定使用兩個位元組表示一個字元,平常所說的unicode編碼就是utf-16的。

ascii字元在utf-8裡面還是一樣的,一個位元組表示,超出ascii字元範圍的,就用多位元組表示,位元組的數目由第一位元組确定,最多6

位元組。如下:

utf-8:

1位元組:0XXXXXXX(ascii)

2位元組:110XXXXX 10XXXXXX

3位元組:1110XXXX 10XXXXXX 10XXXXXX

4位元組:11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

5位元組:。。。

utf-16:所有:XXXXXXXX XXXXXXXX

ascii:XXXXXXXX 00000000

ASCII碼是用十六進制表示,也就是說它是兩個位元組byte。如:ASCII碼為31(即0x31)對應的字元"1";ASCII碼為32(即0x31)對

應的字元"2".

本文轉自94cool部落格園部落格,原文連結:http://www.cnblogs.com/94cool/archive/2010/03/29/1699615.html,如需轉載請自行聯系原作者