Protobuf 有如 XML,不過它更小、更快、也更簡單。它以高效的二進制方式存儲,比 XML 小 3 到 10 倍,快 20 到 100 倍。你能夠定義本身的數據結構,而後使用代碼生成器生成的代碼來讀寫這個數據結構。你甚至能夠在無需從新部署程序的狀況下更新數據結構。只需使用 Protobuf 對數據結構進行一次描述,便可利用各類不一樣語言或從各類不一樣數據流中對你的結構化數據輕鬆讀寫。
有兩項技術保證了採用 Protobuf 的程序能得到相對於 XML 極大的性能提升。
第一點,咱們能夠考察 Protobuf 序列化後的信息內容。您能夠看到 Protocol Buffer 信息的表示很是緊湊,這意味着消息的體積減小,天然須要更少的資源。好比網絡上傳輸的字節數更少,須要的 IO 更少等,從而提升性能。
第二點,咱們須要理解 Protobuf 封解包的大體過程,從而理解爲何會比 XML 快不少。詳細看如下連接 http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/linux
另外,它有一個很是棒的特性,即「向後」兼容性好,人們沒必要破壞已部署的、依靠「老」數據格式的程序就能夠對數據結構進行升級。這樣您的程序就能夠沒必要擔憂由於消息結構的改變而形成的大規模的代碼重構或者遷移的問題。由於添加新的消息中的 field 並不會引發已經發布的程序的任何改變。
Protobuf 語義更清晰,無需相似 XML 解析器的東西(由於 Protobuf 編譯器會將 .proto 文件編譯生成對應的數據訪問類以對 Protobuf 數據進行序列化、反序列化操做)。 使用 Protobuf 無需學習複雜的文檔對象模型,Protobuf 的編程模式比較友好,簡單易學,同時它擁有良好的文檔和示例,對於喜歡簡單事物的人們而言,Protobuf 比其餘的技術更加有吸引力。
編程
消息由至少一個字段組合而成,相似於C語言中的結構。每一個字段都有必定的格式。
字段格式:限定修飾符① | 數據類型② | 字段名稱③ | = | 字段編碼值④ | 字段默認值⑤
1)限定修飾符包含 required\optional\repeated
Required: 表示是一個必須字段,必須相對於發送方,在發送消息以前必須設置該字段的值,對於接收方,必須可以識別該字段的意思。發送以前沒有設置required字段或者沒法識別required字段都會引起編解碼異常,致使消息被丟棄。至於爲何感興趣的本身能夠到protobuf官網深刻研究其編解碼原理。http://code.google.com/p/protobuf/
Optional:表示是一個可選字段,可選對於發送方,在發送消息時,能夠有選擇性的設置或者不設置該字段的值。對於接收方,若是可以識別可選字段就進行相應的處理,若是沒法識別,則忽略該字段,消息中的其它字段正常處理。---由於optional字段的特性,不少接口在升級版本中都把後來添加的字段都統一的設置爲optional字段,這樣老的版本無需升級程序也能夠正常的與新的軟件進行通訊,只不過新的字段沒法識別而已,由於並非每一個節點都須要新的功能,所以能夠作到按需升級和平滑過渡。
Repeated:表示該字段能夠包含0,N個元素。其特性和optional同樣,可是每一次能夠包含多個值。能夠看做是在傳遞一個數組的值。
2)數據類型
Protobuf定義了一套基本數據類型。幾乎均可以映射到C\C++\Java等語言的基礎數據類型.
數組
另外,有一點特地強調一下:
關於 fixed32 和int32的區別。fixed32的打包效率比int32的效率高,可是使用的空間通常比int32多。所以一個屬於時間效率高,一個屬於空間效率高。根據項目的實際狀況,通常選擇fixed32,若是遇到對傳輸數據量要求比較苛刻的環境,能夠選擇int32.
3)字段編碼值
有了該值,通訊雙方纔能互相識別對方的字段。固然相同的編碼值,其限定修飾符和數據類型必須相同.
編碼值的取值範圍爲 1~2^32(4294967296)。其中 1~15的編碼時間和空間效率都是最高的,編碼值越大,其編碼的時間和空間效率就越低(相對於1-15),固然通常狀況下相鄰的2個值編碼效率的是相同的,除非2個值剛好實在4字節,12字節,20字節等的臨界區。好比15和16.
有一點須要強調,消息中的字段的編碼值無需連續,只要是合法的,而且不能在同一個消息中有字段包含相同的編碼值。
網絡
1) ProtoBuf編碼基礎——Varints, varints是一種將一個整數序列化爲一個或者多個Bytes的方法,越小的整數,使用的Bytes越少。
Varints的基本規則是:
(a) 每一個Byte的最高位(msb)是標誌位,若是該位爲1,表示該Byte後面還有其它Byte,若是該位爲0,表示該Byte是最後一個Byte。
(b)每一個Byte的低7位是用來存數值的位
(c)Varints方法用Litte-Endian(小端)字節序
2)ProtoBuf中消息的編碼規則:
(a)每條消息(message)都是有一系列的key-value對組成的, key和value分別採用不一樣的編碼方式。
(b)對某一條件消息(message)進行編碼的時候,是把該消息中全部的key-value對序列化成二進制字節流;而解碼的時候,解碼程序讀入二進制的字節流,解析出每個key-value對,若是解碼過程當中遇到識別不出來的類型,直接跳過。這樣的機制,保證了即便該消息添加了新的字段,也不會影響舊的編/解碼程序正常工做。
(c)key由兩部分組成,一部分是字段編碼值(field_num),另外一部分是字段類型(wire_type)。key = field_num << 3 | wire_type數據結構
類型 | 含義 | 用於 |
---|---|---|
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 |
(d)varint類型(wire_type=0)的編碼,與第(1)部分中介紹的方法基本一致,可是int32, int64和sint32,sint64有些特別之處:int32和int64就是簡單的按varints方法來編碼,因此像-一、-2這樣負數也會佔比較多的Bytes。因而sint32和sint64採用了一種改進的方法:先採用Zigzag方法將全部的整數(正數、0和負數)一一映射到全部的無符號數上,然 後再採用varints編碼方法進行編碼。Zigzag映射函數爲:
Zigzag(n) = (n << 1) ^ (n >> 31), n爲sint32時
Zigzag(n) = (n << 1) ^ (n >> 63), n爲sint64時
(f)64-bit(wire_type=1)和32-bit(wire_type=5)的編碼方式就比較簡單了,直接在key後面跟上64bits或32bits,採用Little-Endian(小端)字節序。
(g)length-delimited(wire_type=2)的編碼方式:key+length+content, key的編碼方式是統一的,length採用varints編碼方式,content就是由length指定的長度的Bytes。
(h)wire_type=3和4的如今已經不推薦使用了,所以這裏也再也不作介紹。
若是但願更深刻的理解它,能夠從代碼上面去進一步研究,主要接口有protobuf_c_message_unpack、protobuf_c_message_pack、protobuf_c_message_free_unpacked等,路徑/wns/commonlibs/3party/protobuf-c-0.15/protobuf-c-0.15-low-memory-version/src/google/protobuf-cjsp
實際使用過程中,經常出現一些使用不當的狀況致使了程序異常,下面列舉常見的幾個狀況:
一、新增字段限定修飾符設置爲required(項目投入運營之後涉及到版本升級時的新增消息字段若是使用了required,須要全網統一升級)
二、多個版本同時在開發時,往同一個結構裏邊加入相同字段編碼值的新optional字段(要知道字段編碼值正是處於這種兼容性的考慮)
第2種狀況會致使版本兼容性問題,代碼示例以下:函數
發送端(AP),發送函數:性能
void send_person_info_to_wac() { Wns__PersonInfo person = WNS__PERSON_INFO__INIT; person.name = "sanzer"; person.gender = 1; person.has_option = 1; person.option = 5; wns_ipc_to_local_direct(WNS__MODID, 0, WNS__MSGID, (ProtobufCMessage *)&person); }
proto定義:學習
package wns; person_info{ requried string name = 1; required int32 gender = 2; optioned int32 option = 3; }
接收端(WAC),接收函數:ui
int32_t show_person_info_cb(const void *buf, int32_t len, const ProtobufCMessage *msg, const struct wns_cmd_ipc_hdr_t *proxy_hdr) { assert(msg); Wns__PersonInfo *person = (Wns__PersonInfo *)msg; return 0; } // 註冊回調函數: wns_ipc_reg_callback(WNS__MSGID, show_person_info_cb, &wns__person_info__descriptor, AUTO_FREE);
proto定義:
package wns; person_info{ require string name = 1; require int32 gender = 2; optioned double option = 3; }
分別編譯兩種proto,並將動態庫拷貝到指定目錄。在接收端(wac)和發送端(ap)分別運行各自程序,而後觀察接收端解析的數據格式。
結果證實接收端與發送端option字段,都採用了相同的字段編碼值(3),可是不一樣的數據屬性(發送端:int32,接收端:double),這種狀況下,接收端將會解析失敗。
爲了更好的使用它,現制定如下規範:
一、不要修改已經存在的字段編碼值
二、新增字段必須爲optional或repeated,不然沒法保證新老程序在互相傳遞消息時的消息兼容性。
三、在原有的消息中,不能移除已經存在的required字段,optional和repeated類型的字段能夠被移除,可是他們以前使用的標籤號必須被保留,不能被新的字段重用。
四、新增字段標籤號能夠不連續但不能重複。