Google Protocol Buffers 數(shù)據(jù)交換協(xié)議

protobuf 簡介

protobuf是什么

protobuf(Protocol Buffers)是Google推出的一個(gè)結(jié)構(gòu)化數(shù)據(jù)交換協(xié)議劲室,用于傳遞自定義的消息格式,可用于同一臺機(jī)器的進(jìn)程間、不同設(shè)備進(jìn)程間的數(shù)據(jù)傳遞泡一。protobuf是一種語言無關(guān)兰吟、平臺無關(guān)、高效仙逻、擴(kuò)展性良好的語言驰吓,提供了一種將結(jié)構(gòu)化數(shù)據(jù)進(jìn)行序列化和反序列化的方法。

相對于XML系奉,protobuf的體積更小檬贰、速度更快、使用更簡單缺亮。我們僅需要定義一次數(shù)據(jù)結(jié)構(gòu)翁涤,就可以很輕松地使用生成的代碼讀/寫數(shù)據(jù),而且這些數(shù)據(jù)結(jié)構(gòu)是向后兼容的萌踱。

官方網(wǎng)站

https://developers.google.com/protocol-buffers/

protobuf的優(yōu)劣

為什么不使用XML葵礼?

相對于XML來說,Protocol buffers在序列化結(jié)構(gòu)化數(shù)據(jù)上虫蝶,具有非常明顯的優(yōu)勢:

  • 更加簡單
  • 體積減小3~10倍
  • 速度提高20~100倍
  • 更清晰
  • 生成的數(shù)據(jù)結(jié)構(gòu)代碼章咧,更容易使用

如果要生成一個(gè)具有nameemailperson實(shí)例,XML做法如下:

  <person>
    <name>John Doe</name>
    <email>jdoe@example.com</email>
  </person>

使用protocol:

# Textual representation of a protocol buffer.
# This is *not* the binary format used on the wire.
person {
  name: "John Doe"
  email: "jdoe@example.com"
}

當(dāng)此消息被編碼為二進(jìn)制格式時(shí)能真,長度大概是28字節(jié)赁严,解析時(shí)間為100200ns。在去除所有空格后粉铐,XML版本也至少為69字節(jié)疼约,解析時(shí)間長達(dá)500010000ns。

同時(shí)蝙泼,使用protocol buffer更簡單:

  cout << "Name: " << person.name() << endl;
  cout << "E-mail: " << person.email() << endl;

使用XML的代碼:

  cout << "Name: "
       << person.getElementsByTagName("name")->item(0)->innerText()
       << endl;
  cout << "E-mail: "
       << person.getElementsByTagName("email")->item(0)->innerText()
       << endl;

相對于JSON來說程剥,protobuf也有明顯的優(yōu)勢:

  • 更好的前后數(shù)據(jù)版本兼容性
  • 提供了驗(yàn)證機(jī)制,更容易被擴(kuò)展
  • 不同語言間互操作性更好

protobuf也有一些缺點(diǎn),并不是適合所有場景织鲸。

  • 二進(jìn)制編碼和傳輸舔腾,可讀性差
  • 編碼和解碼依賴額外的庫,不能在瀏覽器搂擦、JS中直接使用
  • 缺乏自描述

如何使用protobuf

  • 定義.proto文件
  • 編譯protocol buffer
  • 使用Java protocol buffer API 讀寫數(shù)據(jù)

下面是通過Java使用protobuf的官方示例:https://developers.google.com/protocol-buffers/docs/javatutorial 稳诚,并在此基礎(chǔ)上進(jìn)行了簡化。

定義.proto文件

定義需要序列化的數(shù)據(jù)結(jié)構(gòu)瀑踢,為message中的每一個(gè)變量設(shè)置名稱和類型扳还。下面

package tutorial;

option java_package = "yano";
option java_outer_classname = "AddressBookProtos";

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

message AddressBook {
  repeated Person person = 1;
}
  • package:防止不同工程中的命名沖突;生成Java的proto時(shí)橱夭,如果沒有指定java_package氨距,包名默認(rèn)為package
  • java_package:包名
  • java_outer_classname:定義生成Java代碼的文件名(類名);如果沒有指定棘劣,會將proto文件變成駝峰形式:默認(rèn)會將my_proto.proto生成MyProto的類文件俏让。

定義字段時(shí),我們使用了required茬暇、optional舆驶、repeated三個(gè)關(guān)鍵字。這些關(guān)鍵字表示對字段的約束而钞,分別表示:

  • required-非空約束。如果字段值為空拘荡,會被認(rèn)為是uninitialized臼节,并拋出異常。
  • optional-可選珊皿。表示字段可以賦值网缝,也可以不賦值。不賦值時(shí)蟋定,將會使用默認(rèn)值粉臊。
  • repeated-可重復(fù)次數(shù)。表示字段可以重復(fù)使用的次數(shù)驶兜,重復(fù)順序會被保存在protobuf中扼仲,可以將其理解為一個(gè)數(shù)組。

proto文件中的其它格式抄淑,在此不作介紹屠凶,詳細(xì)內(nèi)容可以參考官方文檔。

編譯protocol buffer

現(xiàn)在我們有了一個(gè).proto文件肆资,接下來就需要將生成class文件矗愧,我們可以通過這個(gè)class文件讀寫AddressBook的消息。

  1. proto編譯器下載地址:https://developers.google.com/protocol-buffers/docs/downloads
  2. 運(yùn)行編譯器郑原,指定proto路徑唉韭、生成路徑夜涕、.proto文件。
protoc -I=$SRC_DIR --java_out=$DST_DIR $SRC_DIR/addressbook.proto

Protocol Buffer API

通過proto編譯器属愤,addressbook.proto生成了Java類AddressBookProtos.java女器。每個(gè)class都有自己的Builder,用以生成該類的實(shí)例春塌。

// required string name = 1;
public boolean hasName();
public String getName();

// required int32 id = 2;
public boolean hasId();
public int getId();

// optional string email = 3;
public boolean hasEmail();
public String getEmail();

// repeated .tutorial.Person.PhoneNumber phone = 4;
public List<PhoneNumber> getPhoneList();
public int getPhoneCount();
public PhoneNumber getPhone(int index);

Person.Builder 除了getters晓避,還有setters方法:

// required string name = 1;
public boolean hasName();
public java.lang.String getName();
public Builder setName(String value);
public Builder clearName();

// required int32 id = 2;
public boolean hasId();
public int getId();
public Builder setId(int value);
public Builder clearId();

// optional string email = 3;
public boolean hasEmail();
public String getEmail();
public Builder setEmail(String value);
public Builder clearEmail();

// repeated .tutorial.Person.PhoneNumber phone = 4;
public List<PhoneNumber> getPhoneList();
public int getPhoneCount();
public PhoneNumber getPhone(int index);
public Builder setPhone(int index, PhoneNumber value);
public Builder addPhone(PhoneNumber value);
public Builder addAllPhone(Iterable<PhoneNumber> value);
public Builder clearPhone();

創(chuàng)建一個(gè) Person 實(shí)例:

Person john =
  Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .addPhone(
      Person.PhoneNumber.newBuilder()
        .setNumber("555-4321")
        .setType(Person.PhoneType.HOME))
    .build();

標(biāo)準(zhǔn)消息方法

以下方法能夠讓我們檢查和操作整個(gè)message:

  • isInitialized():檢查required字段是否全部設(shè)置
  • toString():返回可閱讀的格式,在debug時(shí)非常有用
  • mergeFrom(Message other):將other的內(nèi)容合并到該message中只壳,會覆蓋相同的字段俏拱,對repeated字段會添加
  • clear():重置所有字段

解析和序列化

所有的protocol buffer類都有讀寫二進(jìn)制的方法:

  • byte[] toByteArray():序列化消息并返回包含其原始字節(jié)的字節(jié)數(shù)組
  • static Person parseFrom(byte[] data):通過給定的字節(jié)數(shù)組,解析message
  • void writeTo(OutputStream output):序列化消息吼句,并將其寫到OutputStream
  • static Person parseFrom(InputStream input):從InputStream中讀取并解析message

擴(kuò)展protobuf

在擴(kuò)展proto文件時(shí)锅必,需要注意以下事項(xiàng):

  • 絕對不能改變已經(jīng)存在的字段的tag numbers
  • 絕對不能添加或刪除required字段
  • 可以刪除optionalrepeated字段
  • 可以添加新的optionalrepeated字段,但是必須使用新的tag numbers

結(jié)束語

我的博客:http://www.reibang.com/users/6835c29fc12a/latest_articles
我的知乎:https://www.zhihu.com/people/liu-jia-yu-58/activities

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惕艳,一起剝皮案震驚了整個(gè)濱河市搞隐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌远搪,老刑警劉巖劣纲,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谁鳍,居然都是意外死亡癞季,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門倘潜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绷柒,“玉大人,你說我怎么就攤上這事涮因》夏溃” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵养泡,是天一觀的道長嗜湃。 經(jīng)常有香客問我,道長澜掩,這世上最難降的妖魔是什么净蚤? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮输硝,結(jié)果婚禮上今瀑,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好橘荠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布屿附。 她就那樣靜靜地躺著,像睡著了一般哥童。 火紅的嫁衣襯著肌膚如雪挺份。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天贮懈,我揣著相機(jī)與錄音匀泊,去河邊找鬼。 笑死朵你,一個(gè)胖子當(dāng)著我的面吹牛各聘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抡医,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼躲因,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了忌傻?” 一聲冷哼從身側(cè)響起大脉,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎水孩,沒想到半個(gè)月后镰矿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡俘种,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年衡怀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片安疗。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖够委,靈堂內(nèi)的尸體忽然破棺而出荐类,到底是詐尸還是另有隱情,我是刑警寧澤茁帽,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布玉罐,位于F島的核電站,受9級特大地震影響潘拨,放射性物質(zhì)發(fā)生泄漏吊输。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一铁追、第九天 我趴在偏房一處隱蔽的房頂上張望季蚂。 院中可真熱鬧,春花似錦、人聲如沸扭屁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽料滥。三九已至然眼,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間葵腹,已是汗流浹背高每。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留践宴,地道東北人鲸匿。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像浴井,于是被迫代替她去往敵國和親晒骇。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

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