1. 程式人生 > >Golang gRPC實踐 連載三 Protobuf語法

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語言的使用姿勢會在其它章節詳細說明:

吐槽: 照著官方文件一步步操作不一定成功哦!