protobuf 3 版本文檔翻譯
- protobuf 語法 -(proto3) =========================
本文描述瞭如何使用 prototol buffer(簡稱 pb)語言來構建你的 pb 數據,內容包括:後綴爲. proto 的文件語法(語法:syntax)以及如何從. proto 文件生成自己語言的數據訪問類(數據訪問類:data access class)。本文檔中使用 pb 的 proto3 版本,proto2 版本的 pb 語法看 Proto2 Language Guide。
https://developers.google.com/protocol-buffers/docs/proto
Proto 2 language guide
本文是一個參考指南,用來一步一步展示文檔中的各種特性。
1.1. 定義一個 Message
首先,看一個非常簡單的示例。假如你想定義一個搜索請求(搜索請求:search request)的 message,那麼就需要有 query 的 string 字段、你感興趣的結果所在的數據頁的頁碼 page_number 以及每頁上的結果的數量 result_per_page。
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
-
文件的第一行聲明標識你使用 proto3 語法:如果你不寫這句聲明,pb 編譯器會假設你使用的 proto2。它必須是. proto 文件中第一個非空、非註釋的語句。
-
SearchRequest 類型中聲明瞭三個字段,每個字段都是鍵值對,都是用來在 SearchRequest 中儲存對應信息的。每個字段都有一個名字和類型。
1.1.1. 聲明字段類型
上面的代碼中,所有的字段都是 scale 類型:兩個 integer 類型(page_number 和 result_per_page)以及一個 string 類型(query)。然而,你也可以把字段定義成複合類型(複合類型:composite type),包括枚舉類型(枚舉類型:enumerations)和其他的 message 類型。
1.1.2. 分配字段編號
如你所見,message 定義中的每個字段都有一個唯一編號。這些編號用來在 message 編碼後的二進制數據中來區分各個字段,一旦你的 message 開始使用就不可以改變其字段的編號。1-15 的字段號使用一個字節進行編碼,內容包括字段號和字段類型(在 Protocol Buffer Encoding 這一章中可以找到更多編碼信息。16-2047 的字段號佔用兩個字節進行編碼。因此,你應該把 1-15 保留爲經常使用的字段編號。記得要保留幾個 1-15 的編號,以便爲後續添加的頻繁使用的字段進行編號。
https://developers.google.com/protocol-buffers/docs/encoding#structure
Protocol buffer encoding
可以使用的最小字段號是 1,最大是 或 536870911. 你不可以使用 19000 到 19999 作爲字段號,因爲它們是爲 Protocol Buffer 的實現而保留的,如果你在. proto 文件中用了某一個保留的字段號,pb 的編譯器就會報錯。同樣的,你也不可以使用之前使用 reserved 標記的保留字段號。
1.1.3. 指定字段規則
message 的字段可以是下面的一種:
-
singular 規則:一個 message 可以有零個或者一個 singular 字段(但是不會超過一個)。這也是 proto3 語法的默認字段規則。
-
repeated 規則:使用 repeated 修飾的字段,可以重複任何次(包括零次)。(其實就是一個數組)。repeated 數組中元素的順序是固定的。
在 proto3 中,repeated 修飾的字段默認使用 packed 編碼。
在 Protocol Buffer Encoding 中可以找到更多關於 packed 編碼的信息。
https://developers.google.com/protocol-buffers/docs/encoding#packed
Protocol buffer encoding
1.1.4. 添加更多的 message
在一個. proto 文件中可以定義多個 message。如果你想定義多個有聯繫的 message,這個特性是很有用的。例如,你想定義 SearchResponse 作爲你的響應 message,你就可以把它加到相同的. proto 文件中。
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
1.1.5. 添加註釋
在. proto 文件中,使用 C/C++ 風格的註釋語法:// 或 /.../
/* 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.
}
1.1.6. 保留字段
如果你通過刪除或註釋字段修改了一個 message,之後的開發者在修改這個 message 的時候可能會重新使用這個字段號。如果他們之後加載舊版本的. proto 文件,可能導致嚴重的問題,例如數據污染、權限漏洞等。避免這個問題的方法就是指定你刪除的字段號(和字段名,因爲字段名重複使用也可能導致 JSON 序列化的問題)是 reserved。後續的開發者如果想使用這些字段標識,pb 編譯器都會報錯。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
注意,在同一個 reserved 語句中不能同時聲明字段號和字段名。
1.1.7. 從你的. proto 文件生成什麼東西
當你在. proto 文件上運行 pb 編譯器,編譯器以所選語言生成代碼。你使用這種語言來操作這些 message 類型,包括獲取和設置字段值,將 message 序列化爲輸出流,以及從輸入流解析 message。
-
對於 C ++,編譯器從每個. proto 生成一個. h 和. ccc 文件,以及文件中描述的每條 message 的類。
-
對於 Java,編譯器爲每個 message 的都生成一個類並寫在. java 文件,以及用於創建 message 類實例的特殊 Builder 類。
-
對於 Kotlin,除了 Java 生成的代碼之外,編譯器還爲每個 message 生成一個. kt 文件,其中包含可用於簡化創建消息實例的 DSL。
-
Python 有點不同 - Python 編譯器爲. proto 中的每一個 message 生成一個具有靜態描述符的模塊,然後與 metaclass 一起使用,以在運行時創建必要的 python 數據訪問類。
-
對於 Go,編譯器在. pb.go 文件中爲每一個 message 生成對應的類型。
-
對於 Ruby,編譯器生成一個. rb 文件,內容是包括每個 message 的 Ruby 模塊。
-
...
文檔後續有相關語言的 API,具體可見 API reference
https://developers.google.com/protocol-buffers/docs/reference/overview
API reference
1.2. Scalar 值類型
一個 message 的字段可以是以下的類型 - 這個表中顯示了. proto 文件中可以使用的類型以及在不同語言中生成對應類型。
在 Protocol Buffer Encoding 中你可以看到這些類型在序列化時是如何編碼的。
https://developers.google.com/protocol-buffers/docs/encoding
Protocol buffer encoding
1.3. 默認值
解析編碼後的 message 時,如果 message 有個 singular 類型的字段沒有指定值,那麼解析出來的相關字段都會設置爲默認值。
-
string:默認值是空字符串
-
bytes:默認值是空 byte 數組
-
bool:默認是 false
-
數字:默認是 0
-
enum:默認值是定義的第一個枚舉值,肯定是 0
-
message:它的默認值取決於語言的實現。
repeated 字段的默認值爲空(通常是一個空數組)。
注意:message 的字段中,一旦一個 message 被解析出來,我們就不知道它是被設置爲默認值(例如有個 bool 被設置爲 false)或者它並沒有被設置;你在定義 message 類型的時候需要注意這一點。例如,不要定義一個在某些情況需要設置爲 false 的 bool 類型。同時注意,如果一個 message 字段設置爲默認值,它的值在傳輸時就不會被序列化。
在 generated code guide 可以查看你使用的語言如何生成代碼。
1.4. Enumerations
當你定義一個 message,可能需要某一個字段的值在一個預定義的列表中。例如,你想要爲 SearchRequest 添加一個 corpus 字段,corpus 有這幾種值:UNIVERSAL、WEB、IMAGES、LOCAL、NEWS、PRODUCES 或 VIDEO。通過在你的 message 定義處添加一個具有多個常量的枚舉類型就可以實現這個功能。
下面代碼中,我們添加了一個 Corpus 的枚舉類型,這個類型具有上面提到的七種值;同時在 message 中添加了一個類型爲 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 的常量作爲它的第一個成員。這是因爲:
-
必須存在一個零值,因爲需要使用 0 作爲默認值
-
零值需要是第一個元素,這是爲了與 proto2 語義兼容,proto2 中第一個枚舉值始終是默認值
你可以通過給不同的枚舉變量賦予相同的值來定義別名(別名:alias)。前提是你需要設置 allow_alias 選項爲 true,否則 pb 編譯器會報 error 異常。
message MyMessage1 {
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
}
message MyMessage2 {
enum EnumNotAllowingAlias {
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}
}
枚舉變量必須是 32 位的 integer。因爲枚舉使用 varint encoding 進行編碼,負數在編碼時的效率很低因此是不推薦使用的。如上面代碼所示,你可以在 message 內部定義枚舉,也可以在 message 外部定義。你也可以在把一個 message 的枚舉作爲另一個 message 中的字段,使用這個語法:MessageType.EnumType。
https://developers.google.com/protocol-buffers/docs/encoding
varint encoding
當使用 pb 編譯器編譯. proto 文件中的枚舉類型時,生成的代碼中將具有對應的 Java、Kotlin 或 C++ 枚舉,或用於 Python 的特殊 EnumDescriptor 類,用於在運行時生成的類中創建一組具有整數值的符號常量。
注意: 生成的代碼可能會受到特定語言的枚舉數限制(一種語言低至數千)。請查看你使用的語言的限制。
反序列化時,message 中會保留未識別出來的枚舉值,具體的表現形式取決於語言的實現。在 C++ 和 golang 中,枚舉類型的值可以在其指定範圍之外,因此未識別出來的值會簡單存儲爲一個 integer。在 Java 等的枚舉類型中,枚舉中專門有一種情況用來標識未識別的值,它可以通過特殊的訪問機制來獲取具體內容。在其他情況下,如果 message 被序列化了,這個無法識別的值也會隨 message 序列化。
1.4.1. 保留值
如果你通過刪除或註釋掉枚舉的一個條目來更新這個枚舉類型,未來的開發者可能會重新使用這個枚舉值來實現他們對這個枚舉類型的修改。如果他們在之後使用了舊版本的. proto 文件會導致嚴重問題,例如數據污染,權限漏洞等。保證這個不再發生的方法就是枚舉條目的值和名字都聲明爲 reserved。如果未來開發者使用這些條目名或者值,pb 編譯器就會報錯。你可以使用 max 關鍵字來聲明你保留的條目值的範圍一直到最大值。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
注意:不能在同一行 reserved 語句中同時添加枚舉成員的名和值。
1.5. 使用其他 message 類型
你可以使用其他的 message 作爲當前 message 字段的類型。例如,你想在 SearchResponse 中包括 Result,你可以在當前. proto 文件中定義一個 Result,然後在 SearchResponse 中聲明一個 Result 字段。
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
1.5.1. 導入已定義的數據類型
本節的特性不適合 Java。
在上面的例子中,Result 和 SearchResponse 定義在同一個. proto 文件中,如果你想使用的 message 已經定義在另一個. proto 文件中呢?
你可以通過在當前. proto 文件中 import 其他的. proto 文件以使用已有的類型,例如,在文件的開頭可以這麼寫:
import "myproject/other_protos.proto";
這樣,你只能使用 other_protos.proto 中的類型定義。然而,你可能需要把一個. proto 文件移動到一個新的位置。你可以把. proto 文件移動到新的位置並在所有 import 它的. proto 文件中更新其路徑;但是還有更簡單的方法,就是在原來的路徑上放一個假的. proto 文件,使用 import public 來將 import 重定向到新的. proto 文件中。任何導入具有 import public 聲明的. proto 文件的. proto 文件,可以進行依賴的傳遞。(這麼說有點不明白,看下面的代碼。new.proto 就是原來的. proto 文件的新名字,而在. proto 文件的原來位置即 old.proto 中使用了 import public 聲明。這樣,當 client.proto 中 import 了 old.proto 文件,client.proto 也可以使用 new.proto 的定義。從另一個角度說,old.proto 繼承了 new.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";
// You use definitions from old.proto and new.proto, but not other.proto
pb 編譯器通過 - I/--proto_path 標誌獲取的路徑下查找導入的. proto 文件。如果沒有這個標誌的參數,它會在運行編譯器的當前路徑下查找。通常情況下,你應該將 --proto_path 標誌設置爲項目的根目錄,併爲所有的需要導入的. proto 文件提供完全的路徑名。
1.5.2. 使用 proto2 的 message 類型
在 proto3 的 message 中可以導入使用 proto2 的 message,反之亦然。然而,proto2 的枚舉無法直接在 proto3 語法中使用,除非一個 ptoro2 的 message 使用了它們。
1.6. 嵌套類型
你可以在一個 message 定義內部再定義或使用另一個 message,如下代碼所示。這裏,這個 Result 就是定義在 SearchResponse 內部。
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
如果你想在與 SearchResponse 同級的 message 中使用 Result,就需要使用 SearchResponse.Result 來進行定義。
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
在 message 中你想嵌套多少就能嵌套多少層 message。
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;
}
}
}
1.7. 更新一個 message
如果已有的 message 已經無法滿足你的需求了,例如,你需要這個 message 增加一個額外的字段,但是你還需要使用原來的 message 來開發。不要怕,更新你的 message 是很簡單的,而且不會破壞你原有的代碼。只要記住以下幾條規則:
-
不要改變已有字段的字段號
-
如果你新增一個字段,新代碼也可以解析你的舊 message 序列化後的內容。你應該記住這些字段的默認值,以便新代碼與舊代碼生成的 message 能夠正確交互。同樣,由你的新代碼創建的消息可以由舊代碼解析:舊代碼在解析時會忽略新的字段。具體內容見未知字段的具體內容。
-
字段可以被刪除,但是它的字段號在更新的 message 不可以使用。你可能想重命名這些字段,例如在字段名開頭加 OBSULETE,或者讓字段編號保留(保留:reserved),所以未來的開發者不會使用這些字段號。
-
int32,uint32,int64,uint64 和 bool 是互相兼容的,這意味着你可以把一個這些類型的字段任意轉換成另一種類型。如果從線上獲取的一個數字無法滿足這個字段的類型,你得到的結果會和 C++ 中把這個數字轉換成那個字段類型的結果一致。
-
sint32 和 sint64 是可以互相轉換的,但是無法與其他的 integer 類型相互轉換。
-
string 和 bytes 是相互轉換的,前提是 bytes 是 utf-8 編碼。
-
嵌套的 message 與 bytes 是互相轉換的,前提是 bytes 中包含這個 message 的編碼版本。
-
fixed32 與 sfixed32 可以互相轉換,fixed64 和 sfixed64 互相轉換。
-
對於 string 和 bytes 以及 message 的字段來說,optional 和 repeated 聲明是可以相互轉換的。假設現在有 repeated 的字段被序列化後作爲輸入,對於想要 optional 字段的客戶端來說,如果字段是原始數據類型,它就會只取最後一個值;然而如果它是一個 message 類型,則會合並所有的輸入值。注意,這種方式對於 bool、枚舉等類型並不安全。repeated 字段和 bool、枚舉等類型使用 packed 格式進行序列化,但是它們不會正確的解析到 optional 字段中。
-
枚舉和 int32、uint32、int64、uint64 是可相互轉換的,注意它們轉換時可能存在截斷的情況。然而,客戶端在反序列化 message 時可能會出現不同的情況:例如,一個未識別的 proto3 枚舉類型儲存在 message 中,但是 message 在反序列化時這個類型的表現形式取決於具體語言實現。
-
把一個字段值轉換爲新的 oneof 的一個成員這種操作是安全的,同時是可以互相轉換的。把多個字段移動到一個新的 oneof 中可能是安全的,前提是你確定沒有其他的代碼同時設置它們中的多個字段值。把任何的字段移動到現有的 oneof 中是不安全的。
1.8. 未知的字段
未知字段(未知字段:unknown field)是序列化的數據中,pb 解析器無法識別的內容。例如,當使用基於舊. proto 文件的解析器解析具有新增字段的數據時,這些新字段就是未知字段。
起初,proto3 的 message 在解析時會丟棄未知字段,但是 3.5 版本我們重新引入了對未知字段的保留以兼容 proto2。在 3.5 或更高版本中,未知字段在解析過程中會保留並且包含在序列化的輸出中。
1.9. Any 類型
Any 類型可以讓你在不知道 message 定義的情況下把一個 message 作爲字段的類型。一個 Any 類型中包含任何類型 message 序列化後的數據,同時還有一個 URL 作爲標識符並且用於解析該數據的類型。使用 Any,需要導入 google/protobuf/any.proto。
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
對於給定 message 類型的 URL 是:type.googleapis.com/packagename.messagename。
不同的語言實現將支持 runtime library helper 以類型安全的方式打包和解包 Any 的值。例如,在 Java 中,Any 類型會使用特殊的 pack() 和 unpack() 組件,而在 C++ 中使用 PackFrom() 和 UnpackTo() 方法。
// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
目前,關於 Any 的 runtime libraries 還在開發中。
如果你已經熟悉了 proto2 的語法,Any 可以持有任意 proto3 的 message 與 proto2 的 message 可以允許 extension 是相似的。
1.10. Oneof
如果你在一個 message 中有很多個字段,但是同時最多隻有一個字段被設置,你可以通過 oneof 來解決這個問題同時節省內存。
oneof 與普通的字段相似,但是 oneof 中的所有字段共享一塊內存,並且最多隻有一個字段可以被設置。設置 oneof 中的任意字段都會清除其他的字段。你可以使用 case() 或者 WhichOneof() 方法來判斷哪個字段被設置,具體實現依賴於具體的語言。
1.10.1. 使用 Oneof
使用 oneof 關鍵字後面跟 oneof 名來定義一個 oneof 類型。
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
接下來可以向 oneof 定義中添加 oneof 字段。除了 map 和 repeated 字段,其他類型都可以作爲 oneof 的字段。
在生成的代碼中,oneof 字段與普通的字段都有 getter 和 setter 方法。你也會有一個特殊的方法來檢查 oneof 中哪個字段被設置了。具體查看 API 文檔。
https://developers.google.com/protocol-buffers/docs/reference/overview
API 文檔
1.10.2. 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 的多個字段,那麼解析 message 的時候只使用遇到的最後一個字段。
-
oneof 不能被 repeated 修飾。
-
oneof 字段使用反射的 API。
-
如果你設置了 oneof 某一個字段爲默認值(例如設置一個 int32 字段爲 0),這個字段的 "case" 就會被設置,同時它的值在傳輸時會被序列化。
-
如果你使用 C++,請確保你的代碼不會導致內存崩潰。下面的代碼會有這個問題因爲 sub_message 已經通過 set_name() 方法刪除掉了。
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name"); // Will delete sub_message
sub_message->set_... // Crashes here
- 還是在 C++ 中,如果你 Swap() 兩個具有 oneof 的 message,每個 message 最後都會有對方的 oneof 字段:下面的代碼中,msg1 會有 sub_message,msg2 會有 name。
1.10.3. 向後兼容性問題
當刪除或增加 oneof 的字段時需要小心。如果檢查一個 oneof 字段返回 None 或者 NOT_SET,這說明這個字段沒有被設置或者在 oneof 的之前某一個版本中是被設置的。這是很難區分的,因爲無法知道傳輸過來的一個未知字段是不是 oneof 的成員。
1.10.3.1. 標籤重用問題
-
從一個 oneof 移出或移入字段:message 序列化和解析後可能會丟失一部分信息,因爲有的字段會被清楚掉。然而,單個字段是可以安全的添加到一個新的 oneof 中。多個字段的話,如果這些字段同時最多隻有一個字段被設置,那麼也是可以添加到 oneof 中的。
-
刪除一個 oneof 中的字段然後添加回去:當 message 被序列化和解析後,可能會清除掉當前設置的字段。
-
拆分或者合併 oneof:這與移動字段產生的問題相同。
1.11. 字典(字典:map)
如果你想使用字典,pb 提供了一個語法:
map<key_type, value_type> map_field = N;
其中,key_type 可以是數字或者 string 類型(可以是任何的 scalar 類型,除了浮點類型和 bytes)。注意,枚舉不是一個有效的 key_type。value_type 可以是除了 map 以外的任何的類型。
例如,你想創建一個 map,其中每個 Project 都和一個 string 相關聯:
map<string, Project> projects = 3;
-
map 作爲字段時不能用 repeated 修飾。即沒有 []map。
-
map 在傳輸前後,其元素的順序不是固定的。因此你不能指望你的 map 元素在傳輸後也按照固定的順序進行遍歷。
-
當爲. proto 文件生成代碼時,map 會根據 key 進行排序。數字 key 會按照數字順序排序。
-
當解析傳輸的數據或者合併 map 時,如果有重複的 key,map 只會使用最後一個遇到的 key。當從文件中解析 map 時,如果遇到了重複的 key 可能會導致解析失敗。
-
如果你提供了一個沒有 value 的 key 作爲 map 的字段,這個字段在序列化時的行爲取決於語言實現。在 C++、Java、Kotlin 和 Python 中,這個類型的默認值是會被序列化的,但是其他的語言中不會序列化這個值。
map 的 API 見 API 文檔。
https://developers.google.com/protocol-buffers/docs/reference/overview
API 文檔
1.11.1 向後兼容
map 在傳輸時等效於下面的代碼,因此不支持 map 的 pb 實現仍然可以解析 map 數據。
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
任意支持 map 的 pb 實現在構造和接收的數據時都必須與上述代碼中定義生成的數據相同。
1.12. package
在一個. proto 文件中,你可以添加一個可選的 package 標識來防止 message 重名。
package foo.bar;
message Open { ... }
在你自己的 message 中定義字段時可以使用上述的包定義:
message Foo {
...
foo.bar.Open open = 1;
...
}
package 標識符對後續生成的代碼的影響取決於你使用的語言:
-
在 C++ 中,生成的類都在 C++ 的同一個命名空間下。例如,Open 會在 foo::bar 裏面。
-
在 Java 和 Kotlin 中,package 默認用作 java 包,除非你顯式提供. proto 文件中的選項 java_package。
-
在 Python 中,忽略 package 指令,因爲 Python 模塊根據文件系統中的位置組織。
-
在 Go 中,package 默認用作 Go 包名,除非你顯式提供. proto 文件中的選項 go_package。
-
在 Ruby 中,生成的類被包裝在嵌套的 Ruby 命名空間內,轉換爲所需的 Ruby 大寫樣式(首字母大寫; 如果第一個字符不是字母,則使用 PB_)。例如,Open 將在 Foo :: Bar 中。
-
在 C#中,package 默認被用作轉換爲 PascalCase 之後的命名空間,除非你在. proto 文件中明確提供選項 csharp_namespace。例如,Open 將在命名空間 Foo.Bar 中。
1.12.1. package 和名稱解析
在 pb 語言中類型名稱的解析工作和 C++ 一樣:首先搜索最內層的範圍,然後是下一個最內層,依此類推,每個包都比它的父包更靠內。一個前置的'.'(例如,.foo.bar.baz)意味着從最外面的包開始。
pb 編譯器通過解析導入的. proto 文件來解析所有類型名稱。每種語言的代碼生成器都知道如何引用該語言的每種類型,即使它具有不同的範圍規則。
1.13. 定義 service
如果你想在 RPC 系統中使用你定義的 message,你可以在. proto 文件中定義一個 RPC 服務接口,pb 編譯器會根據你使用的語言生成一個服務接口代碼與樁代碼(樁代碼:stub)。例如,如果你想定義一個 RPC 服務,裏面有一個方法,輸入 SearchRequest,輸出 SearchResponse,你可以在. proto 文件中這麼寫:
service SearchService {
rpc Search(SearchRequest) returns (SearchResponse);
}
最直接了當使用 pb 的 RPC 系統是 gRPC:Google 實現的語言和平臺中立性的開源 RPC 系統。gRPC 特別適用於 pb,並且通過一個特殊的 pb 編譯器插件讓你直接從. proto 文件中生成 RPC 代碼。
如果你不想使用 gRPC,也可以在你自己的 RPC 實現上使用 pb。具體看 Proto2 Language Guide。
https://developers.google.com/protocol-buffers/docs/proto#services
Proto2 language guide
目前還有很多第三方工程在開發適用於 pb 的 RPC 實現。具體見 third-party add-on wiki page。
https://github.com/protocolbuffers/protobuf/blob/master/docs/third_party.md
third-party add-on wiki page
1.14. JSON 映射
proto3 支持 JSON 的規範編碼,使系統之間的數據傳輸更加容易。編碼方式在下表中。
如果 JSON 數據中一個值丟失或者是 null,轉換成 pb 時會使用該類型默認值。如果 pb 中一個字段是默認值,轉換成 JSON 時就會被省略掉。有的實現可能會提供選項,從而可以在 JSON 中保留具有默認值的字段。
1.14.1. JSON 選項
一個 proto3 的 JSON 實現可能會提供如下選項:
-
傳輸具有默認值的字段:在 proto3 的 JSON 輸出中,pb 中帶有默認值的字段在序列化時會被省略掉。有的 pb 實現可能會提供一個選項來重載這個行爲並且輸出具有默認值的字段。
-
忽略未知字段:proto3 的 JSON 解析器默認會駁回未知字段,但是也可以提供一個選項在解析時忽略這些字段。
-
不使用小寫駝峯名,使用 proto 的字段名:proto3 默認的在序列化時應該把字段名轉換成小寫駝峯名並用作 JSON 的鍵。有的實現可能會直接使用 message 的字段名作爲 JSON 的名字。proto3 的 JSON 解析器必須都能夠轉換小寫駝峯名和 proto 字段名。
-
以整型而不是字符串形式傳輸枚舉類型:pb 默認序列化後的 JSON 輸出中使用枚舉的名字進行傳輸。其他的實現可能會使用枚舉值而不是枚舉名進行傳輸。
1.15. 選項
可以使用多個選項註釋. proto 文件中的單個聲明。選項不會更改聲明的整體含義,但可能影響其在特定上下文中處理的方式。完整的可用選項定義在:google/protobuf/descriptor.proto。
有些選項是文件級別的,意味着它們應該寫在最頂層的範圍內,而不是在 message、枚舉或者 service 定義中。有些選項是 message 級的,意味着他們應該寫在 message 定義內部。有些選項是字段級的,意味着它們應該被寫在字段定義的內部。選項也可以寫在枚舉類型、枚舉值、oneof、service 類型和 service 方法上,然而,現在沒有選項提供使用。
具體的選項在生成各種語言的樁代碼中用的比較多,這個用到再查就可以了。
1.16. 生成你的類代碼
通過. proto 文件中的 message 定義,你可以生成 Java、Kotlin、Python、C++、Go、Ruby 等的類代碼,這需要在. proto 文件上執行 pb 編譯器 protoc 得到。如果你沒有安裝這個編譯器,下載安裝包然後按照 README 的指引進行。對於 Go 語言,你還需要安裝一個特殊的代碼生成插件:你可以在 github 的 golang/protobuf 上找到它和它的安裝指南。
https://developers.google.com/protocol-buffers/docs/downloads
protobuf 安裝包
https://github.com/golang/protobuf/
golang/protobuf
這個 pb 編譯器的運行指令如下:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
-
IMPORT_PATH 定義了一個文件夾,當解析 import 的時候會在這個文件夾下查找. proto 文件。如果省略了,就會使用當前文件夾。多個 import 文件夾可以通過多次聲明 --proto_path 來添加;它們會被按照順序依次查找。-I=_IMPORT_PATH_可以用作縮寫的 --proto_path。
-
你可以提供一個或多個輸出文件夾:
-
--cpp_out 將生成的 C++ 代碼放到 DST_DIR。更多信息見:C++ generated code reference
https://developers.google.com/protocol-buffers/docs/reference/cpp-generated
C++ generated code reference
- --java_out 在 DST_DIR 中生成 Java 代碼。有關更多信息,請參閱 Java generated code reference。
https://developers.google.com/protocol-buffers/docs/reference/java-generated
Java generated code reference
- --kotlin_out 在 DST_DIR 中生成額外的 Kotlin 代碼。有關更多信息,請參閱 Kotlin generated code reference。
https://developers.google.com/protocol-buffers/docs/reference/kotlin-generated
Kotlin generated code reference
- --python_out 在 DST_DIR 中生成 Python 代碼。有關更多信息,請參閱 Python generated code reference。
https://developers.google.com/protocol-buffers/docs/reference/python-generated
Python generated code reference
- --go_out 在 DST_DIR 中生成 Go 代碼。有關更多信息,請參閱 Go generated code reference。
https://developers.google.com/protocol-buffers/docs/reference/go-generated
Go generated code reference
-
--ruby_out 在 DST_DIR 中生成 Ruby 代碼。Ruby 生成的代碼參考即將推出!
-
--objc_out 在 DST_DIR 中生成 Objective-C 代碼。有關更多信息,請參閱 Objective-C generated code reference。
https://developers.google.com/protocol-buffers/docs/reference/objective-c-generated
Objective-C generated code reference
- --csharp_out 在 DST_DIR 中生成 C# 代碼。有關更多信息,請參閱 C# generated code reference。
https://developers.google.com/protocol-buffers/docs/reference/csharp-generated
C# generated code reference
- --php_out 在 DST_DIR 中生成 PHP 代碼。有關更多信息,請參閱 PHP generated code reference。爲方便起見,如果 DST_DIR 以 .zip 或 .jar 結尾,則編譯器會將輸出寫入具有給定名稱的單個 ZIP 格式存檔文件。.jar 輸出還將根據 Java JAR 規範的要求提供清單文件。請注意,如果輸出存檔已經存在,它將被覆蓋;編譯器不夠聰明,無法將文件添加到現有存檔中。
https://developers.google.com/protocol-buffers/docs/reference/php-generated
PHP generated code reference
- 你必須提供一個或多個. proto 文件作爲輸入。可以一次指定多個 .proto 文件。儘管文件是相對於當前目錄命名的,但每個文件都必須在 IMPORT_PATH 之一中,以便編譯器可以確定其規範名稱。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/vufLI2J-pTt3QBnRezqc_A