這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Protobuf文法
gRPC推薦使用proto3,本節只介紹常用文法,更多進階使用姿勢請參考官方文檔
Message定義
一個message類型定義描述了一個請求或相應的訊息格式,可以包含多種類型欄位。例如定義一個搜尋請求的訊息格式,每個請求包含查詢字串、頁碼、每頁數目。
syntax = "proto3";message SearchRequest { string query = 1; // 查詢字串 int32 page_number = 2; // 頁碼 int32 result_per_page = 3; // 每頁條數}
首行聲明使用的protobuf版本為proto3
SearchRequest 定義了三個欄位,每個欄位聲明以分號結尾,可使用雙斜線//
添加註釋。
欄位型別宣告
所有的欄位需要前置聲明資料類型,上面的樣本指定了兩個數實值型別和一個字串類型。除了基本的標量類型還有複合類型,如枚舉、其它message類型等。
標識符Tags
可以看到,訊息的定義中,每個欄位都有一個唯一的數值型標識符。這些標識符用於識別欄位在訊息中的二進位格式,使用中的類型不應該隨意改動。需要注意的是,[1-15]內的標識在編碼時只佔用一個位元組,包含標識符和欄位類型。[16-2047]之間的標識符佔用2個位元組。建議為頻繁出現的訊息元素使用[1-15]間的標識符。如果考慮到以後可能或擴充頻繁元素,可以預留一些標識符。
最小的標識符可以從1開始,最大到229 - 1,或536,870,911。不可以使用[19000-19999]之間的標識符, Protobuf協議實現中預留了這些標識符。在.proto檔案中使用這些預留標識號,編譯時間就會報錯。
欄位規則
添加更多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 |
關於這些類型在序列化時的編碼規則請參考 Protocol Buffer Encoding.
[1] java
[2] all
[3] 64
[4] Python
預設值
字串類型預設為空白字串
位元組類型預設為空白位元組
布爾類型預設false
數實值型別預設為0值
enums類型預設為第一個定義的枚舉值,必須是0
針對不同語言的預設值的具體行為參考 generated code guide
枚舉(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;
包(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命名採用駝峰命名方式,欄位命名採用小寫字母加底線分隔方式
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語言的使用姿勢會在其它章節詳細說明:
C++ generated code reference
Java generated code reference
Python generated code reference
Go generated code reference
Objective-C generated code reference
C# generated code reference
吐槽: 照著官方文檔一步步操作不一定成功哦!
更多
Any 訊息類型
Oneof 欄位
自訂Options
這些用法在實踐中很少使用,這裡不做詳細介紹,尤其自訂選項設計進階用法,有需要請參考官方文檔。
參考
本系列範例程式碼