本文基於Google提供的ProtolBuffer LanguageGuide英文文檔: ProtolBuffer3 Language Guidephp
ProtoBuf的API文檔java
首先以一個簡單的例子開頭:好比查百度,那麼須要一個查詢語句:query,還有查詢的頁面號:page_number,而後就是查詢的每一頁的結果數:result_per_page。 這樣就有三個字段:query,page_number和result_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
前面提到ProtocolBuffer本身預留(reserved)的字段號19000~19999
。 能夠本身預留字段名或者字段號,這樣預留(reserved)的字段將不會被之後的用戶修改了。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
複製代碼
.proto
文件可使用ProtocolBufer編譯器將.proto
文件編譯成本身選擇的語言。在以後可使用編譯後的消息(message)進行get/set字段值,序列化消息(message)到輸出流,或者從輸入流中反序列化獲得消息(message)。
.proto
文件編譯生成一對.h
和.cc
文件。.java
文件,使用消息(message)指定的Builder
類建立消息(message)對象的實例。.pb.go
文件。.rb
文件,是一個module
中包含各個消息(message)。.proto
文件生成一對pbobjc
和pbobjc.m
文件,每一個消息(message)對應一個class。.cs
文件,每一個消息(message)對應一個class。.pb.dart
文件,每一個消息(message)對應一個class。2^28
比uint32更有效率。2^56
比uint64更有效率。若是一個消息(message)被解析了,可是其中的字段並無被賦值,那麼將會被設置爲默認值。
這裏仍是以以前的查百度的例子來講,有了查詢關鍵字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
的方式來複用。
一樣的,對於消息(message)中能夠預留(reserved)字段號,在枚舉中,能夠預留(reserved)值。 下面預留(reserved)了,值,名字。(2,15,9到11,40到最大值都不能後續使用)。
enum Foo {
reserved 2, 15, 9 to 11, 40 to max;
reserved "FOO", "BAR";
}
複製代碼
以前定義的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
中又有消息(message)URL
,在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
字段類型是Google本身對於Proto中類型的封裝,並提供必定特定方法。 以下定義一個Any
字段,須要導入Google提供的any.proto
ErrorStatus
消息調用
details
的
get
方法時,返回的實例是
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_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中提供了一下方法進行輔助使用: 對於生成類中的枚舉類:
.proto
文件中定義的索引值,如foo_int
則返回4。foo_int
。 生成類中:Builder
:使用這樣定義Map
類型:
map<key_type, value_type> map_field = N;
複製代碼
key_type
:可使用任何常規類型(int32或者string等),不能使用浮點數和bytes類型定義。value_type
:能夠是任何類型,除了又是一個Map
。 和Oneof
一樣,使用Map
定義的字段不能夠是**repeated
**的。對於.proto
文件,可使用包組織,package
字段就是相似於Java中的Package
。 定義的計算CalculateMsg
消息,在proto.Calculation文件夾下:
Calculation
。
package
和文件夾想對應,在Java中的習慣哈。
在這裏我使用上面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提供例子寫的計算服務: 對於服務端:
Proto3可以轉換到JSON數據格式,其相應的數據類型映射以下: 若是Proto中某個字段未設置,在JSON中就是null。
.proto
文件中可使用option
字段聲明特定選項。Opion不會影響總體消息的定義,可是在特定的上下文中進行影響。 Option選項也是分級別的,有時候在外定義,則影響的是文件級別,如:java_package
、java_multiple_files
、java_outer_classname
等,分別是:編譯後在哪一個java包下,是否將.proto
文件中不一樣消息分紅多個文件,定義編譯後的java類名。
package
在
tech.sylardaemon.Calculation
中,生成後的類名
CalculatorProt
,java_generic_services爲true則是生成gRPC相應的服務接口和客戶stub。 還能夠本身定義option,是ProtoBuf的一種高級應用,這裏就略過了,有興趣的同窗能夠本身查查看。
編譯器的使用以下:
protoc --proto_path = IMPORT_PATH --Language_out = DST_DIR path/to/*.proto
複製代碼
整篇就差很少完成了,最後還有一點考試,若是有錯誤或者缺了啥,歡迎提出,大概寒假有時間就來改改,你們一塊兒進步xio習。