Protobuf學(xué)習(xí)

什么是 Protobuf

Protobuf是Protocol Buffers的簡稱媳溺,它是Google公司開發(fā)的一種數(shù)據(jù)描述語言迟几,用于描述一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式释牺,并于2008年對外開源。Protobuf可以用于結(jié)構(gòu)化數(shù)據(jù)串行化侦锯,或者說序列化剖踊。它的設(shè)計(jì)非常適用于在網(wǎng)絡(luò)通訊中的數(shù)據(jù)載體庶弃,很適合做數(shù)據(jù)存儲或 RPC 數(shù)據(jù)交換格式,它序列化出來的數(shù)據(jù)量少再加上以 K-V 的方式來存儲數(shù)據(jù)德澈,對消息的版本兼容性非常強(qiáng)歇攻,可用于通訊協(xié)議、數(shù)據(jù)存儲等領(lǐng)域的語言無關(guān)梆造、平臺無關(guān)掉伏、可擴(kuò)展的序列化結(jié)構(gòu)數(shù)據(jù)格式。開發(fā)者可以通過Protobuf附帶的工具生成代碼并實(shí)現(xiàn)將結(jié)構(gòu)化數(shù)據(jù)序列化的功能澳窑。

Protobuf中最基本的數(shù)據(jù)單元是message,是類似Go語言中結(jié)構(gòu)體的存在供常。在message中可以嵌套message或其它的基礎(chǔ)數(shù)據(jù)類型的成員摊聋。

定義Message

首先看一個簡單的例子,比如說你定義一個搜索請求的message栈暇,每一個搜索請求會包含一個搜索的字符串麻裁,返回第幾頁的結(jié)果,以及結(jié)果集的大小源祈。在.proto文件中定義如下:

syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • .proto文件的第一行指定了使用proto3語法煎源。如果省略protocol buffer編譯器默認(rèn)使用proto2語法。他必須是文件中非空非注釋行的第一行香缺。
  • SearchRequest定義中指定了三個字段(name/value鍵值對)手销,每個字段都會有名稱和類型。

指定字段類型

上面的例子中图张,所有的字段都是標(biāo)量類型的兩個整型(page_number和result_per_page)和一個字符串型(query)锋拖。不過你還可以給字段指定復(fù)合類型诈悍,包括枚舉類型和其他message類型

指定字段編號

在message定義中每個字段都有一個唯一的編號,這些編號被用來在二進(jìn)制消息體中識別你定義的這些字段兽埃,一旦你的message類型被用到后就不應(yīng)該在修改這些編號了侥钳。注意在將message編碼成二進(jìn)制消息體時字段編號1-15將會占用1個字節(jié),16-2047將占用兩個字節(jié)柄错。所以在一些頻繁使用用的message中舷夺,你應(yīng)該總是先使用前面1-15字段編號。

你可以指定的最小編號是1售貌,最大是2E29 - 1(536,870,911)给猾。其中19000到19999是給protocol buffers實(shí)現(xiàn)保留的字段標(biāo)號,定義message時不能使用趁矾。同樣的你也不能重復(fù)使用任何當(dāng)前message定義里已經(jīng)使用過和預(yù)留的字段編號耙册。

定義字段的規(guī)則

message的字段必須符合以下規(guī)則:

  • singular:一個遵循singular規(guī)則的字段,在一個結(jié)構(gòu)良好的message消息體(編碼后的message)可以有0或1個該字段(但是不可以有多個)毫捣。這是proto3語法的默認(rèn)字段規(guī)則详拙。(這個理解起來有些晦澀,舉例來說上面例子中三個字段都是singular類型的字段蔓同,在編碼后的消息體中可以有0或者1個query字段饶辙,但不會有多個。)
  • repeated:遵循repeated規(guī)則的字段在消息體重可以有任意多個該字段值斑粱,這些值的順序在消息體重可以保持(就是數(shù)組類型的字段)

添加更多消息類型

在單個.proto文件中可以定義多個message弃揽,這在定義多個相關(guān)message時非常有用。比如說则北,我們定義SearchRequest對應(yīng)的響應(yīng)message SearchResponse,把它加到之前的.proto文件中矿微。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
 ...
}

添加注釋

.proto文件中的注釋和C,C++的注釋風(fēng)格相同尚揣,使用// 和 /* ... */

/* SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response. */

message SearchRequest {
  string query = 1;
  int32 page_number = 2;  // Which page number do we want?
  int32 result_per_page = 3;  // Number of results to return per page.
}

保留字段

當(dāng)你刪掉或者注釋掉message中的一個字段時涌矢,未來其他開發(fā)者在更新message定義時就可以重用之前的字段編號。如果他們意外載入了老版本的.proto文件將會導(dǎo)致嚴(yán)重的問題快骗,比如數(shù)據(jù)損壞娜庇、隱私泄露等。一種避免問題發(fā)生的方式是指定保留的字段編號和字段名稱方篮。如果未來有人用了這些字段標(biāo)識那么在編譯時protocol buffer的編譯器會報(bào)錯名秀。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

標(biāo)量類型

.proto Type Notes C++ Type Java Type Python Type[2] Go Type Ruby Type C# Type PHP Type Dart Type
double double double float float64 Float double float double
float float float float float32 Float float float double
int32 使用可變長度編碼。編碼負(fù)數(shù)的效率低 - 如果您的字段可能有負(fù)值藕溅,請改用sint32匕得。 int32 int int int32 Fixnum or Bignum (as required) int integer int
int64 使用可變長度編碼。編碼負(fù)數(shù)的效率低 - 如果您的字段可能有負(fù)值蜈垮,請改用sint64耗跛。 int64 long int/long[3] int64 Bignum long integer/string[5] Int64
uint32 使用可變長度編碼 uint32 int int/long uint32 Fixnum or Bignum (as required) uint integer int
uint64 使用可變長度編碼. uint64 long int/long uint64 Bignum ulong integer/string[5] Int64
sint32 使用可變長度編碼裕照。簽名的int值。這些比常規(guī)int32更有效地編碼負(fù)數(shù)调塌。 int32 int int int32 Fixnum or Bignum (as required) int integer int
sint64 使用可變長度編碼晋南。簽名的int值。這些比常規(guī)int64更有效地編碼負(fù)數(shù)羔砾。 int64 long int/long int64 Bignum long integer/string[5] Int64
fixed32 總是四個字節(jié)负间。如果值通常大于228,則比uint32更有效姜凄。 uint32 int int/long uint32 Fixnum or Bignum (as required) uint integer int
fixed64 總是八個字節(jié)政溃。如果值通常大于256,則比uint64更有效 uint64 long int/long[3] uint64 Bignum ulong integer/string[5] Int64
sfixed32 總是四個字節(jié) int32 int int int32 Fixnum or Bignum (as required) int integer int
sfixed64 總是八個字節(jié) int64 long int/long int64 Bignum long integer/string[5] Int64
bool bool boolean bool bool TrueClass/FalseClass bool boolean bool
string 字符串必須始終包含UTF-8編碼或7位ASCII文本态秧,且不能超過232董虱。 string String str/unicode string String (UTF-8) string string String
bytes 可以包含不超過232的任意字節(jié)序列。 string ByteString str []byte String (ASCII-8BIT) ByteString string List

默認(rèn)值

當(dāng)一個被編碼的message體中不存在某個message定義中的singular字段時申鱼,在message體解析成的對象中愤诱,相應(yīng)字段會被設(shè)置為message定義中該字段的默認(rèn)值。默認(rèn)值依類型而定:

  • 對于字符串捐友,默認(rèn)值為空字符串淫半。
  • 對于字節(jié),默認(rèn)值為空字節(jié)匣砖。
  • 對于bools科吭,默認(rèn)值為false。
  • 對于數(shù)字類型猴鲫,默認(rèn)值為零对人。
  • 對于枚舉,默認(rèn)值是第一個定義的枚舉值拂共,該值必須為0规伐。
  • 對于消息字段,未設(shè)置該字段匣缘。它的確切值取決于語言。

枚舉類型

在定義消息類型時鲜棠,您可能希望其中一個字段只有一個預(yù)定義的值列表中的值肌厨。例如,假設(shè)您要為每個SearchRequest添加corpus字段豁陆,其中corpus可以是UNIVERSAL柑爸,WEB,IMAGES盒音,LOCAL表鳍,NEWS馅而,PRODUCTS或VIDEO。您可以非常簡單地通過向消息定義添加枚舉譬圣,并為每個可能的枚舉值值添加常量來實(shí)現(xiàn)瓮恭。

在下面的例子中,我們添加了一個名為Corpus的枚舉類型厘熟,和一個Corpus類型的字段:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}

如你所見屯蹦,Corpus枚舉的第一個常量映射到了0:所有枚舉定義都需要包含一個常量映射到0并且作為定義的首行,這是因?yàn)椋?/p>

  • 必須有0值绳姨,這樣我們就可以將0作為枚舉的默認(rèn)值登澜。
  • proto2語法中首行的枚舉值總是默認(rèn)值,為了兼容0值必須作為定義的首行飘庄。

使用其他Message類型

可以使用其他message類型作為字段的類型脑蠕,假設(shè)你想在每個SearchResponse消息中攜帶類型為Result的消息,

你可以在同一個.proto文件中定義一個Result消息類型跪削,然后在SearchResponse中指定一個Result類型的字段谴仙。

message SearchResponse {
  repeated Result results = 1;
}

message Result {
  string url = 1;
  string title = 2;
  repeated string snippets = 3;
}

導(dǎo)入消息定義

在上面的示例中,Result消息類型在與SearchResponse相同的文件中定義 - 如果要用作字段類型的消息類型已在另一個.proto文件中定義切揭,該怎么辦狞甚?

您可以通過導(dǎo)入來使用其他.proto文件中的定義。要導(dǎo)入另一個.proto的定義廓旬,請?jiān)谖募敳刻砑右粋€import語句:

import "myproject/other_protos.proto";

默認(rèn)情況下哼审,您只能使用直接導(dǎo)入的.proto文件中的定義。但是孕豹,有時你可能需要將.proto文件移動到新位置∩埽現(xiàn)在,你可以在舊位置放置一個虛擬.proto文件励背,在文件中使用import public語法將所有導(dǎo)入轉(zhuǎn)發(fā)到新位置春霍,而不是直接移動.proto文件并在一次更改中更新所有調(diào)用點(diǎn)。任何導(dǎo)入包含import public語句的proto文件的人都可以傳遞依賴導(dǎo)入公共依賴項(xiàng)叶眉。例如

// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto

編譯器會在通過命令行參數(shù)-I或者--proto-path中指定的文件夾中搜索.proto文件址儒,如果沒有提供編譯器會在喚其編譯器的目錄中進(jìn)行搜索。通常來說你應(yīng)該將--proto-path的值設(shè)置為你項(xiàng)目的根目錄衅疙,并對所有導(dǎo)入使用完全限定名稱莲趣。

使用proto2的消息類型

可以導(dǎo)入proto2版本的消息類型到proto3的消息類型中使用,當(dāng)然也可以在proto2消息類型中導(dǎo)入proto3的消息類型饱溢。但是proto2的枚舉類型不能直接應(yīng)用到proto3的語法中喧伞。

嵌套消息類型

消息類型可以被定義和使用在其他消息類型中,下面的例子里Result消息被定義在SearchResponse消息中

message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result results = 1;
}

如果你想在外部使用定義在父消息中的子消息,使用Parent.Type引用他們

message SomeOtherMessage {
  SearchResponse.Result result = 1;
}

你可以嵌套任意多層消息

message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}

更新Message

如果一個現(xiàn)存的消息類型不再滿足你當(dāng)前的需求--比如說你希望在消息中增加一個額外的字段--但是仍想使用由舊版的消息格式生成的代碼潘鲫,不用擔(dān)心翁逞!只要記住下面的規(guī)則,在更新消息定義的同時又不破壞現(xiàn)有的代碼就非常簡單溉仑。

  • 不要更改任何已存字段的字段編號挖函。
  • 如果添加了新字段,任何由舊版消息格式生成的代碼所序列化的消息彼念,仍能被依據(jù)新消息格式生成的代碼所解析挪圾。你應(yīng)該記住這些元素的默認(rèn)值這些新生成的代碼就能夠正確地與由舊代碼序列化創(chuàng)建的消息交互了。類似的逐沙,新代碼創(chuàng)建的消息也能由舊版代碼解析:舊版消息(二進(jìn)制)在解析時簡單地忽略了新增的字段哲思,查看下面的未知字段章節(jié)了解更多。
  • 只要在更新后的消息類型中不再重用字段編號吩案,就可以刪除該字段棚赔。你也可以重命名字段,比如說添加OBSOLETE_前綴或者將字段編號設(shè)置為reserved徘郭,這些未來其他用戶就不會意外地重用該字段編號了靠益。

未知字段

未知字段是格式良好的協(xié)議緩沖區(qū)序列化數(shù)據(jù),表示解析器無法識別的字段残揉。例如胧后,當(dāng)舊二進(jìn)制文件解析具有新字段的新二進(jìn)制文件發(fā)送的數(shù)據(jù)時,這些新字段將成為舊二進(jìn)制文件中的未知字段抱环。

最初壳快,proto3消息在解析期間總是丟棄未知字段,但在3.5版本中镇草,我們重新引入了未知字段的保留以匹配proto2行為眶痰。在版本3.5及更高版本中,未知字段在解析期間保留梯啤,并包含在序列化輸出中竖伯。

映射類型

如果你想創(chuàng)建一個映射作為message定義的一部分,protocol buffers提供了一個簡易便利的語法

map<key_type, value_type> map_field = N;

key_type可以是任意整數(shù)或者字符串(除了浮點(diǎn)數(shù)和bytes以外的所有標(biāo)量類型)因宇。注意enum不是一個有效的key_type七婴。value_type可以是除了映射以外的任意類型(意思是protocol buffers的消息體中不允許有嵌套map)。

舉例來說察滑,假如你想創(chuàng)建一個名為projects的映射本姥,每一個Project消息關(guān)聯(lián)一個字符串鍵,你可以像如下來定義:

map<string, Project> projects = 3;
  • 映射里的字段不能是follow repeated規(guī)則的(意思是映射里字段的值不能是數(shù)組)杭棵。
  • 映射里的值是無序的,所以不能依賴映射里元素的順序。
  • 生成.proto的文本格式時魂爪,映射按鍵排序先舷。數(shù)字鍵按數(shù)字排序。
  • 從線路解析或合并時滓侍,如果有重復(fù)的映射鍵蒋川,則使用最后看到的鍵缘挑。從文本格式解析映射時角塑,如果存在重復(fù)鍵用僧,則解析可能會失敗述召。
  • 如果未給映射的字段指定值纺荧,字段被序列化時的行為依語言而定杠纵。在C++语卤, Java和Python中字段類型的默認(rèn)值會被序列化作為字段值抱慌,而其他語言則不會歹鱼。

給Message加包名

你可以在.proto文件中添加一個可選的package符來防止消息類型之前的名稱沖突泣栈。

package foo.bar;
message Open { ... }

在定義message的字段時像如下這樣使用package名稱

message Foo {
  ...
  foo.bar.Open open = 1;
  ...
}

package符對生成代碼的影響視編程語言而定

定義服務(wù)

如果想消息類型與RPC(遠(yuǎn)程過程調(diào)用)系統(tǒng)一起使用,你可以在.proto文件中定義一個RPC服務(wù)接口弥姻,然后protocol buffer編譯器將會根據(jù)你選擇的編程語言生成服務(wù)接口代碼和stub南片,加入你要定義一個服務(wù),它的一個方法接受SearchRequest消息返回SearchResponse消息庭敦,你可以在.proto文件中像如下示例這樣定義它:

service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}

與protocol buffer 一起使用的最簡單的RPC系統(tǒng)是gRPC:一種由Google開發(fā)的語言和平臺中立的開源RPC系統(tǒng)疼进。 gRPC特別適用于protocol buffer,并允許您使用特殊的protocol buffer編譯器插件直接從.proto文件生成相關(guān)的RPC代碼秧廉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末伞广,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子定血,更是在濱河造成了極大的恐慌赔癌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澜沟,死亡現(xiàn)場離奇詭異灾票,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)茫虽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門刊苍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人濒析,你說我怎么就攤上這事正什。” “怎么了号杏?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵婴氮,是天一觀的道長斯棒。 經(jīng)常有香客問我,道長主经,這世上最難降的妖魔是什么荣暮? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮罩驻,結(jié)果婚禮上穗酥,老公的妹妹穿的比我還像新娘。我一直安慰自己惠遏,他們只是感情好砾跃,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著节吮,像睡著了一般抽高。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上课锌,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天厨内,我揣著相機(jī)與錄音,去河邊找鬼渺贤。 笑死雏胃,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的志鞍。 我是一名探鬼主播瞭亮,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼固棚!你這毒婦竟也來了统翩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤此洲,失蹤者是張志新(化名)和其女友劉穎厂汗,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體呜师,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡娶桦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了汁汗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衷畦。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖知牌,靈堂內(nèi)的尸體忽然破棺而出祈争,到底是詐尸還是另有隱情,我是刑警寧澤角寸,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布菩混,位于F島的核電站忿墅,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏沮峡。R本人自食惡果不足惜球匕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帖烘。 院中可真熱鬧,春花似錦橄杨、人聲如沸秘症。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乡摹。三九已至,卻和暖如春采转,著一層夾襖步出監(jiān)牢的瞬間聪廉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工故慈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留板熊,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓察绷,卻偏偏與公主長得像干签,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拆撼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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

  • Day03 Java語言的數(shù)據(jù)類型與基本數(shù)據(jù)類型轉(zhuǎn)換 1容劳,變量與變量的定義 1.1變量的定義 內(nèi)存中的一小塊空間,...
  • 一闸度、什么是Response 我們開發(fā)的軟件是B/S結(jié)構(gòu)的軟件竭贩,是可以通過瀏覽器訪問服務(wù)器的軟件。從瀏覽器輸入一...
    提筆執(zhí)江山閱讀 163評論 0 0
  • 沒有規(guī)矩不成方圓莺禁,C也一樣留量。它有自己的規(guī)矩,你想用就守規(guī)矩睁宰。 標(biāo)識符(Identifier)你給自己寫的東西起名字...
    自行車騎士閱讀 109評論 0 0
  • 《Python編程快速上手——讓繁瑣工作自動化》 >>接昨天 Chp4 列表 in和not in確定一個值是否在列...
    筱晴_cbad閱讀 206評論 0 0
  • 參照文檔:http://c.biancheng.net/view/1769.html這個類似與數(shù)詞和量詞肪获。 數(shù)學(xué)里...
    自行車騎士閱讀 202評論 0 0