網絡字節轉換

做過socket的都知道網絡字節轉換的事情,網絡中傳輸的數據是純字節流,沒有類型信息,從低地址開始傳遞;網絡字節序通常爲大端的,即先傳遞高字節,因此和大端的本地字節存儲順序一致,和小端的則截然相反。爲了數據的一致性,就要把本地的數據轉換成網絡上使用的格式,然後發送出去,接收的時候也是一樣的,經過轉換然後纔去使用這些數據。基本的庫函數中提供了這樣的可以進行字節轉換的函數,如和htons( ) htonl( ) ntohs( ) ntohl( ),這裏n表示network,h表示host,htons( ) htonl( )用於本地字節向網絡字節轉換的場合,s表示short,即對2字節操作,l表示long即對4字節操作。同樣ntohs( )ntohl( )用於網絡字節向本地格式轉換的場合。隨着c99標準的推行,我們偉大的c中增加了新的類型long long int ,unsigned long long int,都是64位的,怎麼辦?不轉肯定是不行,就得自己想辦法把它轉了。當然有很多方法,我這裏想使用一種類比的解決方法,看看如何舉一反三。

 

一、字節序定義
字節序,顧名思義字節的順序,再多說兩句就是大於一個字節類型的數據在內存中的存放順序。其實大部分人在實際的開發中都很少會直接和字節序打交道。唯有在跨平臺以及網絡程序中字節序纔是一個應該被考慮的問題。一次Sun SPARC到Intel X86的平臺移植讓我們的程序遭遇了“字節序問題”。

 

在所有的介紹字節序的文章中都會提到字節序分爲兩類:Big-Endian和Little-Endian。引用標準的Big-Endian和Little-Endian的定義如下:

a) Little-Endian就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。

b) Big-Endian就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。

c) 網絡字節序:TCP/IP各層協議將字節序定義爲Big-Endian,因此TCP/IP協議中使用的字節序通常稱之爲網絡字節序。

 

二、高/低地址與高低字節
首先我們要知道我們C程序映像中內存的空間佈局情況:在《C專家編程》中或者《Unix環境高級編程》中有關於內存空間佈局情況的說明,大致如下圖:

----------------------- 最高內存地址 0xffffffff

。。。。。。

 | 棧底

 .

 .                       棧

  棧頂

-----------------------

 |

 |

NULL (空洞)

/|/

-----------------------

                          堆

-----------------------

未初始化的數據

----------------------         (統稱數據段)

初始化的數據

-----------------------

正文段(代碼段)

----------------------- 最低內存地址 0x00000000

以上圖爲例如果我們在棧上分配一個unsigned char buf[4],那麼這個數組變量在棧上是如何佈局的呢?看下圖:

棧底 (高地址)

----------

buf[3]

buf[2]

buf[1]

buf[0]

----------

棧頂 (低地址)

 

現在我們弄清了高低地址,接着我來弄清高/低字節,如果我們有一個32位無符號整型0x12345678(呵呵,恰好是把上面的那4個字節buf看成一個整型),那麼高位是什麼,低位又是什麼呢?其實很簡單。在十進制中我們都說靠左邊的是高位,靠右邊的是低位,在其他進制也是如此。就拿0x12345678來說,從高位到低位的字節依次是0x12、0x34、0x56和0x78。

高低地址和高低字節都弄清了。我們再來回顧一下Big-Endian和Little-Endian的定義,並用圖示說明兩種字節序:

以unsigned int value = 0x12345678爲例,分別看看在兩種字節序下其存儲情況,我們可以用unsigned char buf[4]來表示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) -- 低位

---------------

棧頂 (低地址)

 

在現有的平臺上Intel的X86採用的是Little-Endian,而像Sun的SPARC採用的就是Big-Endian。

 

三、網絡字節序的轉換
假設對於little endian的IA-32架構上面的Linux,首先考慮網絡字節轉換的結果與原來有什麼不同,如 int a = 0x12345678,b = htonl(a),那麼就應該是0x78563412。如果是 short c = 0x1234,short d = 0x5678,e = htons(c),f = htons(d),這樣e=0x3412,f=0x7856,如果能把e和f調換一下組合放在一起,不就是一個整型a(a=0x12345678)轉換之後的值麼?實驗的代碼如下:

#include <stdio.h>

struct ST{

    short val1;

    short val2;

};

union U{

    int val;

    struct ST st;

};

 

int main(void)

{

    int a = 0;

    union U u1, u2;

 

    a = 0x12345678;

    u1.val = a;

    printf("u1.val is 0x%x/n", u1.val);

    printf("val1 is 0x%x/n", u1.st.val1);

    printf("val2 is 0x%x/n", u1.st.val2);

    printf("after first convert is: 0x%x/n", htonl(u1.val));

    u2.st.val2 = htons(u1.st.val1);

    u2.st.val1 = htons(u1.st.val2);

    printf("after second convert is: 0x%x/n", u2.val);

    return 0;

}

輸出結果:

u1.val is 0x12345678

val1 is 0x5678

val2 is 0x1234

after first convert is: 0x78563412

after second convert is: 0x78563412

 

按照這種想法我們實現long long int(64bit)類型,把它分割成兩個int(32bit),然後分別使用htonl(),分別轉換,然後再將兩種int交換順序重新組合,即實現了整個64位的八個字節的翻轉。

代碼如下:

#include <stdio.h>

struct ST{

    int val1;

    int val2;

};

union test {

    long long int val;

    struct ST st;

};

 

int main(void)

{

    long long int a;

    union test u1, u2;

 

    a = 0x7654321087654321LL;

    u1.val = a;

    u2.st.val2 = htonl(u1.st.val1);

    u2.st.val1 = htonl(u1.st.val2);

    printf("val1 is 0x%x/n", u2.st.val1);

    printf("val2 is 0x%x/n", u2.st.val2);

    printf("u1.val     is    : 0x%llx/n", u1.val);

    printf("after convert is : 0x%llx/n", u2.val);

   

    return 0;

}

執行結果:

val1 is 0x10325476

val2 is 0x21436587

u1.val     is    : 0x7654321087654321

after convert is  : 0x2143658710325476

 

另外注意long long int 最大值是0x7fffffffffffffff,即7後面15個f(2的63次方減1) unsigned long long int 最大值是0xffffffffffffffff,16個f(2的64次方減1)。程序中long long int 可以簡寫爲 long long,但是記住這是簡寫,就像long是long int的簡寫。

 

想看數據在內存中如何存儲的,就用gdb吧!使用gdb中 x命令,如 x /xb &a表示要察看存儲在變量a中的前一個字節(byte)中的數據(16進制)。x /xw &a 就是要察看變量a中前4個字節(word)數據(16進制)。x /xg &a 察看a開始8個字節的數據。

 

網絡字節轉換行數如果是win32則定義在winsock2.h內,否則,定義在netdb.h sys/socket.h netinet/in.h等3個頭文件中

#ifdef __WIN32__

#include <winsock2.h>

#else

#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>

#endif

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章