1. 程式人生 > >你知道位元組序嗎

你知道位元組序嗎

位元組序

最近在調一個自定義報文的介面時,本來以為挺簡單的,發現踩了好幾個坑,其中一個比較“刻骨銘心”的問題就是資料的位元組序問題。

背景

自定義報文,呼叫介面,服務端報文解析失敗 程式碼截圖

iOS 小端序

檢視 iOS 裝置使用的端序

 if (NSHostByteOrder() == NS_LittleEndian) {
    NSLog(@"NS_LittleEndian");
} if (NSHostByteOrder() == NS_BigEndian) {
    NSLog(@"NS_BigEndian");
} else {
    NSLog(@"Unknown");
}

概念

位元組序,位元組順序,又稱端序或尾序(Endianness),在電腦科學領域中,指「儲存器」中或者「數字通訊鏈路」中,組成多位元組的字的位元組排列順序

在幾乎所有的機器上,多位元組物件都被儲存為連續的位元組序列。例如在 C 語言中,一個 int 型別的變數 x 地址為 0x100,那麼其對應的地址表示式 &x 的值為 0x100,且 x 的4個位元組將被儲存在儲存器的 0x100,0x101,0x102,0x103 位置。

位元組的排列方式有2個通用規則。例如一個多位整數,按照儲存地址從低到高排序的位元組中,如果該整數的最低有效位元組(類似於最低有效位)排在最高有效位元組前面,則成為**“小端序“;反之成為

”大端序“**。在計算機網路中,位元組序是一個必須要考慮的因素,因為不同型別的機器可能採用不同標準的位元組序,所以均需要按照網路標準進行轉化。

假設一個型別為 int 的變數 x,位於地址 0x100 處,它的值為 0x01234567,地址範圍為 0x100~0x103位元組,其內部的排列順序由機器決定,也就是和 CPU 有關,和作業系統無關。

  • 大端序(Big Endian):也叫大尾序。高位元組儲存在記憶體的低地址 | 地址增長方向 |記憶體地址序號|16進位制| |:- |:-|:-| |↓ |0x100| 01| |↓ |0x101| 23| |↓ |0x102| 45| |↓|0x103| 67|

  • 小端序(Little Endian):也叫小尾序。低位元組儲存在記憶體的低地址 | 地址增長方向 |記憶體地址序號|16進位制| |:- |:-|:-| |↓ |0x100| 67| |↓ |0x101| 45| |↓ |0x102| 23| |↓|0x103| 01|

緣起

“endian”一詞來源於十八世紀愛爾蘭作家喬納森·斯威夫特(Jonathan Swift)的小說《格列佛遊記》(Gulliver's Travels)。小說中,小人國為水煮蛋該從大的一端(Big-End)剝開還是小的一端(Little-End)剝開而爭論,爭論的雙方分別被稱為“大端派”和“小端派”。以下是1726年關於大小端之爭歷史的描述:

“我下面要告訴你的是,Lilliput和Blefuscu這兩大強國在過去36個月裡一直在苦戰。戰爭開始是由於以下的原因:我們大家都認為,吃雞蛋前,原始的方法是打破雞蛋較大的一端,可是當今皇帝的祖父小時候吃雞蛋,一次按古法打雞蛋時碰巧將一個手指弄破了。因此他的父親,當時的皇帝,就下了一道敕令,命令全體臣民吃雞蛋時打破雞蛋較小的一端,違令者重罰。老百姓們對這項命令極其反感。歷史告訴我們,由此曾經發生過6次叛亂,其中一個皇帝送了命,另一個丟了王位。這些叛亂大多都是由Blefuscu的國王大臣們煽動起來的。叛亂平息後,流亡的人總是逃到那個帝國去尋求避難。據估計,先後幾次有11000人情願受死也不肯去打破雞蛋較小的一端。關於這一爭端,曾出版過幾百本大部著作,不過大端派的書一直是受禁的,法律也規定該派任何人不得做官。” —— 《格列夫遊記》 第一卷第4章 蔣劍鋒(譯)

1980年,丹尼·科恩(Danny Cohen),一位網路協議的早期開發者,在其著名的論文"On Holy Wars and a Plea for Peace"中,為平息一場關於位元組該以什麼樣的順序傳送的爭論,而第一次引用了該詞。

位元組順序

對於單一的位元組(a byte),大部分處理器以相同的順序處理位元(bit),因此單位元組的存放方法和傳輸方式一般相同。 對於多位元組資料,如整數(32位機中一般佔4位元組),在不同的處理器的存放方式主要有兩種:大、小端序。

拓展

以記憶體中0x0A0B0C0D的存放方式為例,分別有以下幾種方式: 注:0x 字首代表十六進位制。

  1. 大端序

資料以8bit為單位:

地址增長方向 → ... 0x0A 0x0B 0x0C 0x0D ... 示例中,最高位位元組是0x0A 儲存在最低的記憶體地址處。下一個位元組0x0B存在後面的地址處。正類似於十六進位制位元組從左到右的閱讀順序。

資料以16bit為單位:

地址增長方向 → ... 0x0A0B 0x0C0D ... 最高的16bit單元0x0A0B儲存在低位。

  1. 小端序

資料以8bit為單位:

地址增長方向 → ... 0x0D 0x0C 0x0B 0x0A ... 最低位位元組是0x0D 儲存在最低的記憶體地址處。後面位元組依次存在後面的地址處。

資料以16bit為單位:

地址增長方向 → ... 0x0C0D 0x0A0B ... 最低的16bit單元0x0C0D儲存在低位。

更改地址的增長方向: 當更改地址的增長方向,使之由右至左時,表格更具有可閱讀性。

← 地址增長方向 ... 0x0A 0x0B 0x0C 0x0D ...

最低有效位(LSB)是0x0D 儲存在最低的記憶體地址處。後面位元組依次存在後面的地址處。

← 地址增長方向 ... 0x0A0B 0x0C0D ...

最低的16bit單元0x0C0D儲存在低位。

  1. 混合序(英:middle-endian)具有更復雜的順序。以 PDP-11 為例,0x0A0B0C0D 被儲存為: 32bit在PDP-11的儲存方式

地址增長方向 → ... 0x0B 0x0A 0x0D 0x0C ...

可以看作高16bit和低16bit以大端序儲存,但16bit內部以小端儲存。

處理器體系

  • x86、MOS Technology 6502、Z80、VAX、PDP-11等處理器為小端序;
  • Motorola 6800、Motorola 68000、PowerPC 970、System/370、SPARC(除V9外)等處理器為大端序;
  • ARM、PowerPC(除PowerPC 970外)、DEC Alpha、SPARC V9、MIPS、PA-RISC及IA64的位元組序是可配置的。

網路位元組順序(NBO)

通常我們認為,在網路傳輸的位元組順序即為網路位元組序為標準順序,考慮到與協議的一致以及與其他平臺產品的互通,在程式傳送資料包的時候,將主機位元組序轉換為網路位元組序,收資料包處將網路位元組序轉換為主機位元組序。

NBO(Network Byte Order):按照從高到低的順序儲存,在網路上使用統一的網路位元組順序,可以避免相容性問題。TCP/IP中規定好的一種資料表示格式,與具體的 CPU 型別、作業系統等無關。從而保證資料在不同主機之間傳輸時能夠被正確解釋。網路位元組序採用大端序。

主機位元組順序 HBO

主機位元組順序(HBO:Host Byte Order):不同機器 HBO 不相同,與 CPU 有關。計算機儲存資料有兩種位元組優先順序:Big Endian 和 Little Endian。Internet 以 Big Endian 順序在網路上傳輸,所以對於在內部是以 Little Endian 方式儲存資料的機器,在網路通訊時就需要進行轉換。

如何轉換

由於 Internet 和 Intel 處理器的位元組順序不同,所以開發者需要使用 Sockets API 提供的標準轉換函式。

BSD Socket 提供了轉換函式

  • htons() : unsigned short 從主機序轉換到網路序
  • htonl(): unsigned long 從主機序轉換到網路序
  • ntohs():unsigned short 從網路序轉換到主機序
  • ntohl():unsigned long 從網路序轉換到主機序

參考資料