1/ 網(wǎng)絡(luò)字節(jié)順序是TCP/IP中規(guī)定好的一種數(shù)據(jù)表示格式,它與具體的CPU類型、操作系統(tǒng)等無(wú)關(guān),從而可以保證數(shù)據(jù)在不同主機(jī)之間傳輸時(shí)能夠被正確解釋,網(wǎng)絡(luò)字節(jié)順序采用big-endian排序方式。
2/ 而我們常用的 x86 CPU (intel, AMD) 電腦是 little-endian,也就是整數(shù)的低位字節(jié)放在內(nèi)存的低字節(jié)處。
舉個(gè)例子吧。假定你的數(shù)據(jù)是0x1234,
在網(wǎng)絡(luò)字節(jié)順序里 這個(gè)數(shù)據(jù)放到內(nèi)存中就應(yīng)該顯示成
addr addr+1
0x12 0x34
而在x86電腦上,數(shù)據(jù)0x1234放到內(nèi)存中實(shí)際是:
addr addr+1
0x34 0x12
htons 的用處就是把實(shí)際主機(jī)內(nèi)存中的整數(shù)存放方式調(diào)整成網(wǎng)絡(luò)字節(jié)順序。
------------------------------------------------------------------------------------------------------------
大端小端(Big- Endian和Little-Endian) 字節(jié)序(Endian),大端(Big-Endian),小端(Little-Endian) 圖文并茂
http://m.shnenglu.com/tx7do/archive/2009/01/06/71276.html
http://my.oschina.net/alphajay/blog/5478
在 各種計(jì)算機(jī)體系結(jié)構(gòu)中,對(duì)于字節(jié)、字等的存儲(chǔ)機(jī)制有所不同,因而引發(fā)了計(jì)算機(jī) 通信領(lǐng) 域中一個(gè)很重要的問(wèn)題,即通信雙方交流的信息單元(比特、字節(jié)、字、雙字等等)應(yīng)該以什么樣的順序進(jìn)行傳送。如果不達(dá)成一致的規(guī)則,通信雙方將無(wú)法進(jìn)行正 確的編/譯碼從而導(dǎo)致通信失敗。目前在各種體系的計(jì)算機(jī)中通常采用的字節(jié)存儲(chǔ)機(jī)制主要有兩種:Big-Endian和Little-Endian,下面先 從字節(jié)序說(shuō)起。
一、什么是字節(jié)序
字節(jié)序,顧名思義字節(jié)的順序,再多說(shuō)兩句就是大于一個(gè)字節(jié)類型的數(shù)據(jù)在內(nèi)存中的存放順序(一個(gè)字節(jié)的數(shù)據(jù)當(dāng)然就無(wú)需談順序的問(wèn)題了)。其實(shí)大部分人在實(shí)際的開(kāi) 發(fā)中都很少會(huì)直接和字節(jié)序打交道。唯有在跨平臺(tái)以及網(wǎng)絡(luò)程序中字節(jié)序才是一個(gè)應(yīng)該被考慮的問(wèn)題。
在所有的介紹字節(jié)序的文章中都會(huì)提到字 節(jié)序分為兩類:Big-Endian和Little-Endian,引用標(biāo)準(zhǔn)的Big-Endian和Little-Endian的定義如下:
a) Little-Endian就是低位字節(jié)排放在內(nèi)存的低地址端,高位字節(jié)排放在內(nèi)存的高地址端。
b) Big-Endian就是高位字節(jié)排放在內(nèi)存的低地址端,低位字節(jié)排放在內(nèi)存的高地址端。
c) 網(wǎng)絡(luò)字節(jié)序:TCP/IP各層協(xié)議將字節(jié)序定義為Big-Endian,因此TCP/IP協(xié)議中使用的字節(jié)序通常稱之為網(wǎng)絡(luò)字節(jié)序。
1.1 什么是高/低地址端
首先我們要知道我們C程序映像中內(nèi)存的空間布局情況:在《C專 家編程》中或者《Unix環(huán)境高級(jí)編程》中有關(guān)于內(nèi)存空間布局情況的說(shuō)明,大致如下圖:
----------------------- 最高內(nèi)存地址 0xffffffff
棧底
棧
棧頂
-----------------------
NULL (空洞)
-----------------------
堆
-----------------------
未初始 化的數(shù)據(jù)
----------------------- 統(tǒng)稱數(shù)據(jù)段
初始化的數(shù)據(jù)
-----------------------
正 文段(代碼段)
----------------------- 最低內(nèi)存地址 0x00000000
以上圖為例如果我們?cè)跅?上分配一個(gè)unsigned char buf[4],那么這個(gè)數(shù)組變量在棧上是如何布局的呢?看下圖:
棧底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
棧頂 (低地址)
1.2 什么是高/低字節(jié)
現(xiàn)在我們弄清了高/低地址,接著考慮高/低字節(jié)。有些文章中稱低位字節(jié)為最低有效位,高位字節(jié)為最高有效位。如果我們有一個(gè)32位無(wú)符號(hào)整型0x12345678,那么高位是什么,低位又是什么呢? 其實(shí)很簡(jiǎn)單。在十進(jìn)制中我們都說(shuō)靠左邊的是高位,靠右邊的是低位,在其他進(jìn)制也是如此。就拿 0x12345678來(lái)說(shuō),從高位到低位的字節(jié)依次是0x12、0x34、0x56和0x78。
高/低地址端和高/低字節(jié)都弄清了。我們?cè)賮?lái)回顧 一下Big-Endian和Little-Endian的定義,并用圖示說(shuō)明兩種字節(jié)序:
以u(píng)nsigned int value = 0x12345678為例,分別看看在兩種字節(jié)序下其存儲(chǔ)情況,我們可以用unsigned char buf[4]來(lái)表示value:
Big-Endian: 低地址存放高位,如下圖:
棧底 (高地址)
---------------
buf[3] (0x78) -- 低位
buf[2] (0x56)
buf[1] (0x34)
buf[0] (0x12) -- 高位
---------------
棧頂 (低地址)
Little-Endian: 低地址存放低位,如下圖:
棧底 (高地址)
---------------
buf[3] (0x12) -- 高位
buf[2] (0x34)
buf[1] (0x56)
buf[0] (0x78) -- 低位
--------------
棧 頂 (低地址)
二、各種Endian
2.1 Big-Endian
計(jì)算機(jī)體系結(jié)構(gòu)中一種描述多字節(jié)存儲(chǔ)順序的術(shù)語(yǔ),在這種機(jī)制中最重要字節(jié)(MSB)存放在最低端的地址 上。采用這種機(jī)制的處理器有IBM3700系列、PDP-10、Mortolora微處理器系列和絕大多數(shù)的RISC處理器。
+----------+
| 0x34 |<-- 0x00000021
+----------+
| 0x12 |<-- 0x00000020
+----------+
圖 1:雙字節(jié)數(shù)0x1234以Big-Endian的方式存在起始地址0x00000020中
在Big-Endian中,對(duì)于bit序列 中的序號(hào)編排方式如下(以雙字節(jié)數(shù)0x8B8A為例):
bit 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+-----------------------------------------+
val | 1 0 0 0 1 0 1 1 | 1 0 0 0 1 0 1 0 |
+----------------------------------------+
圖 2:Big-Endian的bit序列編碼方式
2.2 Little-Endian
計(jì)算機(jī)體系結(jié)構(gòu)中 一種描述多字節(jié)存儲(chǔ)順序的術(shù)語(yǔ),在這種機(jī)制中最不重要字節(jié)(LSB)存放在最低端的地址上。采用這種機(jī)制的處理器有PDP-11、VAX、Intel系列 微處理器和一些網(wǎng)絡(luò)通信設(shè)備。該術(shù)語(yǔ)除了描述多字節(jié)存儲(chǔ)順序外還常常用來(lái)描述一個(gè)字節(jié)中各個(gè)比特的排放次序。
+----------+
| 0x12 |<-- 0x00000021
+----------+
| 0x34 |<-- 0x00000020
+----------+
圖3:雙字節(jié)數(shù)0x1234以Little-Endian的方式存在起始地址0x00000020中
在 Little-Endian中,對(duì)于bit序列中的序號(hào)編排和Big-Endian剛好相反,其方式如下(以雙字節(jié)數(shù)0x8B8A為例):
bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+-----------------------------------------+
val | 1 0 0 0 1 0 1 1 | 1 0 0 0 1 0 1 0 |
+-----------------------------------------+
圖 4:Little-Endian的bit序列編碼方式
注2:通常我們說(shuō)的主機(jī)序(Host Order)就是遵循Little-Endian規(guī)則。所以當(dāng)兩臺(tái)主機(jī)之間要通過(guò)TCP/IP協(xié)議進(jìn)行通信的時(shí)候就需要調(diào)用相應(yīng)的函數(shù)進(jìn)行主機(jī)序 (Little-Endian)和網(wǎng)絡(luò)序(Big-Endian)的轉(zhuǎn)換。
注3:正因?yàn)檫@兩種機(jī)制對(duì)于同一bit序列的序號(hào)編排方式恰 恰相反,所以《現(xiàn)代英漢詞典》中對(duì)MSB的翻譯為“最高有效位”欠妥,故本文定義為“最重要的bit/byte”。
2.3 Middle-Endian
除了Big-Endian和Little-Endian之外的多字節(jié)存儲(chǔ)順序就是Middle- Endian,比如以4個(gè)字節(jié)為例:象以3-4-1-2或者2-1-4-3這樣的順序存儲(chǔ)的就是Middle-Endian。這種存儲(chǔ)順序偶爾會(huì)在一些小 型機(jī)體系中的十進(jìn)制數(shù)的壓縮格式中出現(xiàn)。
嵌入式系統(tǒng)開(kāi)發(fā)者應(yīng)該對(duì)Little-endian和Big-endian模式非常了解。采用 Little-endian模式的CPU對(duì)操作數(shù)的存放方式是從低字節(jié)到高字節(jié),而Big-endian模式對(duì)操作數(shù)的存放方式是從高字節(jié)到低字節(jié)。 32bit寬的數(shù)0x12345678在Little-endian模式CPU內(nèi)存中的存放方式(假設(shè)從地址0x4000開(kāi)始存放)為:
內(nèi)存 地址 | 0x4000 | 0x4001 | 0x4002 | 0x4003 |
存放內(nèi)容 | 0x78 | 0x56 | 0x34 | 0x12 |
而在Big- endian模式CPU內(nèi)存中的存放方式則為:
內(nèi)存地址 | 0x4000 | 0x4001 | 0x4002 | 0x4003 |
存放內(nèi)容 | 0x12 | 0x34 | 0x56 | 0x78 |
三、Big-Endian和Little-Endian優(yōu)缺點(diǎn)
Big-Endian優(yōu)點(diǎn):靠首先提取高位字節(jié),你總是可以由看看在偏移位置為0的字節(jié)來(lái)確定這個(gè)數(shù)字是 正數(shù)還是負(fù)數(shù)。你不必知道這個(gè)數(shù)值有多長(zhǎng),或者你也不必過(guò)一些字節(jié)來(lái)看這個(gè)數(shù)值是否含有符號(hào)位。這個(gè)數(shù)值是以它們被打印出來(lái)的順序存放的,所以從二進(jìn)制到十進(jìn)制的函數(shù)特別有效。因而,對(duì)于不同要求的機(jī)器,在設(shè)計(jì)存取方式時(shí)就會(huì)不同。
Little-Endian優(yōu)點(diǎn):提取一個(gè),兩個(gè),四個(gè)或者更長(zhǎng)字節(jié)數(shù)據(jù)的匯編指令以與其他所有格式相同的方式進(jìn)行:首先在偏移地址為0的地方提取最低位的字節(jié),因?yàn)榈刂菲坪妥止?jié)數(shù)是一對(duì) 一的關(guān)系,多重精度的數(shù)學(xué)函數(shù)就相對(duì)地容易寫(xiě)了。
如 果你增加數(shù)字的值,你可能在左邊增加數(shù)字(高位非指數(shù)函數(shù)需要更多的數(shù)字)。 因此, 經(jīng)常需要增加兩位數(shù)字并移動(dòng)存儲(chǔ)器里所有Big-endian順序的數(shù)字,把所有數(shù)向右移,這會(huì)增加計(jì)算機(jī)的工作量。不過(guò),使用Little- Endian的存儲(chǔ)器中不重要的字節(jié)可以存在它原來(lái)的位置,新的數(shù)可以存在它的右邊的高位地址里。這就意味著計(jì)算機(jī)中的某些計(jì)算可以變得更加簡(jiǎn)單和快速。
四、如何檢查處理器是Big-Endian還是Little-Endian?
由于聯(lián)合體union的存放順序是所有成員都從低地址開(kāi)始存放,利用該特性就可以輕松地獲得了CPU對(duì)內(nèi)存采用Little- endian還是Big-endian模式讀寫(xiě)。例如:
int checkCPUendian(){
union {
unsigned int a;
unsigned char b;
}c;
c.a = 1;
return (c.b == 1);
} /*return 1 : little-endian, return 0:big-endian*/
五、Big-Endian和Little-Endian轉(zhuǎn) 換
現(xiàn)有的平臺(tái)上Intel的X86采用的是Little-Endian,而像 Sun的SPARC采用的就是Big-Endian。那么在跨平臺(tái)或網(wǎng)絡(luò)程序中如何實(shí)現(xiàn)字節(jié)序的轉(zhuǎn)換呢?這個(gè)通過(guò)C語(yǔ)言的移位操作很容易實(shí)現(xiàn),例如下面的 宏:
#if defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN)
#define htons(A) (A)
#define htonl(A) (A)
#define ntohs(A) (A)
#define ntohl(A) (A)
#elif defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN)
#define htons(A) ((((uint16)(A) & 0xff00) >> 8) | \
(((uint16)(A) & 0x00ff) << 8))
#define htonl(A) ((((uint32)(A) & 0xff000000) >> 24) | \
(((uint32)(A) & 0x00ff0000) >> 8) | \
(((uint32)(A) & 0x0000ff00) << 8) | \
(((uint32)(A) & 0x000000ff) << 24))
#define ntohs htons
#define ntohl htohl
#else
#error "Either BIG_ENDIAN or LITTLE_ENDIAN must be #defined, but not both."
網(wǎng)絡(luò)字節(jié)順序
1、字節(jié)內(nèi)的比特位不受這種順序的影響
比如一個(gè)字節(jié) 1000 0000 (或表示為十六進(jìn)制 80H)不管是什么順序其內(nèi)存中的表示法都是這樣。
2、大于1個(gè)字節(jié)的數(shù)據(jù)類型才有字節(jié)順序問(wèn)題
比如 Byte A,這個(gè)變量只有一個(gè)字節(jié)的長(zhǎng)度,所以根據(jù)上一條沒(méi)有字節(jié)順序問(wèn)題。所以字節(jié)順序是“字節(jié)之間的相對(duì)順序”的意思。
3、大于1個(gè)字節(jié)的數(shù)據(jù)類型的字節(jié)順序有兩種
比如 short B,這是一個(gè)兩字節(jié)的數(shù)據(jù)類型,這時(shí)就有字節(jié)之間的相對(duì)順序問(wèn)題了。
網(wǎng)絡(luò)字節(jié)順序是“所見(jiàn)即所得”的順序。而Intel類型的CPU的字節(jié)順序與此相反。
比如上面的 short B=0102H(十六進(jìn)制,每?jī)晌槐硎疽粋€(gè)字節(jié)的寬度)。所見(jiàn)到的是“0102”,按一般數(shù)學(xué)常識(shí),數(shù)軸從左到右的方向增加,即內(nèi)存地址從左到右增加的話,在內(nèi)存中這個(gè) short B的字節(jié)順序是:
01 02
這就是網(wǎng)絡(luò)字節(jié)順序。所見(jiàn)到的順序和在內(nèi)存中的順序是一致的!
假設(shè)通過(guò)抓包得到網(wǎng)絡(luò)數(shù)據(jù)的兩個(gè)字節(jié)流為:01 02
而相反的字節(jié)順序就不同了,其在內(nèi)存中的順序?yàn)椋?2 01
如果這表示兩個(gè) Byte類型的變量,那么自然不需要考慮字節(jié)順序的問(wèn)題。如果這表示一個(gè) short 變量,那么就需要考慮字節(jié)順序問(wèn)題。根據(jù)網(wǎng)絡(luò)字節(jié)順序“所見(jiàn)即所得”的規(guī)則,這個(gè)變量的值就是:0102
假設(shè)本地主機(jī)是Intel類型的,那么要表示這個(gè)變量,有點(diǎn)麻煩:
定義變量 short X,字節(jié)流地址為:pt,按順序讀取內(nèi)存是為x=*((short*)pt);
那么X的內(nèi)存順序當(dāng)然是 01 02按非“所見(jiàn)即所得”的規(guī)則,這個(gè)內(nèi)存順序和看到的一樣顯然是不對(duì)的,所以要把這兩個(gè)字節(jié)的位置調(diào)換。調(diào)換的方法可以自己定義,但用已經(jīng)有的API還是更為方便。
網(wǎng)絡(luò)字節(jié)順序與主機(jī)字節(jié)順序
NBO 與HBO 網(wǎng)絡(luò)字節(jié)順序NBO(Network Byte Order):按從高到低的順序存儲(chǔ),在網(wǎng)絡(luò)上使用統(tǒng)一的網(wǎng)絡(luò)字節(jié)順序,可以避免兼容性問(wèn)題。主機(jī)字節(jié)順序(HBO,Host Byte Order):不同的機(jī)器HBO不相同,與CPU設(shè)計(jì)有關(guān)計(jì)算機(jī)數(shù)據(jù)存儲(chǔ)有兩種字節(jié)優(yōu)先順序:高位字節(jié)優(yōu)先和低位字節(jié)優(yōu)先。Internet上數(shù)據(jù)以高位字節(jié)優(yōu)先順序在網(wǎng)絡(luò)上傳輸,所以對(duì)于在內(nèi)部是以低位字節(jié)優(yōu)先方式存儲(chǔ)數(shù)據(jù)的機(jī)器,在Internet上傳輸數(shù)據(jù)時(shí)就需要進(jìn)行轉(zhuǎn)換。
htonl()
簡(jiǎn)述:
將主機(jī)的無(wú)符號(hào)長(zhǎng)整形數(shù)轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)順序。
#include <winsock.h>
u_long PASCAL FAR htonl( u_long hostlong);
hostlong:主機(jī)字節(jié)順序表達(dá)的32位數(shù)。
注釋:
本函數(shù)將一個(gè)32位數(shù)從主機(jī)字節(jié)順序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)順序。
返回值:
htonl()返回一個(gè)網(wǎng)絡(luò)字節(jié)順序的值。
inet_ntoa()
簡(jiǎn)述:
將網(wǎng)絡(luò)地址轉(zhuǎn)換成“.”點(diǎn)隔的字符串格式。
#include <winsock.h>
char FAR* PASCAL FAR inet_ntoa( struct in_addr in);
in:一個(gè)表示Internet主機(jī)地址的結(jié)構(gòu)。
注釋:
本 函數(shù)將一個(gè)用in參數(shù)所表示的Internet地址結(jié)構(gòu)轉(zhuǎn)換成以“.” 間隔的諸如“a.b.c.d”的字符串形式。請(qǐng)注意inet_ntoa()返回的字符串存放在WINDOWS套接口實(shí)現(xiàn)所分配的內(nèi)存中。應(yīng)用程序不應(yīng)假設(shè) 該內(nèi)存是如何分配的。在同一個(gè)線程的下一個(gè)WINDOWS套接口調(diào)用前,數(shù)據(jù)將保證是有效。
返回值:
若無(wú)錯(cuò)誤發(fā)生,inet_ntoa()返回一個(gè)字符指針。否則的話,返回NULL。其中的數(shù)據(jù)應(yīng)在下一個(gè)WINDOWS套接口調(diào)用前復(fù)制出來(lái)。
網(wǎng) 絡(luò)中傳輸?shù)臄?shù)據(jù)有的和本地字節(jié)存儲(chǔ)順序一致,而有的則截然不同,為了數(shù)據(jù)的一致性,就要把本地的數(shù)據(jù)轉(zhuǎn)換成網(wǎng)絡(luò)上使用的格式,然后發(fā)送出去,接收的時(shí)候也 是一樣的,經(jīng)過(guò)轉(zhuǎn)換然后才去使用這些數(shù)據(jù),基本的庫(kù)函數(shù)中提供了這樣的可以進(jìn)行字節(jié)轉(zhuǎn)換的函數(shù),如和htons( ) htonl( ) ntohs( ) ntohl( ),這里n表示network,h表示host,htons( ) htonl( )用于本地字節(jié)向網(wǎng)絡(luò)字節(jié)轉(zhuǎn)換的場(chǎng)合,s表示short,即對(duì)2字節(jié)操作,l表示long即對(duì)4字節(jié)操作。同樣ntohs( )ntohl( )用于網(wǎng)絡(luò)字節(jié)向本地格式轉(zhuǎn)換的場(chǎng)合。
#endif