https://blog.csdn.net/skh2015java/article/details/78404235php
原文地址:http://blog.csdn.net/lyjshen/article/details/52298003html
定义一个消息类型java
· 标量数值类型python
· 默认值git
· 枚举github
· 使用其余消息类型golang
· 嵌套类型objective-c
· 更新一个消息类型api
· Any安全
· Oneof
· 映射(Maps)
· 定义服务
· JSON 映射
· 选项
· 生成你的类
英文原文:
中文出处:
中文出处是proto2的译文,proto3的英文出现后在原来基础上增改了,水平有限,还请指正
这个指南描述了如何使用Protocol buffer 语言去描述你的protocol buffer 数据, 包括 .proto文件符号和如何从.proto文件生成类。包含了proto2版本的protocol buffer语言:对于老版本的proto3 符号,请见Proto2 Language Guide(以及中文译本,抄了不少这里的感谢下老版本的翻译者)
本文是一个参考指南——若是要查看如何使用本文中描述的多个特性的按部就班的例子,请在教程中查找须要的语言的教程。
定义一个消息类型
先来看一个很是简单的例子。假设你想定义一个“搜索请求”的消息格式,每个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。能够采用以下的方式来定义消息类型的.proto文件了:
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
指定字段类型
在上面的例子中,全部字段都是标量类型:两个整型(page_number和result_per_page),一个string类型(query)。固然,你也能够为字段指定其余的合成类型,包括枚举(enumerations)或其余消息类型。
分配标识号
正如你所见,在消息定义中,每一个字段都有惟一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不可以再改变。注:[1,15]以内的标识号在编码的时候会占用一个字节。[16,2047]以内的标识号则占用2个字节。因此应该为那些频繁出现的消息元素保留 [1,15]以内的标识号。切记:要为未来有可能添加的、频繁出现的标识号预留一些标识号。
最小的标识号能够从1开始,最大到2^29 - 1, or 536,870,911。不可使用其中的[19000-19999]( (从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber))的标识号, Protobuf协议实现中对这些进行了预留。若是非要在.proto文件中使用这些预留标识号,编译时就会报警。一样你也不能使用早期保留的标识号。
指定字段规则
所指定的消息字段修饰符必须是以下之一:
在proto3中,repeated的标量域默认状况虾使用packed。
你能够了解更多的pakced属性在Protocol Buffer编码
添加更多消息类型
在一个.proto文件中能够定义多个消息类型。在定义多个相关的消息的时候,这一点特别有用——例如,若是想定义与SearchResponse消息类型对应的回复消息格式的话,你能够将它添加到相同的.proto文件中,如:
message SearchRequest {
string query =1;
int32page_number = 2;
int32result_per_page = 3;
}
message SearchResponse {
...
}
添加注释
向.proto文件添加注释,可使用C/C++/java风格的双斜杠(//) 语法格式,如:
messageSearchRequest {
string query = 1;
int32 page_number = 2; // Which page number do we want?
int32 result_per_page = 3; // Number of results to return per page.
}
保留标识符(Reserved)
若是你经过删除或者注释全部域,之后的用户能够重用标识号当你从新更新类型的时候。若是你使用旧版本加载相同的.proto文件这会致使严重的问题,包括数据损坏、隐私错误等等。如今有一种确保不会发生这种状况的方法就是指定保留标识符(and/or names, which can also cause issues for JSON serialization不明白什么意思),protocol buffer的编译器会警告将来尝试使用这些域标识符的用户。
message Foo {
reserved 2, 15, 9to 11;
reserved "foo","bar";
}
注:不要在同一行reserved声明中同时声明域名字和标识号
从.proto文件生成了什么?
当用protocol buffer编译器来运行.proto文件时,编译器将生成所选择语言的代码,这些代码能够操做在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。
你能够从以下的文档连接中获取每种语言更多API(proto3版本的内容很快就公布)。API Reference
标量数值类型
一个标量消息字段能够含有一个以下的类型——该表格展现了定义于.proto文件中的类型,以及与之对应的、在自动生成的访问类中定义的类型:
.proto Type |
Notes |
C++ Type |
Java Type |
Python Type[2] |
Go Type |
Ruby Type |
C# Type |
PHP Type |
double |
double |
double |
float |
float64 |
Float |
double |
float |
|
float |
float |
float |
float |
float32 |
Float |
float |
float |
|
int32 |
使用变长编码,对于负值的效率很低,若是你的域有可能有负值,请使用sint64替代 |
int32 |
int |
int |
int32 |
Fixnum 或者 Bignum(根据须要) |
int |
integer |
uint32 |
使用变长编码 |
uint32 |
int |
int/long |
uint32 |
Fixnum 或者 Bignum(根据须要) |
uint |
integer |
uint64 |
使用变长编码 |
uint64 |
long |
int/long |
uint64 |
Bignum |
ulong |
integer/string |
sint32 |
使用变长编码,这些编码在负值时比int32高效的多 |
int32 |
int |
int |
int32 |
Fixnum 或者 Bignum(根据须要) |
int |
integer |
sint64 |
使用变长编码,有符号的整型值。编码时比一般的int64高效。 |
int64 |
long |
int/long |
int64 |
Bignum |
long |
integer/string |
fixed32 |
老是4个字节,若是数值老是比老是比228大的话,这个类型会比uint32高效。 |
uint32 |
int |
int |
uint32 |
Fixnum 或者 Bignum(根据须要) |
uint |
integer |
fixed64 |
老是8个字节,若是数值老是比老是比256大的话,这个类型会比uint64高效。 |
uint64 |
long |
int/long |
uint64 |
Bignum |
ulong |
integer/string |
sfixed32 |
老是4个字节 |
int32 |
int |
int |
int32 |
Fixnum 或者 Bignum(根据须要) |
int |
integer |
sfixed64 |
老是8个字节 |
int64 |
long |
int/long |
int64 |
Bignum |
long |
integer/string |
bool |
bool |
boolean |
bool |
bool |
TrueClass/FalseClass |
bool |
boolean |
|
string |
一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。 |
string |
String |
str/unicode |
string |
String (UTF-8) |
string |
string |
bytes |
可能包含任意顺序的字节数据。 |
string |
ByteString |
str |
[]byte |
String (ASCII-8BIT) |
ByteString |
string |
你能够在文章Protocol Buffer编码中,找到更多“序列化消息时各类类型如何编码”的信息。
1. 在java中,无符号32位和64位整型被表示成他们的整型对应形似,最高位被储存在标志位中。
2. 对于全部的状况,设定值会执行类型检查以确保此值是有效。
3. 64位或者无符号32位整型在解码时被表示成为ilong,可是在设置时可使用int型值设定,在全部的状况下,值必须符合其设置其类型的要求。
4. python中string被表示成在解码时表示成unicode。可是一个ASCIIstring能够被表示成str类型。
5. Integer在64位的机器上使用,string在32位机器上使用
默认值
当一个消息被解析的时候,若是被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不一样类型指定以下:
对于可重复域的默认值是空(一般状况下是对应语言中空列表)。
注:对于标量消息域,一旦消息被解析,就没法判断域释放被设置为默认值(例如,例如boolean值是否被设置为false)仍是根本没有被设置。你应该在定义你的消息类型时很是注意。例如,好比你不该该定义boolean的默认值false做为任何行为的触发方式。也应该注意若是一个标量消息域被设置为标志位,这个值不该该被序列化传输。
查看generated code guide选择你的语言的默认值的工做细节。
枚举
当须要定义一个消息类型的时候,可能想为一个字段指定某“预约义值序列”中的一个值。例如,假设要为每个SearchRequest消息添加一个 corpus字段,而corpus的值多是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一个。 其实能够很容易地实现这一点:经过向消息定义中添加一个枚举(enum)而且为每一个可能的值定义一个常量就能够了。
在下面的例子中,在消息格式中添加了一个叫作Corpus的枚举类型——它含有全部可能的值 ——以及一个类型为Corpus的字段:
messageSearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB =1;
IMAGES = 2;
LOCAL= 3;
NEWS =4;
PRODUCTS = 5;
VIDEO= 6;
}
Corpus corpus = 4;
}
如你所见,Corpus枚举的第一个常量映射为0:每一个枚举类型必须将其第一个类型映射为0,这是由于:
你能够经过将不一样的枚举常量指定位相同的值。若是这样作你须要将allow_alias设定位true,不然编译器会在别名的地方产生一个错误信息。
enum EnumAllowingAlias{
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
enum EnumNotAllowingAlias{
UNKNOWN = 0;
STARTED = 1;
// RUNNING = 1; // Uncommenting this line will cause acompile error inside Google and a warning message outside.
}
枚举常量必须在32位整型值的范围内。由于enum值是使用可变编码方式的,对负数不够高效,所以不推荐在enum中使用负数。如上例所示,能够在 一个消息定义的内部或外部定义枚举——这些枚举能够在.proto文件中的任何消息定义里重用。固然也能够在一个消息中声明一个枚举类型,而在另外一个不一样 的消息中使用它——采用MessageType.EnumType的语法格式。
当对一个使用了枚举的.proto文件运行protocol buffer编译器的时候,生成的代码中将有一个对应的enum(对Java或C++来讲),或者一个特殊的EnumDescriptor类(对 Python来讲),它被用来在运行时生成的类中建立一系列的整型值符号常量(symbolic constants)。
在反序列化的过程当中,没法识别的枚举值会被保存在消息中,虽然这种表示方式须要依据所使用语言而定。在那些支持开放枚举类型超出指定范围以外的语言中(例如C++和Go),为识别的值会被表示成所支持的整型。在使用封闭枚举类型的语言中(Java),使用枚举中的一个类型来表示未识别的值,而且可使用所支持整型来访问。在其余状况下,若是解析的消息被序列号,未识别的值将保持原样。
关于如何在你的应用程序的消息中使用枚举的更多信息,请查看所选择的语言generatedcode guide
使用其余消息类型
你能够将其余消息类型用做字段类型。例如,假设在每个SearchResponse消息中包含Result消息,此时能够在相同的.proto文件中定义一个Result消息类型,而后在SearchResponse消息中指定一个Result类型的字段,如:
message SearchResponse {
repeated Resultresults = 1;
}
message Result {
string url = 1;
string title = 2;
repeated stringsnippets = 3;
}
导入定义
在上面的例子中,Result消息类型与SearchResponse是定义在同一文件中的。若是想要使用的消息类型已经在其余.proto文件中已经定义过了呢?
你能够经过导入(importing)其余.proto文件中的定义来使用它们。要导入其余.proto文件的定义,你须要在你的文件中添加一个导入声明,如:
import "myproject/other_protos.proto";
默认状况下你只能使用直接导入的.proto文件中的定义. 然而, 有时候你须要移动一个.proto文件到一个新的位置, 能够不直接移动.proto文件, 只需放入一个伪 .proto 文件在老的位置, 而后使用import public转向新的位置。import public 依赖性会经过任意导入包含import public声明的proto文件传递。例如:
// 这是新的proto
// All definitions are movedhere
// 这是久的proto
// 这是全部客户端正在导入的包
import public "new.proto";
import "other.proto";
// 客户端proto
import "old.proto";
// 如今你可使用新久两种包的proto定义了。
经过在编译器命令行参数中使用-I/--proto_pathprotocal 编译器会在指定目录搜索要导入的文件。若是没有给出标志,编译器会搜索编译命令被调用的目录。一般你只要指定proto_path标志为你的工程根目录就好。而且指定好导入的正确名称就好。
使用proto2消息类型
在你的proto3消息中导入proto2的消息类型也是能够的,反之亦然,而后proto2枚举不能够直接在proto3的标识符中使用(若是仅仅在proto2消息中使用是能够的)。
嵌套类型
你能够在其余消息类型中定义、使用消息类型,在下面的例子中,Result消息就定义在SearchResponse消息内,如:
message SearchResponse {
message Result{
string url =1;
string title= 2;
repeated stringsnippets = 3;
}
repeatedResult results = 1;
}
若是你想在它的父消息类型的外部重用这个消息类型,你须要以Parent.Type的形式使用它,如:
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
固然,你也能够将消息嵌套任意多层,如:
message Outer { // Level 0
messageMiddleAA { // Level 1
messageInner { // Level 2
int64 ival= 1;
bool booly = 2;
}
}
messageMiddleBB { // Level 1
messageInner { // Level 2
int32 ival= 1;
bool booly = 2;
}
}
}
更新一个消息类型
若是一个已有的消息格式已没法知足新的需求——如,要在消息中添加一个额外的字段——可是同时旧版本写的代码仍然可用。不用担忧!更新消息而不破坏已有代码是很是简单的。在更新时只要记住如下的规则便可。
Any
Any类型消息容许你在没有指定他们的.proto定义的状况下使用消息做为一个嵌套类型。一个Any类型包括一个能够被序列化bytes类型的任意消息,以及一个URL做为一个全局标识符和解析消息类型。为了使用Any类型,你须要导入importgoogle/protobuf/any.proto
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeatedgoogle.protobuf.Any details = 2;
}
对于给定的消息类型的默认类型URL是type.googleapis.com/packagename.messagename。
不一样语言的实现会支持动态库以线程安全的方式去帮助封装或者解封装Any值。例如在java中,Any类型会有特殊的pack()和unpack()访问器,在C++中会有PackFrom()和UnpackTo()方法。
// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
if(detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processingnetwork_error ...
}
}
目前,用于Any类型的动态库仍在开发之中
Oneof
若是你的消息中有不少可选字段, 而且同时至多一个字段会被设置, 你能够增强这个行为,使用oneof特性节省内存.
Oneof字段就像可选字段, 除了它们会共享内存, 至多一个字段会被设置。 设置其中一个字段会清除其它字段。 你可使用case()或者WhichOneof() 方法检查哪一个oneof字段被设置, 看你使用什么语言了.
使用Oneof
为了在.proto定义Oneof字段, 你须要在名字前面加上oneof关键字, 好比下面例子的test_oneof:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
而后你能够增长oneof字段到 oneof 定义中. 你能够增长任意类型的字段, 可是不能使用repeated 关键字.
在产生的代码中, oneof字段拥有一样的 getters 和setters, 就像正常的可选字段同样. 也有一个特殊的方法来检查到底那个字段被设置. 你能够在相应的语言API指南中找到oneof API介绍.
Oneof 特性
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name"); // Will delete sub_message
sub_message->set_... // Crashes here
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
向后兼容性问题
当增长或者删除oneof字段时必定要当心. 若是检查oneof的值返回None/NOT_SET, 它意味着oneof字段没有被赋值或者在一个不一样的版本中赋值了。 你不会知道是哪一种状况,由于没有办法判断若是未识别的字段是一个oneof字段。
Tage 重用问题:
Map(映射)
若是你但愿建立一个关联映射,protocol buffer提供了一种快捷的语法:
map<key_type, value_type> map_field = N;
其中key_type能够是任意Integer或者string类型(因此,除了floating和bytes的任意标量类型都是能够的)value_type能够是任意类型。
例如,若是你但愿建立一个project的映射,每一个Projecct使用一个string做为key,你能够像下面这样定义:
map<string, Project> projects = 3;
生成map的API如今对于全部proto3支持的语言均可用了,你能够从API指南找到更多信息。
向后兼容性问题
map语法序列化后等同于以下内容,所以即便是不支持map语法的protocol buffer实现也是能够处理你的数据的:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntrymap_field = N;
包
固然能够为.proto文件新增一个可选的package声明符,用来防止不一样的消息类型有命名冲突。如:
package foo.bar;
message Open { ... }
在其余的消息格式定义中可使用包名+消息名的方式来定义域的类型,如:
message Foo {
...
required foo.bar.Open open = 1;
...
}
包的声明符会根据使用语言的不一样影响生成的代码。
包及名称的解析
Protocol buffer语言中类型名称的解析与C++是一致的:首先从最内部开始查找,依次向外进行,每一个包会被看做是其父类包的内部类。固然对于 (foo.bar.Baz)这样以“.”分隔的意味着是从最外围开始的。
ProtocolBuffer编译器会解析.proto文件中定义的全部类型名。 对于不一样语言的代码生成器会知道如何来指向每一个具体的类型,即便它们使用了不一样的规则。
定义服务(Service)
若是想要将消息类型用在RPC(远程方法调用)系统中,能够在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不一样语言生成服务接口代码及存根。如,想要定义一个RPC服务并具备一个方法,该方法可以接收 SearchRequest并返回一个SearchResponse,此时能够在.proto文件中进行以下定义:
service SearchService{
rpc Search (SearchRequest) returns(SearchResponse);
}
最直观的使用protocol buffer的RPC系统是gRPC一个由谷歌开发的语言和平台中的开源的PRC系统,gRPC在使用protocl buffer时很是有效,若是使用特殊的protocol buffer插件能够直接为您从.proto文件中产生相关的RPC代码。
若是你不想使用gRPC,也可使用protocol buffer用于本身的RPC实现,你能够从proto2语言指南中找到更多信息
还有一些第三方开发的PRC实现使用Protocol Buffer。参考第三方插件wiki查看这些实现的列表。
JSON 映射
Proto3 支持JSON的编码规范,使他更容易在不一样系统之间共享数据,在下表中逐个描述类型。
若是JSON编码的数据丢失或者其自己就是null,这个数据会在解析成protocol buffer的时候被表示成默认值。若是一个字段在protocol buffer中表示为默认值,体会在转化成JSON的时候编码的时候忽略掉以节省空间。具体实现能够提供在JSON编码中可选的默认值。
proto3 |
JSON |
JSON示例 |
注意 |
message |
object |
{“fBar”: v, “g”: null, …} |
产生JSON对象,消息字段名能够被映射成lowerCamelCase形式,而且成为JSON对象键,null被接受并成为对应字段的默认值 |
enum |
string |
“FOO_BAR” |
枚举值的名字在proto文件中被指定 |
map |
object |
{“k”: v, …} |
全部的键都被转换成string |
repeated V |
array |
[v, …] |
null被视为空列表 |
bool |
true, false |
true, false |
|
string |
string |
“Hello World!” |
|
bytes |
base64 string |
“YWJjMTIzIT8kKiYoKSctPUB+” |
|
int32, fixed32, uint32 |
number |
1, -10, 0 |
JSON值会是一个十进制数,数值型或者string类型都会接受 |
int64, fixed64, uint64 |
string |
“1”, “-10” |
JSON值会是一个十进制数,数值型或者string类型都会接受 |
float, double |
number |
1.1, -10.0, 0, “NaN”, “Infinity” |
JSON值会是一个数字或者一个指定的字符串如”NaN”,”infinity”或者”-Infinity”,数值型或者字符串都是可接受的,指数符号也能够接受 |
Any |
object |
{“@type”: “url”, “f”: v, … } |
若是一个Any保留一个特上述的JSON映射,则它会转换成一个以下形式:{"@type": xxx, "value": yyy}不然,该值会被转换成一个JSON对象,@type字段会被插入所指定的肯定的值 |
Timestamp |
string |
“1972-01-01T10:00:20.021Z” |
使用RFC 339,其中生成的输出将始终是Z-归一化啊的,而且使用0,3,6或者9位小数 |
Duration |
string |
“1.000340012s”, “1s” |
生成的输出老是0,3,6或者9位小数,具体依赖于所须要的精度,接受全部能够转换为纳秒级的精度 |
Struct |
object |
{ … } |
任意的JSON对象,见struct.proto |
Wrapper types |
various types |
2, “2”, “foo”, true, “true”, null, 0, … |
包装器在JSON中的表示方式相似于基本类型,可是容许nulll,而且在转换的过程当中保留null |
FieldMask |
string |
“f.fooBar,h” |
见fieldmask.proto |
ListValue |
array |
[foo, bar, …] |
|
Value |
value |
任意JSON值 |
|
NullValue |
null |
JSON null |
选项
在定义.proto文件时可以标注一系列的options。Options并不改变整个文件声明的含义,但却可以影响特定环境下处理方式。完整的可用选项能够在google/protobuf/descriptor.proto找到。
一些选项是文件级别的,意味着它能够做用于最外范围,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,意味着它能够用在消息定义的内部。固然有些选项能够做用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并无一种有效的选项能做用于全部的类型。
以下就是一些经常使用的选择:
option java_package = "com.example.foo";
option java_outer_classname = "Ponycopter";
option optimize_for =CODE_SIZE;
int32 old_field = 6[deprecated=true];
自定义选项
ProtocolBuffers容许自定义并使用选项。该功能应该属于一个高级特性,对于大部分人是用不到的。若是你的确但愿建立本身的选项,请参看 Proto2 Language Guide。注意建立自定义选项使用了拓展,拓展只在proto3中可用。
生成访问类
能够经过定义好的.proto文件来生成Java,Python,C++, Ruby, JavaNano, Objective-C,或者C# 代码,须要基于.proto文件运行protocol buffer编译器protoc。若是你没有安装编译器,下载安装包并遵守README安装。对于Go,你还须要安装一个特殊的代码生成器插件。你能够经过GitHub上的protobuf库找到安装过程
经过以下方式调用protocol编译器:
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
做为一个方便的拓展,若是DST_DIR以.zip或者.jar结尾,编译器会将输出写到一个ZIP格式文件或者符合JAR标准的.jar文件中。注意若是输出已经存在则会被覆盖,编译器尚未智能到能够追加文件。
- 你必须提议一个或多个.proto文件做为输入,多个.proto文件能够只指定一次。虽然文件路径是相对于当前目录的,每一个文件必须位于其IMPORT_PATH下,以便每一个文件能够肯定其规范的名称。