[翻譯] ProtoBuf 官方文檔(二)- 語法指引(proto2)

翻譯查閱外網(wǎng)資料過程中遇到的比較優(yōu)秀的文章和資料等孵,一是作為技術參考以便日后查閱税肪,二是訓練英文能力后频。

此文翻譯自 Protocol Buffers 官方文檔 Language Guide 部分

翻譯為意譯裂问,不會照本宣科的字字對照翻譯
以下為原文內(nèi)容翻譯

語法指引(proto2)

本指南介紹如何使用 protocol buffer 語言來構造 protocol buffer 數(shù)據(jù),包括 .proto 文件語法以及如何從 .proto 文件生成數(shù)據(jù)訪問類芜飘。它涵蓋了 protocol buffer 語言的 proto2 版本:有關較新的 proto3 語法的信息,請參閱 Proto3 語法指引磨总。

這是一個參考指南嗦明,有關使用本文檔中描述的許多功能的分步示例,請參閱各種語言對應的具體 教程蚪燕。

定義一個 Message 類型

首先讓我們看一個非常簡單的例子娶牌。假設你要定義一個搜索請求的 message 格式,其中每個搜索請求都有一個查詢字符串馆纳,你感興趣的特定結(jié)果頁數(shù)(第幾頁)以及每頁的結(jié)果數(shù)诗良。下面就是定義這個請求的 .proto 文件:

message SearchRequest {
  required string query = 1;  // 查詢字符串
  optional int32 page_number = 2;  // 第幾頁
  optional int32 result_per_page = 3;  // 每頁的結(jié)果數(shù)
}

SearchRequest message 定義指定了三個字段(名稱/值對)鉴裹,每個字段對應著要包含在 message 中的數(shù)據(jù)绪穆,每個字段都有一個名稱和類型试溯。

指定字段類型

在上面的示例中蹄咖,所有字段都是 標量類型:兩個整數(shù)(page_numberresult_per_page)和一個字符串(query)坐梯。但是亡蓉,你還可以為字段指定復合類型樊卓,包括 枚舉 和其它的 message 類型唾戚。

分配字段編號

如你所見,message 定義中的每個字段都有唯一編號。這些數(shù)字以 message 二進制格式 標識你的字段媒咳,并且一旦你的 message 被使用,這些編號就無法再更改种远。請注意涩澡,1 到 15 范圍內(nèi)的字段編號需要一個字節(jié)進行編碼,編碼結(jié)果將同時包含編號和類型(你可以在 Protocol Buffer 編碼 中找到更多相關信息)坠敷。16 到 2047 范圍內(nèi)的字段編號占用兩個字節(jié)妙同。因此,你應該為非常頻繁出現(xiàn)的 message 元素保留字段編號 1 到 15膝迎。請記住為將來可能添加的常用元素預留出一些空間粥帚。

你可以指定的最小字段數(shù)為 1,最大字段數(shù)為 229 - 1 或 536,870,911限次。你也不能使用 19000 到 19999 范圍內(nèi)的數(shù)字(FieldDescriptor::kFirstReservedNumberFieldDescriptor::kLastReservedNumber)芒涡,因為它們是為 Protocol Buffers 的實現(xiàn)保留的 - 如果你使用這些保留數(shù)字之一,protocol buffer 編譯器會抱怨你的 .proto卖漫。同樣费尽,你也不能使用任何以前定義的 保留 字段編號。

譯者注:
“不能使用任何以前定義的保留字段編號” 指的是使用 reserved 關鍵字聲明的保留字段羊始。

指定字段規(guī)則

你指定的 message 字段可以是下面幾種情況之一:

  • required: 格式良好的 message 必須包含該字段一次旱幼。
  • optional: 格式良好的 message 可以包含該字段零次或一次(不超過一次)。
  • repeated: 該字段可以在格式良好的消息中重復任意多次(包括零)店枣。其中重復值的順序會被保留速警。

由于一些歷史原因,標量數(shù)字類型的 repeated 字段不能盡可能高效地編碼鸯两。新代碼應使用特殊選項 [packed = true] 來獲得更高效的編碼闷旧。例如:

repeated int32 samples = 4 [packed=true];

你可以在 Protocol Buffer 編碼 中找到更多有關 packed 編碼的信息。

對 required 的使用永遠都應該非常小心钧唐。如果你希望在某個時刻停止寫入或發(fā)送 required 字段忙灼,則將字段更改為可選字段將會有問題 - 舊讀者會認為沒有此字段的郵件不完整,可能會無意中拒絕或刪除它們。你應該考慮為 buffers 編寫特定于應用程序的自定義驗證的例程该园。谷歌的一些工程師得出的結(jié)論是酸舍,使用 required 弊大于利;他們更喜歡只使用 optional 和 repeated里初。但是啃勉,這種觀點并未普及。

譯者注:在 proto3 中已經(jīng)為兼容性徹底拋棄 required双妨。

添加更多 message 類型

可以在單個 .proto 文件中定義多種 message 類型淮阐。這在你需要定義多個相關 message 的時候會很有用 - 例如,如果要定義與搜索請求相應的搜索回復 message - SearchResponse message刁品,則可以將其添加到相同的 .proto:

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

message SearchResponse {
 ...
}

組合 messages 會導致膨脹雖然可以在單個 .proto 文件中定義多種 messages 類型(例如 message泣特,enum 和 service),但是當在單個文件中定義了大量具有不同依賴關系的 messages 時挑随,它也會導致依賴性膨脹状您。建議每個 .proto 文件包含盡可能少的 message 類型。

添加注釋

為你的 .proto 文件添加注釋兜挨,可以使用 C/C++ 語法風格的注釋 // 和 /* ... */ 膏孟。

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

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

Reserved 保留字段

如果你通過完全刪除字段或?qū)⑵渥⑨尩魜砀?message 類型,則未來一些用戶在做他們的修改或更新時就可能會再次使用這些字段編號拌汇。如果以后加載相同 .proto 的舊版本骆莹,這可能會導致一些嚴重問題,包括數(shù)據(jù)損壞担猛,隱私錯誤等。確保不會發(fā)生這種情況的一種方法是指定已刪除字段的字段編號(有時也需要指定名稱為保留狀態(tài)丢氢,英文名稱可能會導致 JSON 序列化問題)為 “保留” 狀態(tài)傅联。如果將來的任何用戶嘗試使用這些字段標識符,protocol buffer 編譯器將會抱怨疚察。

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

請注意蒸走,你不能在同一 "reserved" 語句中將字段名稱和字段編號混合在一起指定。

你的 .proto 文件將生成什么貌嫡?

當你在 .proto 上運行 protocol buffer 編譯器時比驻,編譯器將會生成所需語言的代碼,這些代碼可以操作文件中描述的 message 類型岛抄,包括獲取和設置字段值别惦、將 message 序列化為輸出流、以及從輸入流中解析出 message夫椭。

  • 對于 C++掸掸,編譯器從每個 .proto 生成一個 .h 和 .cc 文件,其中包含文件中描述的每種 message 類型對應的類。
  • 對于 Java扰付,編譯器為每個 message 類型生成一個 .java 文件(類)堤撵,以及用于創(chuàng)建 message 類實例的特殊 Builder 類。
  • Python 有點不同 - Python 編譯器生成一個模塊羽莺,其中包含 .proto 中每種 message 類型的靜態(tài)描述符实昨,然后與元類一起使用以創(chuàng)建必要的 Python 數(shù)據(jù)訪問類。
  • 對于 Go盐固,編譯器會生成一個 .pb.go 文件荒给,其中包含對應每種 message 類型的類型。
    你可以按照所選語言的教程了解更多有關各種語言使用 API ??的信息闰挡。有關更多 API 詳細信息试伙,請參閱相關的 API 參考

標量值類型

標量 message 字段可以具有以下幾種類型之一 - 該表顯示 .proto 文件中指定的類型浅侨,以及自動生成的類中的相應類型:

.proto Type Notes C++ Type Java Type Python Type[2] Go Type
double double double float *float64
float float float float *float32
int32 使用可變長度編碼智玻。編碼負數(shù)的效率低 - 如果你的字段可能有負值,請改用 sint32 int32 int int *int32
int64 使用可變長度編碼夺脾。編碼負數(shù)的效率低 - 如果你的字段可能有負值之拨,請改用 sint64 int64 long int/long[3] *int64
uint32 使用可變長度編碼 uint32 int[1] int/long[3] *uint32
uint64 使用可變長度編碼 uint64 long[1] int/long[3] *uint64
sint32 使用可變長度編碼。有符號的 int 值咧叭。這些比常規(guī) int32 對負數(shù)能更有效地編碼 int32 int int *int32
sint64 使用可變長度編碼蚀乔。有符號的 int 值。這些比常規(guī) int64 對負數(shù)能更有效地編碼 int64 long int/long[3] *int64
fixed32 總是四個字節(jié)菲茬。如果值通常大于 228吉挣,則比 uint32 更有效。 uint32 int[1] int/long[3] *uint32
fixed64 總是八個字節(jié)婉弹。如果值通常大于 256睬魂,則比 uint64 更有效。 uint64 long[1] int/long[3] *uint64
sfixed32 總是四個字節(jié) int32 int int *int32
sfixed64 總是八個字節(jié) int64 long int/long[3] *int64
bool bool boolean bool *bool
string 字符串必須始終包含 UTF-8 編碼或 7 位 ASCII 文本 string String str/unicode[4] *string
bytes 可以包含任意字節(jié)序列 string ByteString str []byte

Protocol Buffer 編碼 中你可以找到有關序列化 message 時這些類型如何被編碼的詳細信息镀赌。

[1] 在 Java 中氯哮,無符號的 32 位和 64 位整數(shù)使用它們對應的帶符號表示,第一個 bit 位只是簡單的存儲在符號位中商佛。
[2] 在所有情況下喉钢,設置字段的值將執(zhí)行類型檢查以確保其有效。
[3] 64 位或無符號 32 位整數(shù)在解碼時始終表示為 long良姆,但如果在設置字段時給出 int肠虽,則可以為int。在所有情況下玛追,該值必須適合設置時的類型舔痕。見 [2]。
[4] Python 字符串在解碼時表示為 unicode,但如果給出了 ASCII 字符串伯复,則可以是 str(這條可能會發(fā)生變化)慨代。

Optional 可選字段和默認值

如上所述,message 描述中的元素可以標記為可選 optional啸如。格式良好的 message 可能包含也可能不包含被聲明為可選的元素侍匙。解析 message 時,如果 message 不包含 optional 元素叮雳,則解析對象中的相應字段將設置為該字段的默認值想暗。可以將默認值指定為 message 描述的一部分帘不。例如说莫,假設你要為 SearchRequest 的 result_per_page 字段提供默認值10。

optional int32 result_per_page = 3 [default = 10];

如果未為 optional 元素指定默認值寞焙,則使用特定于類型的默認值:對于字符串储狭,默認值為空字符串。對于 bool捣郊,默認值為 false辽狈。對于數(shù)字類型,默認值為零呛牲。對于枚舉刮萌,默認值是枚舉類型定義中列出的第一個值。這意味著在將值添加到枚舉值列表的開頭時必須小心娘扩。有關如何安全的更改定義的指導着茸,請參閱 更新 Message 類型 部分(見下面的 更新 message 類型)。

枚舉 Enumerations

在定義 message 類型時琐旁,你可能希望其中一個字段只有一個預定義的值列表元扔。例如,假設你要為每個 SearchRequest 添加語料庫字段旋膳,其中語料庫可以是 UNIVERSAL,WEB途事,IMAGES验懊,LOCAL,NEWS尸变,PRODUCTS 或 VIDEO义图。你可以通過向 message 定義添加枚舉來簡單地執(zhí)行此操作 - 具有枚舉類型的字段只能將一組指定的常量作為其值(如果你嘗試提供不同的值,則解析器會將其視為一個未知的領域)召烂。在下面的例子中碱工,我們添加了一個名為 Corpus 的枚舉,其中包含所有可能的值,之后定義了一個類型為 Corpus 枚舉的字段:

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

你可以通過為不同的枚舉常量指定相同的值來定義別名怕篷。為此历筝,你需要將 allow_alias 選項設置為true,否則 protocol 編譯器將在找到別名時生成錯誤消息廊谓。

enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}
enum EnumNotAllowingAlias {
  UNKNOWN = 0;
  STARTED = 1;
  // RUNNING = 1;  // 取消此行注釋將導致 Google 內(nèi)部的編譯錯誤和外部的警告消息
}

枚舉器常量必須在 32 位整數(shù)范圍內(nèi)梳猪。由于 enum 值在線上使用 varint encoding ,負值效率低蒸痹,因此不推薦使用春弥。你可以在 message 中定義 enums,如上例所示的那樣叠荠∧渑妫或者將其定義在 message 外部 - 這樣這些 enum 就可以在 .proto 文件中的任何 message 定義中重用。你還可以使用一個 message 中聲明的 enum 類型作為不同 message 中字段的類型榛鼎,使用語法 MessageType.EnumType 來實現(xiàn)逃呼。

當你在使用 enum.proto 上運行 protocol buffer 編譯器時,生成的代碼將具有相應的用于 Java 或 C++ 的 enum借帘,或者用于創(chuàng)建集合的 Python 的特殊 EnumDescriptor 類蜘渣。運行時生成的類中具有整數(shù)值的符號常量。

有關如何在應用程序中使用 enums 的更多信息肺然,請參閱相關語言的 代碼生成指南

保留值

如果你通過完全刪除枚舉條目或?qū)⑵渥⑨尩魜砀旅杜e類型蔫缸,則未來用戶可能在對 message 做出自己的修改或更新時重復使用這些數(shù)值。如果以后加載相同 .proto 的舊版本际起,這可能會導致嚴重問題拾碌,包括數(shù)據(jù)損壞,隱私錯誤等街望。確保不會發(fā)生這種情況的一種方法是指定已刪除字段的字段編號(有時也需要指定名稱為保留狀態(tài)校翔,英文名稱可能會導致 JSON 序列化問題)為 “保留” 狀態(tài)。如果將來的任何用戶嘗試使用這些字段標識符灾前,protocol buffer 編譯器將會抱怨防症。你可以使用 max 關鍵字指定保留的數(shù)值范圍一直到最大值。

enum Foo {
  reserved 2, 15, 9 to 11, 40 to max;
  reserved "FOO", "BAR";
}

請注意哎甲,你不能在同一 "reserved" 語句中將字段名稱和字段編號混合在一起指定蔫敲。

使用其他 Message 類型

你可以使用其他 message 類型作為字段類型。例如炭玫,假設你希望在每個 SearchResponse 消息中包含 Result message - 為此奈嘿,你可以在同一 .proto 中定義 Result message 類型,然后在SearchResponse 中指定 Result 類型的字段:

message SearchResponse {
  repeated Result result = 1;
}

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

導入定義 Importing Definitions

在上面的示例中吞加,Result message 類型在與 SearchResponse 相同的文件中定義 - 如果要用作字段類型的 message 類型已在另一個 .proto 文件中定義裙犹,該怎么辦尽狠?

你可以通過導入來使用其他 .proto 文件中的定義。要導入另一個 .proto 的定義叶圃,可以在文件頂部添加一個 import 語句:

import "myproject/other_protos.proto";

默認情況下袄膏,你只能使用直接導入的 .proto 文件中的定義。但是盗似,有時你可能需要將 .proto 文件移動到新位置×ㄉ拢現(xiàn)在,你可以在舊位置放置一個虛擬 .proto 文件赫舒,以使用 import public 概念將所有導入轉(zhuǎn)發(fā)到新位置悍及,而不是直接移動 .proto 文件并在一次更改中更新所有調(diào)用點。導入包含 import public 語句的 proto 的任何人都可以傳遞依賴導入公共依賴項接癌。例如:

// 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";
// 你可以使用 old.proto 和 new.proto 中的定義心赶,但無法使用 other.proto

使用命令 -I/--proto_path 讓 protocol 編譯器在指定的一組目錄中搜索要導入的文件。如果沒有給出這個命令選項缺猛,它將查找調(diào)用編譯器所在的目錄缨叫。通常,你應將 --proto_path 設置為項目的根目錄荔燎,并對所有導入使用完全限定名稱耻姥。

使用 proto3 Message 類型

可以導入 proto3 message 類型并在 proto2 message 中使用它們,反之亦然有咨。但是琐簇,proto2 枚舉不能用于 proto3 語法。

嵌套類型 Nested Types

你可以在其他 message 類型中定義和使用 message 類型座享,如下例所示 - 此處結(jié)果消息在SearchResponse 消息中定義:

message SearchResponse {
  message Result {
    required string url = 1;
    optional string title = 2;
    repeated string snippets = 3;
  }
  repeated Result result = 1;
}

如果要在其父消息類型之外重用此消息類型婉商,請將其稱為 Parent.Type

message SomeOtherMessage {
  optional SearchResponse.Result result = 1;
}

你可以根據(jù)需要深入的嵌套消息:

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

Groups

請注意,此功能已棄用渣叛,在創(chuàng)建新消息類型時不應使用 - 請改用嵌套消息類型丈秩。
Groups 是在 message 定義中嵌套信息的另一種方法。例如淳衙,指定包含許多結(jié)果的SearchResponse 的另一種方法如下:

message SearchResponse {
  repeated group Result = 1 {
    required string url = 2;
    optional string title = 3;
    repeated string snippets = 4;
  }
}

group 只是將嵌套 message 類型和字段組合到單個聲明中蘑秽。在你的代碼中,你可以將此消息視為具有名為 resultResult 類型字段(前一名稱轉(zhuǎn)換為小寫箫攀,以便它不與前者沖突)肠牲。因此,此示例完全等同于上面的 SearchResponse匠童,但 message 具有不同的編碼結(jié)果。

譯者注:
再次強調(diào)塑顺,此功能已棄用汤求,這里只為盡可能保留原文內(nèi)容俏险。

更新 message 類型

如果現(xiàn)有的 message 類型不再滿足你的所有需求 - 例如,你希望 message 格式具有額外的字段 - 但你仍然希望使用舊格式創(chuàng)建代碼扬绪,請不要擔心竖独!在不破壞任何現(xiàn)有代碼的情況下更新 message 類型非常簡單。請記住以下規(guī)則:

  • 請勿更改任何現(xiàn)有字段的字段編號挤牛。
  • 你添加的任何新字段都應該是 optionalrepeated莹痢。這意味著使用“舊”消息格式的代碼序列化的任何消息都可以由新生成的代碼進行解析,因為它們不會缺少任何 required 元素墓赴。你應該為這些元素設置合理的 默認值竞膳,以便新代碼可以正確地與舊代碼生成的 message 進行交互。同樣诫硕,你的新代碼創(chuàng)建的 message 可以由舊代碼解析:舊的二進制文件在解析時只是忽略新字段坦辟。但是未丟棄這個新字段(未知字段),如果稍后序列化消息章办,則將新字段(未知字段)與其一起序列化 - 因此锉走,如果將消息傳遞給新代碼,則新字段仍然可用藕届。
  • 只要在更新的 message 類型中不再使用字段編號挪蹭,就可以刪除非必填字段。你可能希望重命名該字段休偶,可能添加前綴 "OBSOLETE_"梁厉,或者將字段編號保留(Reserved),以便將來你的 .proto 的用戶不會不小心重用這個編號椅贱。
  • 只要類型和編號保持不變懂算,非必填字段就可以轉(zhuǎn)換為擴展 extensions,反之亦然庇麦。
  • int32计技,uint32int64山橄,uint64bool 都是兼容的 - 這意味著你可以將字段從這些類型更改為另一種類型垮媒,而不會破壞向前或向后兼容性。如果從中解析出一個不符合相應類型的數(shù)字航棱,你將獲得與在 C++ 中將該數(shù)字轉(zhuǎn)換為該類型時相同的效果(例如睡雇,如果將 64 位數(shù)字作為 int32 讀取,它將被截斷為 32 位)饮醇。
  • sint32sint64 彼此兼容它抱,但與其他整數(shù)類型不兼容。
  • 只要字節(jié)是有效的 UTF-8朴艰,stringbytes 就是兼容的观蓄。
  • 如果字節(jié)包含 message 的編碼版本混移,則嵌入 message 與 bytes 兼容。
  • fixed32sfixed32 兼容侮穿,fixed64sfixed64 兼容歌径。
  • optionalrepeated 兼容。給定重復字段的序列化數(shù)據(jù)作為輸入亲茅,期望該字段為 optional 的客戶端將采用最后一個輸入值(如果它是基本類型字段)或合并所有輸入元素(如果它是 message 類型字段)回铛。
  • 更改默認值通常是正常的,只要你記住永遠不會通過網(wǎng)絡發(fā)送默認值克锣。因此茵肃,如果程序接收到未設置特定字段的消息,則程序?qū)⒖吹皆摮绦虻膮f(xié)議版本中定義的默認值娶耍。它不會看到發(fā)件人代碼中定義的默認值免姿。
  • enumint32uint32榕酒,int64uint64兼容(注意胚膊,如果它們不適合,值將被截斷)想鹰,但要注意 message 反序列化時客戶端代碼對待它們將有所不同紊婉。值得注意的是,當 message 被反序列化時辑舷,將丟棄無法識別的 enum 值喻犁,這使得字段的 has.. 訪問器返回 false 并且其 getter 返回 enum 定義中列出的第一個值,或者如果指定了一個默認值則返回默認值何缓。在 repeated 枚舉字段的情況下肢础,任何無法識別的值都將從列表中刪除。但是碌廓,整數(shù)字段將始終保留其值传轰。因此,在有可能接收超出范圍的枚舉值時谷婆,對整數(shù)升級為 enum 這一操作需要非常小心慨蛙。
  • 在當前的 Java 和 C++ 實現(xiàn)中,當刪除無法識別的 enum 值時纪挎,它們與其他未知字段一起存儲期贫。請注意,如果此數(shù)據(jù)被序列化异袄,然后由識別這些值的客戶端重新解析通砍,則會導致奇怪的行為。在 optional 可選字段的情況下烤蜕,即使在反序列化原始 message 之后寫入新值封孙,舊值仍然可以被客戶端識別垢揩。在 repeated 字段的情況下,舊值將出現(xiàn)在任何已識別和新添加的值之后敛瓷,這意味著順序?qū)⒉槐槐A簟?/li>
  • 將單個 optional 值更改為 newoneof 的成員是安全且二進制兼容的。如果你確定沒有代碼一次設置多個斑匪,則將多個 optional 字段移動到新的 oneof 中可能是安全的呐籽。但是將任何字段移動到現(xiàn)有的 oneof 是不安全的。

擴展 Extensions

通過擴展蚀瘸,你可以聲明 message 中的一系列字段編號用于第三方擴展狡蝶。擴展名是那些未由原始 .proto 文件定義的字段的占位符。這允許通過使用這些字段編號來定義部分或全部字段從而將其它 .proto 文件定義的字段添加到當前 message 定義中贮勃。我們來看一個例子:

message Foo {
  // ...
  extensions 100 to 199;
}

這表示 Foo 中的字段數(shù) [100,199] 的范圍是為擴展保留的贪惹。其他用戶現(xiàn)在可以使用指定范圍內(nèi)的字段編號在他們自己的 .proto 文件中為 Foo 添加新字段,例如:

extend Foo {
  optional int32 bar = 126;
}

這會將名為 bar 且編號為 126 的字段添加到 Foo 的原始定義中寂嘉。

譯者注:
第一段翻譯過來的語義實在是太別扭了(因為站在了被擴展字段所在的 .proto 文件的角度來看待擴展)奏瞬,實際站在擴展字段所在的 .proto 文件的角度-就是可以在自己的 .proto 文件中擴展其他人定義的另一個 .proto 中的 message。

當用戶的 Foo 消息被編碼時泉孩,其格式與用戶在 Foo 中常規(guī)定義新字段的格式完全相同硼端。但是,在應用程序代碼中訪問擴展字段的方式與訪問常規(guī)字段略有不同 - 生成的數(shù)據(jù)訪問代碼具有用于處理擴展的特殊訪問器寓搬。那么珍昨,舉個例子,下面就是如何在 C++ 中設置 bar 的值:

Foo foo;
foo.SetExtension(bar, 15);

類似地句喷,F(xiàn)oo 類定義模板化訪問器 HasExtension()镣典,ClearExtension(),GetExtension()唾琼,MutableExtension() 和 AddExtension()兄春。它們都具有與正常字段生成的訪問器相匹配的語義。有關使用擴展的更多信息父叙,請參閱所選語言的代碼生成參考神郊。

請注意,擴展可以是任何字段類型趾唱,包括 message 類型涌乳,但不能是 oneofs 或 maps。

嵌套擴展

你可以在另一種 message 類型內(nèi)部聲明擴展:

message Baz {
  extend Foo {
    optional int32 bar = 126;
  }
  ...
}

在這種情況下甜癞,訪問此擴展的 C++ 代碼為:

Foo foo;
foo.SetExtension(Baz::bar, 15);

換句話說夕晓,唯一的影響是 bar 是在 Baz 的范圍內(nèi)定義。

注意:
這是一個常見的混淆源:在一個 message 類型中聲明嵌套的擴展塊并不意味著外部類型和擴展類型之間存在任何關系悠咱。特別是蒸辆,上面的例子并不意味著 Baz 是 Foo 的任何子類征炼。這意味著符號欄是在 Baz 范圍內(nèi)聲明的;它僅僅只是一個靜態(tài)成員而已躬贡。

一種常見的模式是在擴展的字段類型范圍內(nèi)定義擴展 - 例如谆奥,這里是 Baz 類型的 Foo 擴展,其中擴展名被定義為 Baz 的一部分:

message Baz {
  extend Foo {
    optional Baz foo_ext = 127;
  }
  ...
}

譯者注:
這里比較繞拂玻,實際上就是要對某個 message A 擴展一個字段 B(B 類型)酸些,那么可以將這條擴展語句寫在 message B 的定義里。

但是檐蚜,并不是必須要在類型內(nèi)才能定義該類型的擴展字段魄懂。你也可以這樣做:

message Baz {
  ...
}

// 該定義甚至可以移到另一個文件中
extend Foo {
  optional Baz foo_baz_ext = 127;
}

實際上,這種語法可能是首選的闯第,以避免混淆市栗。如上所述,嵌套語法經(jīng)常被不熟悉擴展的用戶誤認為是子類咳短。

選擇擴展字段編號

確保兩個用戶不使用相同的字段編號向同一 message 類型添加擴展名非常重要 - 如果擴展名被意外解釋為錯誤類型填帽,則可能導致數(shù)據(jù)損壞。你可能需要考慮為項目定義擴展編號的約定以防止這種情況發(fā)生咙好。

如果你的編號約定可能涉及那些具有非常大字段編號的擴展盲赊,則可以使用 max 關鍵字指定擴展范圍至編號最大值:

message Foo {
  extensions 1000 to max;
}

最大值為 229 - 1,或者 536,870,911敷扫。

與一般選擇字段編號時一樣哀蘑,你的編號約定還需要避免 19000 到 19999 的字段編號(FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber),因為它們是為 Protocol Buffers 實現(xiàn)保留的葵第。你可以定義包含此范圍的擴展名范圍绘迁,但 protocol 編譯器不允許你使用這些編號定義實際擴展名。

Oneof

如果你的 message 包含許多可選字段卒密,并且最多只能同時設置其中一個字段缀台,則可以使用 oneof 功能強制執(zhí)行此行為并節(jié)省內(nèi)存。

Oneof 字段類似于可選字段哮奇,除了 oneof 共享內(nèi)存中的所有字段膛腐,并且最多只能同時設置一個字段。設置 oneof 的任何成員會自動清除所有其他成員鼎俘。你可以使用特殊的 case() 或 WhichOneof() 方法檢查 oneof 字段中當前是哪個值(如果有)被設置哲身,具體方法取決于你選擇的語言。

使用 Oneof

要在 .proto 中定義 oneof贸伐,請使用 oneof 關鍵字勘天,后跟你的 oneof 名稱,在本例中為 test_oneof:

message SampleMessage {
  oneof test_oneof {
     string name = 4;
     SubMessage sub_message = 9;
  }
}

然后,將 oneof 字段添加到 oneof 定義中脯丝。你可以添加任何類型的字段商膊,但不能使用 requiredoptionalrepeated 關鍵字宠进。如果需要向 oneof 添加重復字段晕拆,可以使用包含重復字段的 message。

在生成的代碼中材蹬,oneof 字段與常規(guī) optional 方法具有相同的 getter 和 setter潦匈。你還可以使用特殊方法檢查 oneof 中的值(如果有)。你可以在相關的 API 參考中找到有關所選語言的 oneof API的更多信息赚导。

Oneof 特性

  • 設置 oneof 字段將自動清除 oneof 的所有其他成員。因此赤惊,如果你設置了多個字段吼旧,則只有你設置的最后一個字段仍然具有值。

    SampleMessage message;
    message.set_name("name");
    CHECK(message.has_name());
    message.mutable_sub_message();   // Will clear name field.
    CHECK(!message.has_name());
    
  • 如果解析器遇到同一個 oneof 的多個成員未舟,則在解析的消息中僅使用看到的最后一個成員圈暗。

  • oneof 不支持擴展

  • oneof 不能使用 repeated

  • 反射 API 適用于 oneof 字段

  • 如果你使用的是 C++,請確保你的代碼不會導致內(nèi)存崩潰裕膀。以下示例代碼將崩潰员串,因為已通過調(diào)用 set_name() 方法刪除了 sub_message。

    SampleMessage message;
    SubMessage* sub_message = message.mutable_sub_message();
    message.set_name("name");      // Will delete sub_message
    sub_message->set_...            // Crashes here
    
  • 同樣在 C++中昼扛,如果你使用 Swap() 交換了兩條 oneofs 消息寸齐,則每條消息將以另一條消息的 oneof 實例結(jié)束:在下面的示例中,msg1 將具有 sub_message 而 msg2 將具有 name抄谐。

    SampleMessage msg1;
    msg1.set_name("name");
    SampleMessage msg2;
    msg2.mutable_sub_message();
    msg1.swap(&msg2);
    CHECK(msg1.has_sub_message());
    CHECK(msg2.has_name());
    

向后兼容性問題

添加或刪除其中一個字段時要小心渺鹦。如果檢查 oneof 的值返回 None/NOT_SET帕膜,則可能意味著 oneof 尚未設置或已設置為 oneof 的另一個字段弯囊。這種情況是無法區(qū)分的窒舟,因為無法知道未知字段是否是 oneof 成員衡载。

標簽重用問題

  • 將 optional 可選字段移入或移出 oneof:在序列化和解析 message 后覆糟,你可能會丟失一些信息(某些字段將被清除)枚赡。但是惑芭,你可以安全地將單個字段移動到新的 oneof 中掸绞,并且如果已知只有一個字段被設置酷窥,則可以移動多個字段咽安。
  • 刪除 oneof 字段并將其重新添加回去:在序列化和解析 message 后,這可能會清除當前設置的 oneof 字段蓬推。
  • 拆分或合并 oneof:這與移動常規(guī)的 optional 字段有類似的問題板乙。

Maps

如果要在數(shù)據(jù)定義中創(chuàng)建關聯(lián)映射,protocol buffers 提供了一種方便快捷的語法:

map<key_type, value_type> map_field = N;

...其中 key_type 可以是任何整數(shù)或字符串類型(任何標量類型除浮點類型和 bytes)。請注意募逞,枚舉不是有效的 key_type蛋铆。value_type 可以是除 map 之外的任何類型。

因此放接,舉個例子刺啦,如果要創(chuàng)建項目映射,其中每個 "Project" message 都與字符串鍵相關聯(lián)纠脾,則可以像下面這樣定義它:

map<string, Project> projects = 3;

生成的 map API 目前可用于所有 proto2 支持的語言玛瘸。你可以在相關的 API 參考 中找到有關所選語言的 map API 的更多信息。

Maps 特性

  • maps 不支持擴展
  • maps 不能是 repeated苟蹈、optional糊渊、required
  • map 值的格式排序和 map 迭代排序未定義,因此你不能依賴于特定順序的 map 項
  • 生成 .proto 的文本格式時慧脱,maps 按鍵排序渺绒。數(shù)字鍵按數(shù)字排序
  • 當解析或合并時,如果有重復的 map 鍵菱鸥,則使用最后看到的鍵宗兼。從文本格式解析 map 時,如果存在重復鍵氮采,則解析可能會失敗

向后兼容性

map 語法等效于以下內(nèi)容殷绍,因此不支持 map 的 protocol buffers 實現(xiàn)仍可處理你的數(shù)據(jù):

message MapFieldEntry {
  optional key_type key = 1;
  optional value_type value = 2;
}

repeated MapFieldEntry map_field = N;

任何支持 maps 的 protocol buffers 實現(xiàn)都必須生成和接受上述定義所能接受的數(shù)據(jù)。

Packages

你可以將 optional 可選的包說明符添加到 .proto 文件鹊漠,以防止 protocol message 類型之間的名稱沖突主到。

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

然后,你可以在定義 message 類型的字段時使用包說明符:

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

package 影響生成的代碼的方式取決于你所選擇的語言:

  • C++ 中躯概,生成的類包含在 C++ 命名空間中镰烧。例如,Open 將位于命名空間 foo::bar 中楞陷。
  • Java 中怔鳖,除非在 .proto 文件中明確提供選項 java_package,否則該包將用作 Java 包
  • Python 中固蛾,package 指令被忽略结执,因為 Python 模塊是根據(jù)它們在文件系統(tǒng)中的位置進行組織的

請注意,即使 package 指令不直接影響生成的代碼艾凯,但是例如在 Python 中献幔,仍然強烈建議指定 .proto 文件的包,否則可能導致描述符中的命名沖突并使 proto 對于其他語言不方便趾诗。

Packages 和名稱解析

protocol buffer 語言中的類型名稱解析與 C++ 類似:首先搜索最里面的范圍蜡感,然后搜索下一個范圍蹬蚁,依此類推,每個包被認為是其父包的 “內(nèi)部”郑兴。一個領先的 '.'(例如 .foo.bar.Baz)意味著從最外層的范圍開始犀斋。

protocol buffer 編譯器通過解析導入的 .proto 文件來解析所有類型名稱。每種語言的代碼生成器都知道如何使用相應的語言類型情连,即使它具有不同的范圍和規(guī)則叽粹。

定義服務

如果要將 message 類型與 RPC(遠程過程調(diào)用)系統(tǒng)一起使用,則可以在 .proto 文件中定義 RPC 服務接口却舀,protocol buffer 編譯器將使用你選擇的語言生成服務接口代碼和存根虫几。因此,例如挽拔,如果要定義一個 RPC 服務辆脸,其中具有一個獲取 SearchRequest 并返回 SearchResponse 的方法,可以在 .proto 文件中定義它螃诅,如下所示:

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

默認情況下啡氢,protocol 編譯器將生成一個名為 SearchService 的抽象接口和相應的 “存根” 實現(xiàn)。存根轉(zhuǎn)發(fā)所有對 RpcChannel 的調(diào)用州刽,而 RpcChannel 又是一個抽象接口,你必須根據(jù)自己的 RPC 系統(tǒng)自行定義浪箭。例如穗椅,你可以實現(xiàn)一個 RpcChannel,它將 message 序列化并通過 HTTP 將其發(fā)送到服務器奶栖。換句話說匹表,生成的存根提供了一個類型安全的接口,用于進行基于 protocol-buffer 的 RPC 調(diào)用宣鄙,而不會將你鎖定到任何特定的 RPC 實現(xiàn)中袍镀。所以,在 C++ 中冻晤,你可能會得到這樣的代碼:

using google::protobuf;

protobuf::RpcChannel* channel;
protobuf::RpcController* controller;
SearchService* service;
SearchRequest request;
SearchResponse response;

void DoSearch() {
  // You provide classes MyRpcChannel and MyRpcController, which implement
  // the abstract interfaces protobuf::RpcChannel and protobuf::RpcController.
  channel = new MyRpcChannel("somehost.example.com:1234");
  controller = new MyRpcController;

  // The protocol compiler generates the SearchService class based on the
  // definition given above.
  service = new SearchService::Stub(channel);

  // Set up the request.
  request.set_query("protocol buffers");

  // Execute the RPC.
  service->Search(controller, request, response, protobuf::NewCallback(&Done));
}

void Done() {
  delete service;
  delete channel;
  delete controller;
}

所有服務類還實現(xiàn)了 Service 接口苇羡,它提供了一種在編譯時不知道方法名稱或其輸入和輸出類型的情況下來調(diào)用特定方法的方法。在服務器端鼻弧,這可用于實現(xiàn)一個可以注冊服務的 RPC 服務器设江。

using google::protobuf;

class ExampleSearchService : public SearchService {
 public:
  void Search(protobuf::RpcController* controller,
              const SearchRequest* request,
              SearchResponse* response,
              protobuf::Closure* done) {
    if (request->query() == "google") {
      response->add_result()->set_url("http://www.google.com");
    } else if (request->query() == "protocol buffers") {
      response->add_result()->set_url("http://protobuf.googlecode.com");
    }
    done->Run();
  }
};

int main() {
  // You provide class MyRpcServer.  It does not have to implement any
  // particular interface; this is just an example.
  MyRpcServer server;

  protobuf::Service* service = new ExampleSearchService;
  server.ExportOnPort(1234, service);
  server.Run();

  delete service;
  return 0;
}

如果你不想插入自己現(xiàn)有的 RPC 系統(tǒng),現(xiàn)在可以使用 gRPC: 一個由谷歌開發(fā)的與語言和平臺無關的開源 RPC 系統(tǒng)攘轩。gRPC 特別適用于 protocol buffers叉存,并允許你使用特殊的 protocol buffers 編譯器插件直接從 .proto 文件生成相關的 RPC 代碼。但是度帮,由于使用 proto2 和 proto3 生成的客戶端和服務器之間存在潛在的兼容性問題歼捏,我們建議你使用 proto3 來定義 gRPC 服務。你可以在 Proto3 語言指南 中找到有關 proto3 語法的更多信息。如果你確實希望將 proto2 與 gRPC 一起使用瞳秽,則需要使用 3.0.0 或更高版本的 protocol buffers 編譯器和庫瓣履。

除了 gRPC 之外,還有許多正在進行的第三方項目拂苹,用于開發(fā) Protocol Buffers 的 RPC 實現(xiàn)。有關我們了解的項目的鏈接列表瓢棒,請參閱 第三方附加組件維基頁面

選項 Options

.proto 文件中的各個聲明可以使用許多選項進行注釋。選項不會更改聲明的整體含義跺撼,但可能會影響在特定上下文中處理它的方式歉井∑忻玻可用選項的完整列表在 google/protobuf/descriptor.proto 中定義。

一些選項是文件級選項重荠,這意味著它們應該在頂級范圍內(nèi)編寫箭阶,而不是在任何消息,枚舉或服務定義中戈鲁。一些選項是 message 消息級選項尾膊,這意味著它們應該寫在 message 消息定義中。一些選項是字段級選項荞彼,這意味著它們應該寫在字段定義中冈敛。選項也可以寫在枚舉類型、枚舉值鸣皂、服務類型和服務方法上抓谴,但是暮蹂,目前在這幾個項目上并沒有任何有用的選項。

以下是一些最常用的選項:

  • java_package(文件選項):要用于生成的 Java 類的包癌压。如果 .proto 文件中沒有給出顯式的 java_package 選項仰泻,那么默認情況下將使用 proto 包(使用 .proto 文件中的 “package” 關鍵字指定)。但是滩届,proto 包通常不能生成好的 Java 包集侯,因為 proto 包不會以反向域名開頭。如果不生成Java 代碼帜消,則此選項無效棠枉。
    option java_package = "com.example.foo";
    
  • java_outer_classname(文件選項):要生成的最外層 Java 類(以及文件名)的類名。如果 .proto 文件中沒有指定顯式的 java_outer_classname泡挺,則通過將 .proto 文件名轉(zhuǎn)換為 camel-case 來構造類名(因此 foo_bar.proto 變?yōu)?FooBar.java)辈讶。如果不生成 Java 代碼,則此選項無效娄猫。
option java_outer_classname = "Ponycopter";
  • optimize_for(文件選項):可以設置為 SPEED贱除,CODE_SIZELITE_RUNTIME。這會以下列方式影響 C++和 Java 的代碼生成器(可能還有第三方生成器):
    • SPEED(默認值):protocol buffer 編譯器將生成用于對 message 類型進行序列化媳溺,解析和執(zhí)行其他常見操作的代碼月幌。此代碼經(jīng)過高度優(yōu)化。
    • CODE_SIZE:protocol buffer 編譯器將生成最少的類悬蔽,并依賴于基于反射的共享代碼來實現(xiàn)序列化扯躺,解析和各種其他操作。因此屯阀,生成的代碼將比使用 SPEED 小得多缅帘,但操作會更慢轴术。類仍將實現(xiàn)與 SPEED 模式完全相同的公共 API难衰。此模式在包含大量 .proto 文件的應用程序中最有用,并且不需要所有這些文件都非扯涸裕快盖袭。
    • LITE_RUNTIME:protocol buffer 編譯器將生成僅依賴于 “l(fā)ite” 運行時庫(libprotobuf-lite 而不是libprotobuf)的類。精簡版運行時比整個庫小得多(大約小一個數(shù)量級)彼宠,但省略了描述符和反射等特定功能鳄虱。這對于在移動電話等受限平臺上運行的應用程序尤其有用。編譯器仍將生成所有方法的快速實現(xiàn)凭峡,就像在 SPEED 模式下一樣拙已。生成的類將僅實現(xiàn)每種語言的 MessageLite 接口,該接口僅提供完整 Message 接口的方法的子集摧冀。
    option optimize_for = CODE_SIZE;
    
  • cc_generic_services倍踪,java_generic_services系宫,py_generic_services(文件選項):protocol buffer 編譯器應根據(jù)服務定義判斷是否生成 C++,Java 和 Python 抽象服務代碼建车。由于遺留原因扩借,這些默認為 “true”。但是缤至,從版本 2.3.0(2010年1月)開始潮罪,RPC 實現(xiàn)最好提供 代碼生成器插件 生成更具體到每個系統(tǒng)的代碼,而不是依賴于 “抽象” 服務领斥。
// This file relies on plugins to generate service code.
option cc_generic_services = false;
option java_generic_services = false;
option py_generic_services = false;
  • cc_enable_arenas(文件選項):為 C++ 生成的代碼啟用 arena allocation
  • message_set_wire_format(消息選項):如果設置為 true嫉到,則消息使用不同的二進制格式,旨在與 Google 內(nèi)部使用的舊格式兼容戒突,即 MessageSet屯碴。Google 以外的用戶可能永遠不需要使用此選項。必須嚴格按如下方式聲明消息:
message Foo {
  option message_set_wire_format = true;
  extensions 4 to max;
}
  • packed(字段選項):如果在基本數(shù)字類型的重復字段上設置為 'true`膊存,則一個更緊湊的編碼 被使用导而。使用此選項沒有任何缺點。但請注意隔崎,在版本 2.3.0 之前今艺,在不期望的情況下接收打包數(shù)據(jù)的解析器將忽略它。因此爵卒,在不破壞兼容性的情況下虚缎,無法將現(xiàn)有字段更改為打包格式。在 2.3.0 及更高版本中钓株,此更改是安全的实牡,因為可打包字段的解析器將始終接受這兩種格式,但如果你必須使用舊的 protobuf 版本處理舊程序轴合,請務必小心创坞。
    repeated int32 samples = 4 [packed=true];
    
  • deprecated(field option):如果設置為 true,表示該字段已棄用受葛,新代碼不應使用該字段题涨。在大多數(shù)語言中,這沒有實際效果总滩。在 Java 中纲堵,這變成了 @Deprecated 注釋。將來闰渔,其他特定于語言的代碼生成器可能會在字段的訪問器上生成棄用注釋席函,這將導致在編譯嘗試使用該字段的代碼時發(fā)出警告。如果任何人都未使用該字段冈涧,并且你希望阻止新用戶使用該字段茂附,請考慮使用 reserved 替換字段聲明蝌以。
    optional int32 old_field = 6 [deprecated=true];
    

自定義選項

Protocol Buffers 甚至允許你定義和使用自己的選項。請注意何之,這是 高級功能跟畅,大多數(shù)人不需要。由于選項是由 google/protobuf/descriptor.proto(如 FileOptionsFieldOptions)中定義的消息定義的溶推,因此定義你自己的選項只需要擴展這些消息徊件。例如:

import "google/protobuf/descriptor.proto";

extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}

message MyMessage {
  option (my_option) = "Hello world!";
}

這里我們通過擴展 MessageOptions 定義了一個新的 message 級選項。然后蒜危,當我們使用該選項時虱痕,必須將選項名稱括在括號中以指示它是擴展名。我們現(xiàn)在可以在 C++ 中讀取 my_option 的值辐赞,如下所示:

string value = MyMessage::descriptor()->options().GetExtension(my_option);

這里部翘,MyMessage::descriptor()->options() 返回 MyMessageMessageOptions protocol message。從中讀取自定義選項就像閱讀任何其他擴展响委。

同樣新思,在 Java 中我們會寫:

String value = MyProtoFile.MyMessage.getDescriptor().getOptions().getExtension(MyProtoFile.myOption);

在 Python 中它將是:

value = my_proto_file_pb2.MyMessage.DESCRIPTOR.GetOptions()
  .Extensions[my_proto_file_pb2.my_option]

可以在 Protocol Buffers 語言中為每種結(jié)構自定義選項。這是一個使用各種選項的示例:

import "google/protobuf/descriptor.proto";

extend google.protobuf.FileOptions {
  optional string my_file_option = 50000;
}
extend google.protobuf.MessageOptions {
  optional int32 my_message_option = 50001;
}
extend google.protobuf.FieldOptions {
  optional float my_field_option = 50002;
}
extend google.protobuf.EnumOptions {
  optional bool my_enum_option = 50003;
}
extend google.protobuf.EnumValueOptions {
  optional uint32 my_enum_value_option = 50004;
}
extend google.protobuf.ServiceOptions {
  optional MyEnum my_service_option = 50005;
}
extend google.protobuf.MethodOptions {
  optional MyMessage my_method_option = 50006;
}

option (my_file_option) = "Hello world!";

message MyMessage {
  option (my_message_option) = 1234;

  optional int32 foo = 1 [(my_field_option) = 4.5];
  optional string bar = 2;
}

enum MyEnum {
  option (my_enum_option) = true;

  FOO = 1 [(my_enum_value_option) = 321];
  BAR = 2;
}

message RequestType {}
message ResponseType {}

service MyService {
  option (my_service_option) = FOO;

  rpc MyMethod(RequestType) returns(ResponseType) {
    // Note:  my_method_option has type MyMessage.  We can set each field
    //   within it using a separate "option" line.
    option (my_method_option).foo = 567;
    option (my_method_option).bar = "Some string";
  }
}

請注意赘风,如果要在除定義它之外的包中使用自定義選項夹囚,則必須在選項名稱前加上包名稱,就像對類型名稱一樣邀窃。例如:

// foo.proto
import "google/protobuf/descriptor.proto";
package foo;
extend google.protobuf.MessageOptions {
  optional string my_option = 51234;
}
// bar.proto
import "foo.proto";
package bar;
message MyMessage {
  option (foo.my_option) = "Hello world!";
}

最后一件事:由于自定義選項是擴展名荸哟,因此必須為其分配字段編號,就像任何其他字段或擴展名一樣瞬捕。在上面的示例中鞍历,我們使用了 50000-99999 范圍內(nèi)的字段編號。此范圍保留供個別組織內(nèi)部使用肪虎,因此你可以自由使用此范圍內(nèi)的數(shù)字用于內(nèi)部應用程序劣砍。但是,如果你打算在公共應用程序中使用自定義選項笋轨,則務必確保你的字段編號是全局唯一的秆剪。要獲取全球唯一的字段編號赊淑,請發(fā)送請求以向 protobuf全球擴展注冊表 添加條目爵政。通常你只需要一個擴展號。你可以通過將多個選項放在子消息中來實現(xiàn)一個擴展號聲明多個選項:

message FooOptions {
  optional int32 opt1 = 1;
  optional string opt2 = 2;
}

extend google.protobuf.FieldOptions {
  optional FooOptions foo_options = 1234;
}

// usage:
message Bar {
  optional int32 a = 1 [(foo_options).opt1 = 123, (foo_options).opt2 = "baz"];
  // alternative aggregate syntax (uses TextFormat):
  optional int32 b = 2 [(foo_options) = { opt1: 123 opt2: "baz" }];
}

另請注意陶缺,每種選項類型(文件級別钾挟,消息級別,字段級別等)都有自己的數(shù)字空間饱岸,例如掺出,你可以使用相同的數(shù)字聲明 FieldOptions 和 MessageOptions 的擴展名徽千。

生成你的類

要生成 Java,Python 或 C++代碼汤锨,你需要使用 .proto 文件中定義的 message 類型双抽,你需要在 .proto 上運行 protocol buffer 編譯器 protoc。如果尚未安裝編譯器闲礼,請 下載軟件包 并按照 README 文件中的說明進行操作牍汹。

Protocol 編譯器的調(diào)用如下:

protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
  • IMPORT_PATH 指定在解析導入指令時查找 .proto 文件的目錄。如果省略柬泽,則使用當前目錄慎菲。可以通過多次傳遞 --proto_path 選項來指定多個導入目錄锨并;他們將按順序搜索露该。-I = IMPORT_PATH 可以用作 --proto_path 的縮寫形式。
  • 你可以提供一個或多個輸出指令:
    • --cpp_outDST_DIR 中生成 C++ 代碼第煮。有關詳細信息解幼,請參閱 C++ 生成的代碼參考
    • --java_outDST_DIR中生成 Java 代碼包警。有關更多信息书幕,請參閱 Java 生成的代碼參考
    • --python_outDST_DIR 中生成 Python 代碼揽趾。有關更多信息台汇,請參閱 Python 生成的代碼
      為了方便起見篱瞎,如果 DST_DIR 以 .zip 或 .jar 結(jié)尾苟呐,編譯器會將輸出寫入到具有給定名稱的單個 ZIP 格式的存檔文件。.jar 輸出還將根據(jù) Java JAR 規(guī)范的要求提供清單文件俐筋。請注意牵素,如果輸出存檔已存在,則會被覆蓋澄者;編譯器不夠智能笆呆,無法將文件添加到現(xiàn)有存檔中。
  • 你必須提供一個或多個 .proto 文件作為輸入粱挡≡唬可以一次指定多個 .proto 文件。雖然文件是相對于當前目錄命名的询筏,但每個文件必須駐留在其中一個 IMPORT_PATH 中榕堰,以便編譯器可以確定其規(guī)范名稱。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嫌套,一起剝皮案震驚了整個濱河市逆屡,隨后出現(xiàn)的幾起案子圾旨,更是在濱河造成了極大的恐慌,老刑警劉巖魏蔗,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砍的,死亡現(xiàn)場離奇詭異,居然都是意外死亡莺治,警方通過查閱死者的電腦和手機挨约,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來产雹,“玉大人诫惭,你說我怎么就攤上這事÷冢” “怎么了夕土?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瘟判。 經(jīng)常有香客問我怨绣,道長,這世上最難降的妖魔是什么拷获? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任篮撑,我火速辦了婚禮,結(jié)果婚禮上匆瓜,老公的妹妹穿的比我還像新娘赢笨。我一直安慰自己,他們只是感情好驮吱,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布茧妒。 她就那樣靜靜地躺著,像睡著了一般左冬。 火紅的嫁衣襯著肌膚如雪桐筏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天拇砰,我揣著相機與錄音梅忌,去河邊找鬼。 笑死除破,一個胖子當著我的面吹牛牧氮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播皂岔,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蹋笼,長吁一口氣:“原來是場噩夢啊……” “哼展姐!你這毒婦竟也來了躁垛?” 一聲冷哼從身側(cè)響起剖毯,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎教馆,沒想到半個月后逊谋,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡土铺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年胶滋,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悲敷。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡究恤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出后德,到底是詐尸還是另有隱情部宿,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布瓢湃,位于F島的核電站理张,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏绵患。R本人自食惡果不足惜雾叭,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望落蝙。 院中可真熱鬧织狐,春花似錦、人聲如沸筏勒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奏寨。三九已至起意,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間病瞳,已是汗流浹背揽咕。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留套菜,地道東北人亲善。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像逗柴,于是被迫代替她去往敵國和親蛹头。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

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