網絡通信基礎重難點解析 15 :主機字節序和網絡字節序

主機字節序和網絡字節序

主機字節序

網絡通信本質上是不同的機器進行數據交換,一般不同的機器有着不同的 CPU 型號,不同的 CPU 其字節序可能不一樣。所謂字節序指的是對於存儲需要多個字節(大於 1 字節)的整數來說,其每個字節在不同的機器內存中存儲的順序。這就是所謂的主機字節序,一般分爲兩類:

  • little-endian (LE,俗稱小端編碼小頭編碼)

    對於一個整數值,如果使用小端字節序,整數的位存儲在內存地址的位置,整數的位存儲在內存地址的位置上(所謂的高高低低),這種序列比較符合人的思維習慣。Intel x86 系列的系統使用的是小端編碼方式。

  • big-endian(BE,俗稱大端編碼大頭編碼

    對於一個整數值,如果使用大端字節序,整數的位存儲在內存地址的位置,整數的位存儲在內存地址的位置上(所謂的高低低高),這是最直觀的字節序。Java 程序、Mac 機器上的程序一般是大端編碼方式。

舉個例子,對於內存中雙字值 0x10203040 (4 字節)的存儲方式,如果使用小端編碼,其內存中存儲方式如下:

在這裏插入圖片描述

如果使用大端編碼 來存儲 0x10203040,在內存中存儲示意圖如下:

在這裏插入圖片描述

關於大端小端一詞來源於《格列佛遊記》中的小人國故事,小人國中的兩個國家因爲吃雞蛋應該先從雞蛋的大端還是小端先磕破這一“宗教信仰”問題水火難容,頻繁發送戰爭。

網絡字節序

網絡字節序是 TCP/IP 協議中規定好的一種數據表示格式,它與具體的 CPU 類型、操作系統等無關,從而可以保證數據在不同主機之間傳輸時能夠被正確解釋,網絡字節順序採用 big-endian 排序方式。因此爲了不同的機器和系統可以正常交換數據,一般建議將需要傳輸的整型值轉換成網絡字節序,我們前面代碼中使用端口時即將端口號數值從本地字節序轉換成網絡字節序:

//2.初始化服務器地址
struct sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//將端口號3000轉換成網絡字節序
bindaddr.sin_port = htons(3000);
if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1)
{
    std::cout << "bind listen socket error." << std::endl;
    return -1;
}

htons 函數即將一個 short 類型從本機字節序轉換成網絡字節序,你可以這麼記憶這個函數:host to net short => htons,與這個函數類似的還有一系列將整型轉網絡字節序的函數(以 Linux 系統爲例):

#include <arpa/inet.h>

//host to net long
uint32_t htonl(uint32_t hostlong);
//host to net short
uint16_t htons(uint16_t hostshort);

與此相反,當從網絡上收到數據以後,如果需要將整數從網絡字節序轉換成本地字節序,也有對應的系列函數:

#include <arpa/inet.h>

//net to host long
uint32_t ntohl(uint32_t netlong);
//net to host short
uint16_t ntohs(uint16_t netshort);

這類轉換函數的實現原理也很簡單,以本地字節序轉網絡字節序爲例,如果發現本機字節序就是網絡字節序(即本機字節序就是大端編碼)則什麼也不做,反之將字節順序互換。

那如何判斷本機字節序是不是網絡字節序呢?可以隨意找一個 2 字節的十六進制數值測試一下,例如 0x1234,如果本機字節序是小頭編碼,這值 12 存儲在高地址字節中,34 存儲在低地址字節中,這樣當強行把 0x1234 轉換成 1 字節的 char 時,高字節被丟棄,剩下低字節值,就是 34;反之,如果本機字節序是大端編碼,則高地址字節中存儲的是 34,低地址字節中存儲的是 12,當強轉成一個字節的 char 時,其值是 12,代碼實現如下:

//判斷本機是否是網絡字節序
bool isNetByteOrder()
{
    unsigned short mode = 0x1234;
    char* pmode = (char*)&mode;
    //低字節放低位  小端字節
    if (*pmode == 0x34)
        return false;

    return true;
}

在上面的基礎上,我們來實現一下 htons 函數:

uint16_t htons(uint16_t hostshort);
{
    //如果已經本機字節序是網絡字節序,則直接返回
    if (isNetByteOrder())
        return hostshort;

    return ((uint16_t)(hostshort >> 8)) | ((uint16_t)((hostshort & 0x00ff) << 8));
}

其他的函數實現原理類似,讀者可以自己實現一下,這裏不再重複介紹。


本文首發於『easyserverdev』公衆號,歡迎關注,轉載請保留版權信息。

歡迎加入高性能服務器開發 QQ 羣一起交流: 578019391
微信掃碼關注

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