深入 ProtoBuf - 簡(jiǎn)介

之前在網(wǎng)絡(luò)通信和通用數(shù)據(jù)交換等應(yīng)用場(chǎng)景中經(jīng)常使用的技術(shù)是 JSON 或 XML,而在最近的開(kāi)發(fā)中接觸到了 Google 的 ProtoBuf。

在查閱相關(guān)資料學(xué)習(xí) ProtoBuf 以及研讀其源碼之后,發(fā)現(xiàn)其在效率、兼容性等方面非常出色。在以后的項(xiàng)目技術(shù)選型中,尤其是網(wǎng)絡(luò)通信、通用數(shù)據(jù)交換等場(chǎng)景應(yīng)該會(huì)優(yōu)先選擇 ProtoBuf。

自己在學(xué)習(xí) ProtoBuf 的過(guò)程中翻譯了官方的主要文檔,一來(lái)當(dāng)然是在學(xué)習(xí) ProtoBuf,二來(lái)是培養(yǎng)閱讀英文文檔的能力,三來(lái)是因?yàn)?Google 的文檔?不存在的!

看完這些文檔對(duì) ProtoBuf 應(yīng)該就有相當(dāng)程度的了解了。

翻譯文檔見(jiàn) [索引]文章索引,導(dǎo)航為翻譯 - 技術(shù) - ProtoBuf 官方文檔。

但是官方文檔更多的是作為查閱和權(quán)威參考,并不意味著看完官方文檔就能立馬理解其原理。

本文以及接下來(lái)的幾篇文章會(huì)對(duì) ProtoBuf 的編碼、序列化、反序列化、反射等原理做一些詳細(xì)介紹,同時(shí)也會(huì)盡量將這些原理表達(dá)的更為通俗易懂。

何為 ProtoBuf

我們先來(lái)看看官方文檔給出的定義和描述:

protocol buffers 是一種語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)、可擴(kuò)展的序列化結(jié)構(gòu)數(shù)據(jù)的方法,它可用于(數(shù)據(jù))通信協(xié)議、數(shù)據(jù)存儲(chǔ)等。

Protocol Buffers 是一種靈活,高效,自動(dòng)化機(jī)制的結(jié)構(gòu)數(shù)據(jù)序列化方法-可類比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更為簡(jiǎn)單。

你可以定義數(shù)據(jù)的結(jié)構(gòu),然后使用特殊生成的源代碼輕松的在各種數(shù)據(jù)流中使用各種語(yǔ)言進(jìn)行編寫和讀取結(jié)構(gòu)數(shù)據(jù)。你甚至可以更新數(shù)據(jù)結(jié)構(gòu),而不破壞由舊數(shù)據(jù)結(jié)構(gòu)編譯的已部署程序。

簡(jiǎn)單來(lái)講, ProtoBuf 是結(jié)構(gòu)數(shù)據(jù)序列化[1] 方法,可簡(jiǎn)單類比于 XML[2],其具有以下特點(diǎn):

  • 語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)。即 ProtoBuf 支持 Java、C++、Python 等多種語(yǔ)言,支持多個(gè)平臺(tái)
  • 高效。即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更為簡(jiǎn)單
  • 擴(kuò)展性、兼容性好。你可以更新數(shù)據(jù)結(jié)構(gòu),而不影響和破壞原有的舊程序

序列化[1]:將結(jié)構(gòu)數(shù)據(jù)對(duì)象轉(zhuǎn)換成能夠被存儲(chǔ)和傳輸(例如網(wǎng)絡(luò)傳輸)的格式,同時(shí)應(yīng)當(dāng)要保證這個(gè)序列化結(jié)果在之后(可能在另一個(gè)計(jì)算環(huán)境中)能夠被重建回原來(lái)的結(jié)構(gòu)數(shù)據(jù)或?qū)ο蟆?br> 更為詳盡的介紹可參閱 維基百科
類比于 XML[2]:這里主要指在數(shù)據(jù)通信和數(shù)據(jù)存儲(chǔ)應(yīng)用場(chǎng)景中序列化方面的類比,但個(gè)人認(rèn)為 XML 作為一種擴(kuò)展標(biāo)記語(yǔ)言和 ProtoBuf 還是有著本質(zhì)區(qū)別的。

使用 ProtoBuf

對(duì) ProtoBuf 的基本概念有了一定了解之后,我們來(lái)看看具體該如何使用 ProtoBuf。
第一步,創(chuàng)建 .proto 文件,定義數(shù)據(jù)結(jié)構(gòu),如下例1所示:

// 例1: 在 xxx.proto 文件中定義 Example1 message
message Example1 {
    optional string stringVal = 1;
    optional bytes bytesVal = 2;
    message EmbeddedMessage {
        int32 int32Val = 1;
        string stringVal = 2;
    }
    optional EmbeddedMessage embeddedExample1 = 3;
    repeated int32 repeatedInt32Val = 4;
    repeated string repeatedStringVal = 5;
}

我們?cè)谏侠卸x了一個(gè)名為 Example1 的 消息,語(yǔ)法很簡(jiǎn)單,message 關(guān)鍵字后跟上消息名稱:

message xxx {

}

之后我們?cè)谄渲卸x了 message 具有的字段,形式為:

message xxx {
  // 字段規(guī)則:required -> 字段只能也必須出現(xiàn) 1 次
  // 字段規(guī)則:optional -> 字段可出現(xiàn) 0 次或1次
  // 字段規(guī)則:repeated -> 字段可出現(xiàn)任意多次(包括 0)
  // 類型:int32、int64、sint32、sint64、string、32-bit ....
  // 字段編號(hào):0 ~ 536870911(除去 19000 到 19999 之間的數(shù)字)
  字段規(guī)則 類型 名稱 = 字段編號(hào);
}

在上例中,我們定義了:

  • 類型 string,名為 stringVal 的 optional 可選字段,字段編號(hào)為 1,此字段可出現(xiàn) 0 或 1 次
  • 類型 bytes,名為 bytesVal 的 optional 可選字段,字段編號(hào)為 2,此字段可出現(xiàn) 0 或 1 次
  • 類型 EmbeddedMessage(自定義的內(nèi)嵌 message 類型),名為 embeddedExample1 的 optional 可選字段,字段編號(hào)為 3,此字段可出現(xiàn) 0 或 1 次
  • 類型 int32,名為 repeatedInt32Val 的 repeated 可重復(fù)字段,字段編號(hào)為 4,此字段可出現(xiàn) 任意多次(包括 0)
  • 類型 string,名為 repeatedStringVal 的 repeated 可重復(fù)字段,字段編號(hào)為 5,此字段可出現(xiàn) 任意多次(包括 0)

關(guān)于 proto2 定義 message 消息的更多語(yǔ)法細(xì)節(jié),例如具有支持哪些類型,字段編號(hào)分配、import
導(dǎo)入定義,reserved 保留字段等知識(shí)請(qǐng)參閱 [翻譯] ProtoBuf 官方文檔(二)- 語(yǔ)法指引(proto2)

關(guān)于定義時(shí)的一些規(guī)范請(qǐng)參閱 [翻譯] ProtoBuf 官方文檔(四)- 規(guī)范指引

第二步,protoc 編譯 .proto 文件生成讀寫接口

我們?cè)?.proto 文件中定義了數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)是面向開(kāi)發(fā)者和業(yè)務(wù)程序的,并不面向存儲(chǔ)和傳輸。

當(dāng)需要把這些數(shù)據(jù)進(jìn)行存儲(chǔ)或傳輸時(shí),就需要將這些結(jié)構(gòu)數(shù)據(jù)進(jìn)行序列化、反序列化以及讀寫。那么如何實(shí)現(xiàn)呢?不用擔(dān)心, ProtoBuf 將會(huì)為我們提供相應(yīng)的接口代碼。如何提供?答案就是通過(guò) protoc 這個(gè)編譯器。

可通過(guò)如下命令生成相應(yīng)的接口代碼:

// $SRC_DIR: .proto 所在的源目錄
// --cpp_out: 生成 c++ 代碼
// $DST_DIR: 生成代碼的目標(biāo)目錄
// xxx.proto: 要針對(duì)哪個(gè) proto 文件生成接口代碼

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/xxx.proto

最終生成的代碼將提供類似如下的接口:

例子-序列化和解析接口.png
例子-protoc 生成接口.png

第三步,調(diào)用接口實(shí)現(xiàn)序列化、反序列化以及讀寫
針對(duì)第一步中例1定義的 message,我們可以調(diào)用第二步中生成的接口,實(shí)現(xiàn)測(cè)試代碼如下:

//
// Created by yue on 18-7-21.
//
#include <iostream>
#include <fstream>
#include <string>
#include "single_length_delimited_all.pb.h"

int main() {
    Example1 example1;
    example1.set_stringval("hello,world");
    example1.set_bytesval("are you ok?");

    Example1_EmbeddedMessage *embeddedExample2 = new Example1_EmbeddedMessage();

    embeddedExample2->set_int32val(1);
    embeddedExample2->set_stringval("embeddedInfo");
    example1.set_allocated_embeddedexample1(embeddedExample2);

    example1.add_repeatedint32val(2);
    example1.add_repeatedint32val(3);
    example1.add_repeatedstringval("repeated1");
    example1.add_repeatedstringval("repeated2");

    std::string filename = "single_length_delimited_all_example1_val_result";
    std::fstream output(filename, std::ios::out | std::ios::trunc | std::ios::binary);
    if (!example1.SerializeToOstream(&output)) {
        std::cerr << "Failed to write example1." << std::endl;
        exit(-1);
    }

    return 0;
}

關(guān)于 protoc 的使用以及接口調(diào)用的更多信息可參閱 [翻譯] ProtoBuf 官方文檔(九)- (C++開(kāi)發(fā))教程

關(guān)于例1的完整代碼請(qǐng)參閱 源碼:protobuf 例1。其中的 single_length_delimited_all.* 為例子相關(guān)代碼和文件。

因?yàn)榇讼盗形恼轮攸c(diǎn)在于深入 ProtoBuf 的編碼、序列化、反射等原理,關(guān)于 ProtoBuf 的語(yǔ)法、使用等只做簡(jiǎn)單介紹,更為詳見(jiàn)的使用教程可參閱我翻譯的系列官方文檔。

關(guān)于 ProtoBuf 的一些思考

官方文檔以及網(wǎng)上很多文章提到 ProtoBuf 可類比 XML 或 JSON。

那么 ProtoBuf 是否就等同于 XML 和 JSON 呢,它們是否具有完全相同的應(yīng)用場(chǎng)景呢?

個(gè)人認(rèn)為如果要將 ProtoBuf、XML、JSON 三者放到一起去比較,應(yīng)該區(qū)分兩個(gè)維度。一個(gè)是數(shù)據(jù)結(jié)構(gòu)化,一個(gè)是數(shù)據(jù)序列化。這里的數(shù)據(jù)結(jié)構(gòu)化主要面向開(kāi)發(fā)或業(yè)務(wù)層面,數(shù)據(jù)序列化面向通信或存儲(chǔ)層面,當(dāng)然數(shù)據(jù)序列化也需要“結(jié)構(gòu)”和“格式”,所以這兩者之間的區(qū)別主要在于面向領(lǐng)域和場(chǎng)景不同,一般要求和側(cè)重點(diǎn)也會(huì)有所不同。數(shù)據(jù)結(jié)構(gòu)化側(cè)重人類可讀性甚至有時(shí)會(huì)強(qiáng)調(diào)語(yǔ)義表達(dá)能力,而數(shù)據(jù)序列化側(cè)重效率和壓縮。

從這兩個(gè)維度,我們可以做出下面的一些思考。

XML 作為一種擴(kuò)展標(biāo)記語(yǔ)言,JSON 作為源于 JS 的數(shù)據(jù)格式,都具有數(shù)據(jù)結(jié)構(gòu)化的能力。

例如 XML 可以衍生出 HTML (雖然 HTML 早于 XML,但從概念上講,HTML 只是預(yù)定義標(biāo)簽的 XML),HTML 的作用是標(biāo)記和表達(dá)萬(wàn)維網(wǎng)中資源的結(jié)構(gòu),以便瀏覽器更好的展示萬(wàn)維網(wǎng)資源,同時(shí)也要盡可能保證其人類可讀以便開(kāi)發(fā)人員進(jìn)行編輯,這就是面向業(yè)務(wù)或開(kāi)發(fā)層面的數(shù)據(jù)結(jié)構(gòu)化

再如 XML 還可衍生出 RDF/RDFS,進(jìn)一步表達(dá)語(yǔ)義網(wǎng)中資源的關(guān)系和語(yǔ)義,同樣它強(qiáng)調(diào)數(shù)據(jù)結(jié)構(gòu)化的能力和人類可讀。

關(guān)于 RDF/RDFS 和語(yǔ)義網(wǎng)的概念可查詢相關(guān)資料了解,或參閱 2-Answer 系列-本體構(gòu)建模塊(一)3-Answer 系列-本體構(gòu)建模塊(二) ,文中有一些簡(jiǎn)單介紹。

JSON 也是同理,在很多場(chǎng)合更多的是體現(xiàn)了數(shù)據(jù)結(jié)構(gòu)化的能力,例如作為交互接口的數(shù)據(jù)結(jié)構(gòu)的表達(dá)。在 MongoDB 中采用 JSON 作為查詢語(yǔ)句,也是在發(fā)揮其數(shù)據(jù)結(jié)構(gòu)化的能力。

當(dāng)然,JSON、XML 同樣也可以直接被用來(lái)數(shù)據(jù)序列化,實(shí)際上很多時(shí)候它們也是這么被使用的,例如直接采用 JSON、XML 進(jìn)行網(wǎng)絡(luò)通信傳輸,此時(shí) JSON、XML 就成了一種序列化格式,它發(fā)揮了數(shù)據(jù)序列化的能力。但是經(jīng)常這么被使用,不代表這么做就是合理。實(shí)際將 JSON、XML 直接作用數(shù)據(jù)序列化通常并不是最優(yōu)選擇,因?yàn)樗鼈冊(cè)谒俣取⑿省⒖臻g上并不是最優(yōu)。換句話說(shuō)它們更適合數(shù)據(jù)結(jié)構(gòu)化而非數(shù)據(jù)序列化。

扯完 XML 和 JSON,我們來(lái)看看 ProtoBuf,同樣的 ProtoBuf 也具有數(shù)據(jù)結(jié)構(gòu)化的能力,其實(shí)也就是上面介紹的 message 定義。我們能夠在 .proto 文件中,通過(guò) message、import、內(nèi)嵌 message 等語(yǔ)法來(lái)實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)化,但是很容易能夠看出,ProtoBuf 在數(shù)據(jù)結(jié)構(gòu)化方面和 XML、JSON 相差較大,人類可讀性較差,不適合上面提到的 XML、JSON 的一些應(yīng)用場(chǎng)景。

但是如果從數(shù)據(jù)序列化的角度你會(huì)發(fā)現(xiàn) ProtoBuf 有著明顯的優(yōu)勢(shì),效率、速度、空間幾乎全面占優(yōu),看完后面的 ProtoBuf 編碼的文章,你更會(huì)了解 ProtoBuf 是如何極盡所能的壓榨每一寸空間和性能,而其中的編碼原理正是 ProtoBuf 的關(guān)鍵所在,message 的表達(dá)能力并不是 ProtoBuf 最關(guān)鍵的重點(diǎn)。所以可以看出 ProtoBuf 重點(diǎn)側(cè)重于數(shù)據(jù)序列化 而非 數(shù)據(jù)結(jié)構(gòu)化

最終對(duì)這些個(gè)人思考做一些小小的總結(jié):

  1. XML、JSON、ProtoBuf 都具有數(shù)據(jù)結(jié)構(gòu)化數(shù)據(jù)序列化的能力
  2. XML、JSON 更注重數(shù)據(jù)結(jié)構(gòu)化,關(guān)注人類可讀性和語(yǔ)義表達(dá)能力。ProtoBuf 更注重數(shù)據(jù)序列化,關(guān)注效率、空間、速度,人類可讀性差,語(yǔ)義表達(dá)能力不足(為保證極致的效率,會(huì)舍棄一部分元信息)
  3. ProtoBuf 的應(yīng)用場(chǎng)景更為明確,XML、JSON 的應(yīng)用場(chǎng)景更為豐富。

下一篇

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,818評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,185評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 175,656評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 62,647評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,446評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 54,951評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,041評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,189評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,718評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,800評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,419評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,420評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,755評(píng)論 2 371

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,779評(píng)論 18 139
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,985評(píng)論 2 89
  • 翻譯查閱外網(wǎng)資料過(guò)程中遇到的比較優(yōu)秀的文章和資料,一是作為技術(shù)參考以便日后查閱,二是訓(xùn)練英文能力。此文翻譯自 Pr...
    401閱讀 68,222評(píng)論 1 39
  • 這篇文章是根據(jù)Jake Wharton在GOTO CopenHagen 2016上的講話整理的。 下一個(gè)版本(2....
    uncle_charlie閱讀 4,291評(píng)論 1 1