本文基于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习。