最近在調一個自定義報文的接口時,原本覺得挺簡單的,發現踩了好幾個坑,其中一個比較「刻骨銘心」的問題就是數據的字節序問題。objective-c
自定義報文,調用接口,服務端報文解析失敗 網絡
查看 iOS 設備使用的端序app
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個通用規則。例如一個多位整數,按照存儲地址從低到高排序的字節中,若是該整數的最低有效字節(相似於最低有效位)排在最高有效字節前面,則成爲**「小端序「;反之成爲」大端序「**。在計算機網絡中,字節序是一個必需要考慮的因素,由於不一樣類型的機器可能採用不一樣標準的字節序,因此均須要按照網絡標準進行轉化。spa
假設一個類型爲 int 的變量 x,位於地址 0x100 處,它的值爲 0x01234567,地址範圍爲 0x100~0x103字節,其內部的排列順序由機器決定,也就是和 CPU 有關,和操做系統無關。操作系統
大端序(Big Endian):也叫大尾序。高字節存儲在內存的低地址計算機網絡
地址增加方向 | 內存地址序號 | 16進制 |
---|---|---|
↓ | 0x100 | 01 |
↓ | 0x101 | 23 |
↓ | 0x102 | 45 |
↓ | 0x103 | 67 |
小端序(Little Endian):也叫小尾序。低字節存儲在內存的低地址code
地址增加方向 | 內存地址序號 | 16進制 |
---|---|---|
↓ | 0x100 | 67 |
↓ | 0x101 | 45 |
↓ | 0x102 | 23 |
↓ | 0x103 | 01 |
「endian」一詞來源於十八世紀愛爾蘭做家喬納森·斯威夫特(Jonathan Swift)的小說《格列佛遊記》(Gulliver's Travels)。小說中,小人國爲水煮蛋該從大的一端(Big-End)剝開仍是小的一端(Little-End)剝開而爭論,爭論的雙方分別被稱爲「大端派」和「小端派」。如下是1726年關於大小端之爭歷史的描述:orm
「我下面要告訴你的是,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 前綴表明十六進制。
數據以8bit爲單位:
地址增加方向 → ... 0x0A 0x0B 0x0C 0x0D ... 示例中,最高位字節是0x0A 存儲在最低的內存地址處。下一個字節0x0B存在後面的地址處。正相似於十六進制字節從左到右的閱讀順序。
數據以16bit爲單位:
地址增加方向 → ... 0x0A0B 0x0C0D ... 最高的16bit單元0x0A0B存儲在低位。
數據以8bit爲單位:
地址增加方向 → ... 0x0D 0x0C 0x0B 0x0A ... 最低位字節是0x0D 存儲在最低的內存地址處。後面字節依次存在後面的地址處。
數據以16bit爲單位:
地址增加方向 → ... 0x0C0D 0x0A0B ... 最低的16bit單元0x0C0D存儲在低位。
更改地址的增加方向: 當更改地址的增加方向,使之由右至左時,表格更具備可閱讀性。
← 地址增加方向 ... 0x0A 0x0B 0x0C 0x0D ...
最低有效位(LSB)是0x0D 存儲在最低的內存地址處。後面字節依次存在後面的地址處。
← 地址增加方向 ... 0x0A0B 0x0C0D ...
最低的16bit單元0x0C0D存儲在低位。
地址增加方向 → ... 0x0B 0x0A 0x0D 0x0C ...
能夠看做高16bit和低16bit以大端序存儲,但16bit內部以小端存儲。
一般咱們認爲,在網絡傳輸的字節順序即爲網絡字節序爲標準順序,考慮到與協議的一致以及與其餘平臺產品的互通,在程序發送數據包的時候,將主機字節序轉換爲網絡字節序,收數據包處將網絡字節序轉換爲主機字節序。
NBO(Network Byte Order):按照從高到低的順序存儲,在網絡上使用統一的網絡字節順序,能夠避免兼容性問題。TCP/IP中規定好的一種數據表示格式,與具體的 CPU 類型、操做系統等無關。從而保證數據在不一樣主機之間傳輸時可以被正確解釋。網絡字節序採用大端序。
主機字節順序(HBO:Host Byte Order):不一樣機器 HBO 不相同,與 CPU 有關。計算機存儲數據有兩種字節優先順序:Big Endian 和 Little Endian。Internet 以 Big Endian 順序在網絡上傳輸,因此對於在內部是以 Little Endian 方式存儲數據的機器,在網絡通訊時就須要進行轉換。
因爲 Internet 和 Intel 處理器的字節順序不一樣,因此開發者須要使用 Sockets API 提供的標準轉換函數。
BSD Socket 提供了轉換函數
以前的代碼採用端序轉換
- (NSData *)handlePayloadData:(NSArray *)rawArray
{
if (rawArray.count == 0) {
return 0;
}
// 2. 加密壓縮處理:(meta 總體先加密再壓縮,payload一條條先加密再壓縮)
__block NSMutableString *metaStrings = [NSMutableString string];
__block NSMutableArray<NSData *> *payloads = [NSMutableArray array];
// 2.1. 遍歷拼接model,取出 meta,用 \n 拼接
[rawArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (PCT_IS_CLASS(obj, PCTLogPayloadModel)) {
PCTLogPayloadModel *payloadModel = (PCTLogPayloadModel *)obj;
BOOL shouldAppendLineBreakSymbol = idx < (rawArray.count - 1);
[metaStrings appendString:[NSString stringWithFormat:@"%@%@", payloadModel.meta, shouldAppendLineBreakSymbol ? @"\n" : @""]];
// 2.2 判斷是否須要上傳 payload 信息。若是須要則將 payload 取出。(Payload 可能爲空)
if ([self needUploadPayload:payloadModel]) {
if (payloadModel.payload) {
NSData *payloadData = [PCTDataSerializer compressAndEncryptWithData:payloadModel.payload];
[payloads addObject:payloadData];
}
}
}
}];
if (metaStrings.length == 0) {
return nil;
}
NSData *metaData = [PCTDataSerializer compressAndEncryptWithString:metaStrings];
__block NSMutableData *headerData = [NSMutableData data];
unsigned short metaLength = (unsigned short)metaData.length;
HTONS(metaLength); // 處理2字節的大端序
[headerData appendData:[NSData dataWithBytes:&metaLength length:sizeof(metaLength)]];
Byte payloadCountbytes[] = {payloads.count};
NSData *payloadCountData = [[NSData alloc] initWithBytes:payloadCountbytes length:sizeof(payloadCountbytes)];
[headerData appendData:payloadCountData];
[payloads enumerateObjectsUsingBlock:^(NSData * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
unsigned int payloadLength = (unsigned int)obj.length;
HTONL(payloadLength); // 處理4字節的大端序
[headerData appendData:[NSData dataWithBytes:&payloadLength length:sizeof(payloadLength)]];
}];
__block NSMutableData *uploadData = [NSMutableData data];
// 先添加 header 基礎信息,不須要加密壓縮
[uploadData appendData:[headerData copy]];
// 再添加 meta 信息,meta 信息須要先壓縮再加密
[uploadData appendData:metaData];
// 再添加 payload 信息
[payloads enumerateObjectsUsingBlock:^(NSData * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[uploadData appendData:obj];
}];
return [uploadData copy];
}
複製代碼