Proto3入門

Proto3入門

本文基於Google提供的ProtolBuffer LanguageGuide英文文檔: ProtolBuffer3 Language Guidephp

ProtoBuf的API文檔java

定義一個message

首先以一個簡單的例子開頭:好比查百度,那麼須要一個查詢語句:query,還有查詢的頁面號:page_number,而後就是查詢的每一頁的結果數:result_per_page。 這樣就有三個字段:querypage_numberresult_per_page。 那麼這個消息(message)定義以下:python

/*選中語法格式proto3,也就是ProtocolBuffer的版本3*/
syntax = "proto3";
/*定義一個消息,消息名字爲SearchRequest*/
message SearchRequest{
	/*鍵值對,每一個字段則須要字段名和具體的類型*/
    string query = 1;
    int32 page_number = 2;
    int32 result_per_page = 3;
}
複製代碼

對於每個字段,必須指定具體的類型

除了一個基礎類型(字符串string,整型int32等)還能夠定義其餘綜合類型,如枚舉類型和其餘的message類型。後面將列出。api

分配字段號

在上面定義的SearchRequest消息中,對於每一個字段,都有惟一標識的編號。這些字段號在消息(message)的二進制格式中惟一識別,在該消息(message)投入到使用後不該該被更改。 在ProtocolBuffer中將消息序列化爲二進制後,對於1~15編號的字段,只須要一個字節編碼,對於16~2047則須要兩個字節。因此,對於把常用的字段元素編號到1~15中,而且預留(reserved)幾位以便於之後擴展。 字段號的範圍爲:1~536870911(2^29-1)。其中19000~19999爲ProtocolBuffer本身預留(reserved)的字段號不能使用。其餘均可以本身使用。固然,本身預留(reserved)的編號在後續擴展也不能使用。對於預留(reserved)的後面將講到。ruby

指定字段規則

消息(message)的字段可使用兩種規則描述(proto2與proto3不一樣):框架

  • 單一的(singular):0個或1個,不用在字段定義中指出。dom

  • 重複的(repeated):0個到多個,須要在字段定義中指出。 看以下例子:一我的,只有一個正式的名字(在剛出生的時候名字還沒登記),可是他能夠有多個外號,也能夠沒有。ide

    syntax = "proto3";
      message Person{
          string name = 1;
          repeated string nickname = 2;
      }
    複製代碼

添加更多的消息類型

多個消息類型能夠在一個.proto文件中定義。 好比在上面SearchRequest中添加一個SearchResponse。函數

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

message SearchResponse{
    repeated string result = 1;
    int32 page_number = 2;
}
複製代碼

註釋

.proto文件中註釋爲C/C++風格,用//註釋單方,或/**/註釋多行。ui

預留(reserved)字段

前面提到ProtocolBuffer本身預留(reserved)的字段號19000~19999。 能夠本身預留字段名或者字段號,這樣預留(reserved)的字段將不會被之後的用戶修改了。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}
複製代碼

編譯器編譯.proto文件

可使用ProtocolBufer編譯器將.proto文件編譯成本身選擇的語言。在以後可使用編譯後的消息(message)進行get/set字段值,序列化消息(message)到輸出流,或者從輸入流中反序列化獲得消息(message)。

  • C++:一個.proto文件編譯生成一對.h.cc文件。
  • Java:生成.java文件,使用消息(message)指定的Builder類建立消息(message)對象的實例。
  • Python:Python有點不同:會生成一個module其中包含每一個消息(message)的靜態描述符。
  • Go:每個消息(message)生成一個.pb.go文件。
  • Ruby:生成.rb文件,是一個module中包含各個消息(message)。
  • Objective-C:一個.proto文件生成一對pbobjcpbobjc.m文件,每一個消息(message)對應一個class。
  • C#:生成.cs文件,每一個消息(message)對應一個class。
  • Dart:生成.pb.dart文件,每一個消息(message)對應一個class。

基礎類型

  • double
  • float
  • int32:使用可變長編碼,若是使用的該字段會有負數,效率將變低,這時最好使用sint32。
  • int64:使用可變長編碼,若是使用的該字段會有負數,效率將變低,這時最好使用sint64。
  • uint32:使用可變長編碼。
  • uint64:使用可變長編碼。
  • sin32:使用可變長編碼,有符號整形,有負數時使用常規的int32更有效率。
  • sint64:使用可變長編碼,有符號整形,有負數時使用常規的int64更有效率。
  • fixed32:固定4個字節,若是數字大於2^28比uint32更有效率。
  • fixed64:固定8個字節,若是數字大於2^56比uint64更有效率。
  • sfixed32:固定4個字節。
  • sfixed64:固定8個字節。
  • bool
  • string:字符串,必須使用UTF-8或者7位ASCII編碼格式。
  • bytes:有任意的byte序列。 具體的proto中各個字段類型映射到對應語言中時,見下圖:

默認值

若是一個消息(message)被解析了,可是其中的字段並無被賦值,那麼將會被設置爲默認值。

  • string:空串
  • bytes:空的bytes序列
  • bool:false
  • 數字類型:0
  • 枚舉類型:默認值爲枚舉類型中定義的第一個值,也就是0
  • 消息類型(message:取決於所編譯的語言。 對於repeated,爲空的list。

枚舉

這裏仍是以以前的查百度的例子來講,有了查詢關鍵字query,對於結果,你有可能不僅是想要瀏覽一下WEB頁面,還行看看視頻、圖片、新聞啥的。那麼這樣定義:

syntax = "proto3";

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

    enum Category{
        option allow_alias = true;
        //第一個值必須爲0。
        UNIVERSAL = 0;
        WEB = 1;
        IMAGES = 2;
        NEWS = 3;
        PRODUCT = 4;
        VIDEO = 5;
        //啓用了別名,則能夠賦同一個值
        GENERAL = 0;
    }
    Category result_type = 4;
}
複製代碼

使用enum關鍵字定義枚舉類型。 枚舉常量數值必須在32bit的整型中。使用負數賦值枚舉常量效率低,不推薦。對於枚舉常量,能夠定義在消息(message)中,也可定義在消息(message)外。好比上面定義在SearchRequest中的Category,以SearchRequest.Category的方式來複用。

預留(reserved)值

一樣的,對於消息(message)中能夠預留(reserved)字段號,在枚舉中,能夠預留(reserved)值。 下面預留(reserved)了,值,名字。(2,15,9到11,40到最大值都不能後續使用)。

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

使用其餘消息(message)做爲字段

以前定義的SearchRespon消息(message):

message SearchResponse{
	repeated string result = 1;
	int32 page_number = 2;
}
複製代碼

對於結果,咱們只能獲取多個字符串,讓他迴應的消息功能更強大一點,咱們定義一個Result消息(message):

message SearchResponse{
	repeated Result result = 1;
	int32 page_number = 2;
}
message Result{
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
}
複製代碼

SearchRespon中,咱們嵌套了一個Result消息(message),Result中有請求的地址url,標題title還有描述片斷snippets

嵌套定義

接下來,咱們再具體化URL:

message SearchResponse{
    repeated Result result = 1;
    int32 page_number = 2;
}
message Result{
    URL url = 1;
    string title = 2;
    repeated string snippets = 3;
}

message URL{
    enum Protocol{
        HTTP = 0;
        HTTPS = 1;
    }
    Protocol protocol = 1;
    string domain = 2;
    int32 port = 3;
    string filepath = 4;
}
複製代碼

對於SearchResponse消息(message)中返回的結果result,在Result中又有消息(messageURL,在URL中咱們具體到,使用的協議、域名、端口、請求文件路徑。因此,消息之間能夠互相嵌套,定義更加複雜的消息。

導入

在Java中,或者其餘語言,須要導入其餘以及寫好的包,在ProtocolBuffer中也是同樣,能夠導入先前定義好的.proto文件,使用其中定義的消息(message)或者服務(service)。 在同一目錄下,我將寫好的URL放入URL.proto文件中,在定義SearchResponse消息(message)中導入該文件:

import "URL.proto";

message Result{
    URL url = 1;
    string title = 2;
    repeated string snippets = 3;
}
複製代碼

這樣就能夠複用更多的自定義消息(message)了。 對於import,只能導入其後續指定的.proto文件中定義的消息(message)或服務。好比有3個.proto文件

/*file A.proto*/
syntax = "proto3";

message A{

}
/*file B.proto*/
syntax = "proto3";

import "A.proto";

message B{
    A a = 1;
}
/*file C.proto*/
syntax = "proto3";

import "B.proto";

message C{
    A a = 1;
}
複製代碼

在這其中,C是看不到A的,只有在B中import public "A.proto",C才能看見A。

Any字段類型

Any字段類型是Google本身對於Proto中類型的封裝,並提供必定特定方法。 以下定義一個Any字段,須要導入Google提供的any.proto

在Java中使用 ErrorStatus消息調用 detailsget方法時,返回的實例是 com.google.protobuf.Any,對於該類型提供了pack和unpack方法,以下:

class Any {
  // 對於給定的消息打包成Any類型,前綴則是默認的:type.googleapis.com
  public static Any pack(Message message);
  // 對於給定的消息打包成Any類型,前綴則是typeUrlPrefix指定的
  public static Any pack(Message message,
                         String typeUrlPrefix);

  // 檢查該Any類型是不是給定clazz的消息類型
  public <T extends Message> boolean is(class<T> clazz);
  // 給定clazz消息類型,將Any類型拆包成指定的消息類型,若是不匹配拋出異常
  public <T extends Message> T unpack(class<T> clazz)
      throws InvalidProtocolBufferException;
}
複製代碼

Any字段給了必定的靈活性,在傳遞消息時不用指定特定的類型,能夠在傳遞不一樣消息中傳遞不一樣的類型,在接收端進行判斷便可。在傳輸時,底層仍是被轉換爲bytes類型。

Oneof字段類型

Oneof類型以下定義。

oneof oneof_name {
	int32 foo_int = 4;
	string foo_string = 9;
	...
}
複製代碼

對於這個oneof消息類型,咱們能夠這樣理解,它相似與C語言中的union類型(聯合體),最後生成的Java代碼是這樣的:

public enum OneofNameCase
    implements com.google.protobuf.Internal.EnumLite {
  FOO_INT(4),
  FOO_STRING(9),
  ...
  ONEOFNAME_NOT_SET(0);
  ...
};
複製代碼

若是設置了oneof_name消息中的foo_int字段,那foo_string就無效。一樣的,若是設置了foo_string字段,那麼foo_int字段就無效。在Oneof類型的消息中,只有一片共享內存,每次只有一個字段被設置。 須要注意,Oneof的消息不能使用repeated描述。 在Java中提供了一下方法進行輔助使用: 對於生成類中的枚舉類:

  • int getNumber(): 返回在.proto文件中定義的索引值,如foo_int則返回4。
  • static OneofNameCase forNumber(int value): 返回使用索引值相應的對象,若是該對象未設置則返回null,如4則返回foo_int。 生成類中:
  • OneofNameCase getOneofNameCase(): 返回已經設置了的對象,若是都沒有被設置返回ONEOFNAME_NOT_SET。 生成類中的Builder:
  • Builder clearOneofName(): 清空全部設置。

Map字段類型

使用這樣定義Map類型:

map<key_type, value_type> map_field = N;
複製代碼
  • key_type:可使用任何常規類型(int32或者string等),不能使用浮點數bytes類型定義。
  • value_type:能夠是任何類型,除了又是一個Map。 和Oneof一樣,使用Map定義的字段不能夠是**repeated**的。

包:package

對於.proto文件,可使用包組織,package字段就是相似於Java中的Package。 定義的計算CalculateMsg消息,在proto.Calculation文件夾下:

其中的包就是 Calculation
最好使用 package和文件夾想對應,在Java中的習慣哈。

定義服務Service

在這裏我使用上面CalculationMsg的消息類,定義了其相應了服務,RPC(Remote Procedure Call)。 package Calculation;

import "Calculation/CalculatMsg.proto";

service Calculator{
    rpc Calc( CalRequest ) returns (CalResponse){}
}
複製代碼

使用Proto編譯器編譯上面的文件,相應於選擇的語言將生成服務的接口(interface)和客戶端的stub。 可使用Google提供的gRPC,也可使用第三方的RPC框架。 這裏我給你們看看模仿grpc.io提供例子寫的計算服務: 對於服務端:

複寫編譯生成的gRPC接口類,實現以前定義的calc函數:獲取請求的須要計算方法,數值1和數值2,計算,而後放入輸出流中,最後OnComplete。
客戶端則先Build一個請求,阻塞調用獲取結果。

映射到JSON

Proto3可以轉換到JSON數據格式,其相應的數據類型映射以下: 若是Proto中某個字段未設置,在JSON中就是null。

選項Option

.proto文件中可使用option字段聲明特定選項。Opion不會影響總體消息的定義,可是在特定的上下文中進行影響。 Option選項也是分級別的,有時候在外定義,則影響的是文件級別,如:java_packagejava_multiple_filesjava_outer_classname等,分別是:編譯後在哪一個java包下,是否將.proto文件中不一樣消息分紅多個文件,定義編譯後的java類名。

以前定義的計算服務就是如上,生成的 packagetech.sylardaemon.Calculation中,生成後的類名 CalculatorProt,java_generic_services爲true則是生成gRPC相應的服務接口和客戶stub。 還能夠本身定義option,是ProtoBuf的一種高級應用,這裏就略過了,有興趣的同窗能夠本身查查看。

編譯器使用

編譯器的使用以下:

protoc --proto_path = IMPORT_PATH --Language_out = DST_DIR path/to/*.proto
複製代碼
  • --proto_path:該參數輸入的IMPORT_PATH是指定你要編譯的***.proto文件中import指令中查找的目錄。若是省略,則使用當前編譯器執行的目錄。也能夠屢次使用--proto_path指定多個導入目錄。可使用-I**縮短。
  • --Language_out:能夠提供一個或多個輸出目錄:
    • --cpp_out:生成C ++代碼的目的目錄
    • --java_out:生成Java代碼的目的目錄
    • --python_out:生成Python代碼的目的目錄
    • --go_out:生成Go代碼的目的目錄
    • --ruby_out:生成Ruby代碼的目的目錄
    • --objc_out:生成Objective-C代碼的目的目錄
    • --csharp_out:生成C#代碼的目的目錄
    • --php_out:生成PHP代碼的目的目錄
  • path/to/*.proto:最後的則是將要被編譯的proto文件路徑。

整篇就差很少完成了,最後還有一點考試,若是有錯誤或者缺了啥,歡迎提出,大概寒假有時間就來改改,你們一塊兒進步xio習。

相關文章
相關標籤/搜索