這個文檔會介紹protocol buffer的二進制有線格式(binary wire format)。你並非須要理解這些後才能在應用裏使用protocol buffer,可是當你想知道不一樣的protocol buffer格式是如何影響編碼後的消息體的體積時,這些知識會很是有用。bash
假設有一個很是簡單的消息定義:post
message Test1 {
optional int32 a = 1;
}
複製代碼
在應用中,你建立了一個Test1
消息並把a
設置爲150。而後你把消息序列化到輸出流中,若是你能查看編碼後的消息,你會看到三個字節:ui
08 96 01
複製代碼
到目前爲止,如此小並且都是數字-可是這是什麼意思呢?繼續往下看編碼
要理解上面protocol buffer編碼的數據,你須要先理解vaints
,Varints
是一種使用一個或多個字節編碼整數的方法。較小的數字使用較少的字節。spa
除了最後一個字節外,varint編碼中的每一個字節都設置了最高有效位(most significant bit - msb)–msb爲1則代表後面的字節仍是屬於當前數據的,若是是0那麼這是當前數據的最後一個字節數據。每一個字節的低7位用於以7位爲一組存儲數字的二進制補碼錶示,最低有效組在前,或者叫最低有效字節在前。這代表varint編碼後數據的字節是按照小端序排列的。code
舉例來講,對於數字1-它佔用單個字節,因此字節的最高位上是0orm
0000 0001
複製代碼
對於數字300會有一點複雜,它佔用倆個字節對象
1010 1100 0000 0010
複製代碼
那麼是怎麼計算出來是300的呢?首先你須要把每一個字節的msb去掉,由於它只用來告訴咱們是否已經到達數字的最後一個字節(本例的varint佔用倆個字節因此第一個字節的msb爲1)element
1010 1100 0000 0010
→ 010 1100 000 0010
複製代碼
將兩組7位反轉,由於你記得,varint存儲的數字最低有效組在前。而後,將它們鏈接起來以得到最終值文檔
000 0010 010 1100 (去掉最高有效位,並反轉7位組)
→ 000 0010 ++ 010 1100
→ 100101100
→ 256 + 32 + 8 + 4 = 300
複製代碼
注:varint編碼理解起來有點難,能夠看以前寫的varint編碼原理解析。
如你所知,一個protocol buffer是一系列鍵值對。消息的二進制格式只使用消息字段的字段編號做爲鍵--字段名和聲明的類型只能在解析端經過引用參考消息類型定義(即.proto
文件)才能肯定。
當一個消息被編碼時,鍵和值會被鏈接放入字節流中。當消息被解碼時,分析器須要可以跳過未識別的字段。這樣,新加入消息的字段就不會破壞不知道他們存在的那些老程序。爲此,有線格式消息中每一個對的「鍵」其實是兩個值-.proto文件中的字段編號,加上一種有線類型,該類型僅提供足夠的信息來查找隨後的值的長度。在大多數語言實現中,這個鍵稱爲標籤。
可用的有線類型以下:
Type | Meaning | Used For |
---|---|---|
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit | fixed64, sfixed64, double |
2 | Length-delimited | string, bytes, embedded messages, packed repeated fields |
3 | Start group | groups (deprecated) |
4 | End group | groups (deprecated) |
5 | 32-bit | fixed32, sfixed32, float |
在消息流中的每一個鍵都是varint,使用(filed_number << 3) | wire_type
得到--也就是說字節的後三位存儲的是有線類型。
如今讓咱們再回到上面的消息示例。你如今知道字節流中的首個字節永遠都是一個varint鍵,在咱們的例子中它是08或者下面的二進制(去掉了msb)。
000 1000
複製代碼
經過後三位得出有線類型(0),而後右移三位獲得字段編號(1)。如今你知道字段的編號是1對應的值是一個varint。使用前面學到的解碼varint的知識,你能夠看到下面的兩個字節存儲着值150。
96 01 = 1001 0110 0000 0001
→ 000 0001 ++ 001 0110 (去掉最高有效位,並反轉7位組)
→ 10010110
→ 128 + 16 + 4 + 2 = 150
複製代碼
就像你在上一部分看到的那樣,protocol buffer中全部與有線類型0關聯的類型都會被編碼爲varint。可是,在編碼負數時,帶符號的int類型(sint32和sint64)與「標準」 int類型(int32和int64)之間存在着巨大區別。若是將int32或int64用做負數的類型,則結果varint老是十個字節長––實際上,它被視爲一個很是大的無符號整數。若是使用帶符號類型(sint32和sint64)之一,則生成的varint使用ZigZag編碼,效率更高。
ZigZag編碼將有符號數映射到無符號數以便具備較小絕對值的數字(好比-1)也具備較小的varint編碼值。這樣作的方式是經過正整數和負整數來回「曲折」,將-1編碼爲1,將1編碼爲2,將-2編碼爲3,依此類推,能夠在下表中看到:
Signed Original | Encoded As |
---|---|
0 | 0 |
-1 | 1 |
1 | 2 |
-2 | 3 |
2147483647 | 4294967294 |
-2147483648 | 4294967295 |
對與非可varint編碼的數字來講比較簡單--double
和fixed64
使用有線類型1,這會告訴解析器指望固定的64-bit的數據塊。類似地float
和fixed32
使用有線類型5,這會告訴解析器指望固定的32-bit數據塊。這兩種狀況都是使用小端序排列字節存儲數據的。
有線類型2(長度分隔)表示該值是varint編碼的長度值,後跟長度值指定數量的數據字節。
message Test2 {
optional string b = 2;
}
複製代碼
設置b的值爲"testing"後消息對應的二進制有線格式爲
12 07 74 65 73 74 69 6e 67
紅色的字節是UTF-8編碼後的"testing"
這裏的鍵是0x12→0001 0010
→字段號= 2,類型=2(第一個字節的後三位表示有線類型的編號,而後右移三位變成000 0010
獲得字段號)。值中的varint表示的數據字節長度是7,如你所見咱們在它後面找到的七個字節–就是解析器要找的字符串。
下面是一個擁有內嵌消息的消息定義Test3
,內嵌的消息類型是咱們上面示例中定義的Test1
message Test3 {
optional Test1 c = 3;
}
複製代碼
下面則是內嵌的Test1中的
a設置爲150,
Test3`被編碼後的版本
1a 03 08 96 01
如你所見,最後三個字節和咱們第一個例子編碼後的結果同樣(08 96 01
),在他們以前是數字3,--內嵌消息會像字符串同樣被對對待(有線格式=2)。
若是proto2消息定義具備重複的元素(不帶[packed = true]選項),則編碼消息具備零個或多個具備相同字段編號的鍵值對。這些重複的值沒必要連續出現。它們可能與其餘字段交錯。解析時,元素之間的順序會保留下來,儘管其餘字段的順序會丟失。在proto3中,重複字段使用packed編碼,能夠在下面看到相關編碼。
一般,編碼消息永遠不會有一個以上非重複字段的實例。可是,解析器能處理這種實際狀況,對於數字類型和字符串,若是同一字段屢次出現,則解析器將接受它看到的最後一個值。對於嵌入式消息字段,解析器將合併同一字段的多個實例,就像使用Message :: MergeFrom
方法同樣-也就是說,後一個實例中的全部單個標量字段將替換前一個實例中的單個標量字段,可重複字段會被串聯到一塊。這些規則的做用是,解析兩個編碼的消息的鏈接所產生的結果與您分別解析兩個消息併合並結果對象的結果徹底相同。也就是說:
MyMessage message;
message.ParseFromString(str1 + str2);
複製代碼
等同於
MyMessage message, message2;
message.ParseFromString(str1);
message2.ParseFromString(str2);
message.MergeFrom(message2);
複製代碼
這個特性有時頗有用,由於即便您不知道它們的類型,也容許你合併兩個消息。
proto版本2.1.0引入了壓縮重複字段,在proto2中聲明爲重複字段,並使用特殊的[packed = true]選項。在proto3中,默認狀況下壓縮標量數字類型的重複字段。這些功能相似於重複的字段,但編碼方式不一樣。包含零元素的壓縮重複字段不會出如今編碼的消息中。不然,該字段的全部元素都將打包爲有線類型爲2(定界)的單個鍵值對。每一個元素的編碼方式與一般相同,不一樣之處在於元素以前沒有鍵。
舉例來講,你有如下消息類型:
message Test4 {
repeated int32 d = 4 [packed=true];
}
複製代碼
如今假設您構造一個Test4,爲重複的字段d提供值三、270和86942。而後,消息編碼後的形式爲:
22 // key (field number 4, wire type 2)
06 // payload size (6 bytes)
03 // first element (varint 3)
8E 02 // second element (varint 270)
9E A7 05 // third element (varint 86942)
複製代碼
只能將原始數字類型(使用varint,32位或64位線型的類型)的重複字段聲明爲「packed」。
字段編號能夠在.proto文件中以任何順序使用。選擇使用的順序對消息的序列化方式沒有影響。
序列化消息時,對於如何寫入其已知字段或未知字段沒有保證的順序。序列化順序是一個實現細節,未來任何特定實現的細節均可能更改。所以,protocol buffer解析器必須可以以任何順序解析字段。