Golang gRPC實踐 連載三 Protobuf語法
Protobuf語法
gRPC推薦使用proto3,本節只介紹常用語法,更多高階使用姿勢請參考官方文件
Message定義
一個message型別定義描述了一個請求或相應的訊息格式,可以包含多種型別欄位。例如定義一個搜尋請求的訊息格式,每個請求包含查詢字串、頁碼、每頁數目。
syntax = "proto3"; message SearchRequest { string query = 1; // 查詢字串 int32 page_number = 2; // 頁碼 int32 result_per_page = 3; // 每頁條數 }
首行宣告使用的protobuf版本為proto3
SearchRequest 定義了三個欄位,每個欄位宣告以分號結尾,可使用雙斜線//
添加註釋。
在 example 包中編寫 person.proto
syntax = "proto3";
package example;
message person { // aa 會生成 Aa 命名的結構體
int32 id = 1;
string name = 2;
}
message all_person { // aa_bb 會生成 AaBb 的駝峰命名的結構體
repeated person Per = 1 ;
}
使用列舉
2.1 定義一個訊息型別 (官方例子)
// [START declaration]
syntax = "proto3";
package tutorial;
// [END declaration]
// [START java_declaration] protoc編譯後生成的java包結構名以及外部呼叫類名
option java_package = "com.example.tutorial";
option java_outer_classname = "AddressBookProtos";
// [END java_declaration]
// [START csharp_declaration]
option csharp_namespace = "Google.Protobuf.Examples.AddressBook";
// [END csharp_declaration]
// [START messages]
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
// [END messages]
作者:everlastxgb
連結:https://www.jianshu.com/p/6c9f90538efe
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
作者:謝煙客
連結:https://www.jianshu.com/p/1a3f1c3031b5
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
欄位型別宣告
所有的欄位需要前置宣告資料型別,上面的示例指定了兩個數值型別和一個字串型別。除了基本的標量型別還有複合型別,如列舉、其它message型別等。
識別符號Tags
可以看到,訊息的定義中,每個欄位都有一個唯一的數值型識別符號。這些識別符號用於標識欄位在訊息中的二進位制格式,使用中的型別不應該隨意改動。需要注意的是,[1-15]內的標識在編碼時只佔用一個位元組,包含識別符號和欄位型別。[16-2047]之間的識別符號佔用2個位元組。建議為頻繁出現的訊息元素使用[1-15]間的識別符號。如果考慮到以後可能或擴充套件頻繁元素,可以預留一些識別符號。
最小的識別符號可以從1開始,最大到229 - 1,或536,870,911。不可以使用[19000-19999]之間的識別符號, Protobuf協議實現中預留了這些識別符號。在.proto檔案中使用這些預留標識號,編譯時就會報錯。
欄位規則
-
repeated:標識欄位可以重複任意次,類似陣列
-
proto3不支援proto2中的required和optional
新增更多message型別
一個.proto檔案中可以定義多個訊息型別,一般用於同時定義多個相關的訊息,例如在同一個.proto檔案中同時定義搜尋請求和響應訊息:
syntax = "proto3";
// SearchRequest 搜尋請求
message SearchRequest {
string query = 1; // 查詢字串
int32 page_number = 2; // 頁碼
int32 result_per_page = 3; // 每頁條數
}
// SearchResponse 搜尋響應
message SearchResponse {
...
}
添加註釋
向.proto檔案中添加註釋,支援C風格雙斜線//
單行註釋
保留欄位與識別符號
可以使用reserved關鍵字指定保留欄位和保留識別符號:
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
注意,不能在一個reserved宣告中混合欄位名和識別符號。
.proto檔案編譯結果
當使用protocol buffer編譯器執行.proto
檔案時,編譯器將生成所選語言的程式碼,用於使用在.proto
檔案中定義的訊息型別、服務介面約定等。不同語言生成的程式碼格式不同:
-
C++: 每個
.proto
檔案生成一個.h
檔案和一個.cc
檔案,每個訊息型別對應一個類 -
Java: 生成一個
.java
檔案,同樣每個訊息對應一個類,同時還有一個特殊的Builder
類用於建立訊息介面 -
Python: 姿勢不太一樣,每個
.proto
檔案中的訊息型別生成一個含有靜態描述符的模組,該模組與一個元類metaclass在執行時建立需要的Python資料訪問類 -
Go: 生成一個
.pb.go
檔案,每個訊息型別對應一個結構體 -
Ruby: 生成一個
.rb
檔案的Ruby模組,包含所有訊息型別 -
JavaNano: 類似Java,但不包含
Builder
類 -
Objective-C: 每個
.proto
檔案生成一個pbobjc.h
和一個pbobjc.m
檔案 -
C#: 生成
.cs
檔案包含,每個訊息型別對應一個類
各種語言的更多的使用方法請參考官方API文件
資料型別
這裡直接引用官方文件的描述:
.proto | C++ | Java | Python | Go | Ruby | C# |
---|---|---|---|---|---|---|
double | double | double | float | float64 | Float | double |
float | float | float | float | float32 | Float | float |
int32 | int32 | int | int | int32 | Fixnum or Bignum | int |
int64 | int64 | long | ing/long[3] | int64 | Bignum | long |
uint32 | uint32 | int[1] | int/long[3] | uint32 | Fixnum or Bignum | uint |
uint64 | uint64 | long[1] | int/long[3] | uint64 | Bignum | ulong |
sint32 | int32 | int | intj | int32 | Fixnum or Bignum | int |
sint64 | int64 | long | int/long[3] | int64 | Bignum | long |
fixed32 | uint32 | int[1] | int | uint32 | Fixnum or Bignum | uint |
fixed64 | uint64 | long[1] | int/long[3] | uint64 | Bignum | ulong |
sfixed32 | int32 | int | int | int32 | Fixnum or Bignum | int |
sfixed64 | int64 | long | int/long[3] | int64 | Bignum | long |
bool | bool | boolean | boolean | bool | TrueClass/FalseClass | bool |
string | string | String | str/unicode[4] | string | String(UTF-8) | string |
bytes | string | ByteString | str | []byte | String(ASCII-8BIT) | ByteString |
[1] java
[2] all
[3] 64
[4] Python
預設值
-
字串型別預設為空字串
-
位元組型別預設為空位元組
-
布林型別預設false
-
數值型別預設為0值
-
enums型別預設為第一個定義的列舉值,必須是0
列舉(Enum) TODO
使用其它Message
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
message支援巢狀使用,作為另一message中的欄位型別
匯入定義(import)
可以使用import語句匯入使用其它描述檔案中宣告的型別
import "others.proto";
protocol buffer編譯器會在 -I / --proto_path
引數指定的目錄中查詢匯入的檔案,如果沒有指定該引數,預設在當前目錄中查詢。
Message巢狀
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
內部宣告的message型別名稱只可在內部直接使用,在外部引用需要前置父級message名稱,如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更新 TODO
Map型別
proto3支援map型別宣告:
map<key_type, value_type> map_field = N;
message Project {...}
map<string, Project> projects = 1;
-
鍵、值型別可以是內建的標量型別,也可以是自定義message型別
-
欄位不支援
repeated
屬性 -
不要依賴map型別的欄位順序
包(Packages)
在.proto
檔案中使用package
宣告包名,避免命名衝突。
syntax = "proto3";
package foo.bar;
message Open {...}
在其他的訊息格式定義中可以使用包名+訊息名的方式來使用型別,如:
message Foo {
...
foo.bar.Open open = 1;
...
}
在不同的語言中,包名定義對編譯後生成的程式碼的影響不同:
-
C++ 中:對應C++名稱空間,例如
Open
會在名稱空間foo::bar
中 -
Java 中:package會作為Java包名,除非指定了
option jave_package
選項 -
Python 中:package被忽略
-
Go 中:預設使用package名作為包名,除非指定了
option go_package
選項 -
JavaNano 中:同Java
-
C# 中:package會轉換為駝峰式名稱空間,如
Foo.Bar
,除非指定了option csharp_namespace
選項
定義服務(Service)
如果想要將訊息型別用在RPC(遠端方法呼叫)系統中,可以在.proto
檔案中定義一個RPC服務介面,protocol buffer編譯器會根據所選擇的不同語言生成服務介面程式碼。例如,想要定義一個RPC服務並具有一個方法,該方法接收SearchRequest
並返回一個SearchResponse
,此時可以在.proto
檔案中進行如下定義:
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse) {}
}
生成的介面程式碼作為客戶端與服務端的約定,服務端必須實現定義的所有介面方法,客戶端直接呼叫同名方法向服務端發起請求。比較蛋疼的是即便業務上不需要引數也必須指定一個請求訊息,一般會定義一個空message。
選項(Options)
在定義.proto檔案時可以標註一系列的options。Options並不改變整個檔案宣告的含義,但卻可以影響特定環境下處理方式。完整的可用選項可以檢視google/protobuf/descriptor.proto
.
一些選項是檔案級別的,意味著它可以作用於頂層作用域,不包含在任何訊息內部、enum或服務定義中。一些選項是訊息級別的,可以用在訊息定義的內部。當然有些選項可以作用在欄位、enum型別、enum值、服務型別及服務方法中。但是到目前為止,並沒有一種有效的選項能作用於這些型別。
一下是一些常用的選擇:
-
java_package
(file option):指定生成java類所在的包,如果在.proto檔案中沒有明確的宣告java_package,會使用預設包名。不需要生成java程式碼時不起作用 -
java_outer_classname
(file option):指定生成Java類的名稱,如果在.proto檔案中沒有明確宣告java_outer_classname,生成的class名稱將會根據.proto檔案的名稱採用駝峰式的命名方式進行生成。如(foo_bar.proto生成的java類名為FooBar.java),不需要生成java程式碼時不起任何作用 -
objc_class_prefix
(file option): 指定Objective-C類字首,會前置在所有類和列舉型別名之前。沒有預設值,應該使用3-5個大寫字母。注意所有2個字母的字首是Apple保留的。
基本規範
描述檔案以.proto
做為檔案字尾,除結構定義外的語句以分號結尾
-
結構定義包括:message、service、enum
-
rpc方法定義結尾的分號可有可無
Message命名採用駝峰命名方式,欄位命名採用小寫字母加下劃線分隔方式
message SongServerRequest {
required string song_name = 1;
}
Enums型別名採用駝峰命名方式,欄位命名採用大寫字母加下劃線分隔方式
enum Foo {
FIRST_VALUE = 1;
SECOND_VALUE = 2;
}
Service與rpc方法名統一採用駝峰式命名
詳解Go語言編譯結果 TODO
message對應golang中的struct,編譯生成go程式碼後,欄位名會轉換為駝峰式
編譯
通過定義好的.proto
檔案生成Java, Python, C++, Go, Ruby, JavaNano, Objective-C, or C# 程式碼,需要安裝編譯器protoc
。參考Github專案google/protobuf安裝編譯器.Go語言需要同時安裝一個特殊的外掛:golang/protobuf。
執行命令:
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 --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
這裡只做參考就好,具體語言的編譯例項請參考詳細文件,其中,Go語言的使用姿勢會在其它章節詳細說明:
吐槽: 照著官方文件一步步操作不一定成功哦!