Protocol Buffers官方文档(proto3语言指南)
本文是对官方文档的翻译,大部分内容都是引用其他一些作者的优质翻译使文章内容更加通俗易懂(自己是直译,读起来有点绕口难理解,本人英文水平有限),参考的文章链接在文章末尾
这篇指南描述如何使用protocol buffer语言来组织你的protocol buffer数据,包括.proto文件的语法规则以及如何通过.proto文件来生成数据访问类代码。
Defining A Message Type(定义一个消息类型)
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
- 语法说明(syntax)前只能是空行或者注释
- 每个字段由字段限制、字段类型、字段名和编号四部分组成
Specifying Field Types(指定字段类型)
在上面的例子中,该消息定义了三个字段,两个int32类型和一个string类型的字段
Assigning Tags(赋予编号)
消息中的每一个字段都有一个独一无二的数值类型的编号。1到15使用一个字节编码,16到2047使用2个字节编码,所以应该将编号1到15留给频繁使用的字段。
可以指定的最小的编号为1,最大为2^{29}-1或536,870,911。但是不能使用19000到19999之间的值,这些值是预留给protocol buffer的。
Specifying Field Rules(指定字段限制)
required:必须赋值的字段optional:可有可无的字段repeated:可重复字段(变长字段)
Adding More Message Types(添加更多消息类型)
一个.proto文件可以定义多个消息类型:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
Adding Comments(添加注释)
.proto文件也使用C/C++风格的注释语法//
message SearchRequest {
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 Fields(预留字段)
如果消息的字段被移除或注释掉,但是使用者可能重复使用字段编码,就有可能导致例如数据损坏、隐私漏洞等问题。一种避免此类问题的方法就是指明这些删除的字段是保留的。如果有用户使用这些字段的编号,protocol buffer编译器会发出告警。
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
What's Generated From Your .proto?(编译.proto文件)
对于C++,每一个.proto文件经过编译之后都会对应的生成一个.h和一个.cc文件。
Scalar Value Types(类型对照表)
| .proto Type | Notes | C++ Type |
|---|---|---|
| double | double | double |
| float | float | float |
| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 |
| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 |
| uint32 | Uses variable-length encoding. | uint32 |
| uint64 | Uses variable-length encoding. | uint64 |
| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 |
| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 |
| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28 | uint32 |
| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56 | uint64 |
| sfixed32 | Always four bytes. | int32 |
| sfixed64 | Always eight bytes. | int64 |
| bool | bool | boolean |
| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string |
| bytes | May contain any arbitrary sequence of bytes. | string |
Default Values(缺省值)
如果没有指定默认值,则会使用系统默认值,对于string默认值为空字符串,对于bool默认值为false,对于数值类型默认值为0,对于enum默认值为定义中的第一个元素,对于repeated默认值为空。
Enumerations(枚举)
message SearchRequest {
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;
}
通过设置可选参数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 a compile error inside Google and a warning message outside.
}
由于枚举值采用varint编码,所以为了提高效率,不建议枚举值取负数。这些枚举值可以在其他消息定义中重复使用。
Using Other Message Types(使用其他消息类型)
可以使用一个消息的定义作为另一个消息的字段类型。
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
Importing Definitions(导入定义)
就像C++的头文件一样,你还可以导入其他的.proto文件
import "myproject/other_protos.proto";
如果想要移动一个.proto文件,但是又不想修改项目中import部分的代码,可以在文件原先位置留一个空.proto文件,然后使用import public导入文件移动后的新位置:
// new.proto
// All definitions are moved here
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
Nested Types(嵌套类型)
在protocol中可以定义如下的嵌套类型
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
如果在另外一个消息中需要使用Result定义,则可以通过Parent.Type来使用。
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
protocol支持更深层次的嵌套和分组嵌套,但是为了结构清晰起见,不建议使用过深层次的嵌套。
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;
}
}
Updating A Message Type(更新一个数据类型)
在实际的开发中会存在这样一种应用场景,既消息格式因为某些需求的变化而不得不进行必要的升级,但是有些使用原有消息格式的应用程序暂时又不能被立刻升级,这便要求我们在升级消息格式时要遵守一定的规则,从而可以保证基于新老消息格式的新老程序同时运行。规则如下:
- 不要修改已经存在字段的标签号。
- 任何新添加的字段必须是optional和repeated限定符,否则无法保证新老程序在互相传递消息时的消息兼容性。
- 在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用。
- int32、uint32、int64、uint64和bool等类型之间是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着如果想修改原有字段的类型时,为了保证兼容性,只能将其修改为与其原有类型兼容的类型,否则就将打破新老消息格式的兼容性。
- optional和repeated限定符也是相互兼容的。
Any(任意消息类型)
Any类型是一种不需要在.proto文件中定义就可以直接使用的消息类型,使用前import google/protobuf/any.proto文件即可。
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
C++使用PackFrom()和UnpackTo()方法来打包和解包Any类型消息。
// 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); ... processing network_error ... }
}
Oneof(其中一个字段类型)
有点类似C++中的联合,就是消息中的多个字段类型在同一时刻只有一个字段会被使用,使用case()或WhichOneof()方法来检测哪个字段被使用了。
Using Oneof(使用Oneof)
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
你可以添加除repeated外任意类型的字段到Oneof定义中
Oneof Features(Oneof特性)
- oneof字段只有最后被设置的字段才有效,即后面的set操作会覆盖前面的set操作
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
- oneof不可以是
repeated的 - 反射API可以作用于oneof字段
- 如果使用C++要防止内存泄露,即后面的set操作会覆盖之前的set操作,导致前面设置的字段对象发生析构,要注意字段对象的指针操作
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name"); // Will delete sub_message
sub_message->set_... // Crashes her
- 如果使用C++的
Swap()方法交换两条oneof消息,两条消息都不会保存之前的字段
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
Backwards-compatibility issues(向后兼容)
添加或删除oneof字段的时候要注意,如果检测到oneof字段的返回值是None/NOT_SET,这意味着oneof没有被设置或者设置了一个不同版本的oneof的字段,但是没有办法能够区分这两种情况,因为没有办法确认一个未知的字段是否是一个oneof的成员。
Tag Reuse Issues(编号复用问题)
- 删除或添加字段到oneof:在消息序列化或解析后会丢失一些信息,一些字段将被清空
- 删除一个字段然后重新添加:在消息序列化或解析后会清除当前设置的oneof字段
- 分割或合并字段:同普通的删除字段操作
Maps(表映射)
protocol buffers提供了简介的语法来实现map类型:
map<key_type, value_type> map_field = N;
key_type可以是除浮点指针或bytes外的其他基本类型,value_type可以是任意类型
map<string, Project> projects = 3;
- Map的字段不可以是重复的(repeated)
- 线性顺序和map值的的迭代顺序是未定义的,所以不能期待map的元素是有序的
- maps可以通过key来排序,数值类型的key通过比较数值进行排序
- 线性解析或者合并的时候,如果出现重复的key值,最后一个key将被使用。从文本格式来解析map,如果出现重复key值则解析失败。
Backwards compatibility(向后兼容)
map语法下面的表达方式在线性上是等价的,所以即使protocol buffers没有实现maps数据结构也不会影响数据的处理:
message MapFieldEntry {
key_type key = 1;
value_type value = 2;
}
repeated MapFieldEntry map_field = N;
包
类似C++的命名空间,用来防止名称冲突
package foo.bar;
message Open { ... }
你可以使用包说明符来定义你的消息字段:
message Foo {
...
foo.bar.Open open = 1;
...
}
定义服务
如果想在RPC系统中使用消息类型,就需要在.proto文件中定义RPC服务接口,然后使用编译器生成对应语言的存根。
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
JSON映射
Proto3支持JSON格式的编码。编码后的JSON数据的如果没有值或值为空,解析时protocol buffer将会使用默认值,在对JSON编码时可以节省空间。
| proto3 | JSON | JSON example | Notes |
|---|---|---|---|
| message | object | {"fBar": v, "g": null, …} | Generates JSON objects. Message field names are mapped to lowerCamelCase and become JSON object keys. null is accepted and treated as the default value of the corresponding field type. |
| enum | string | "FOO_BAR" | The name of the enum value as specified in proto is used. |
| map< K,V> | object | {"k": v, …} | All keys are converted to strings. |
| repeated V | array | [v, …] | null is accepted as the empty list []. |
| bool | true, false | true, false | |
| string | string | "Hello World!" | |
| bytes | base64 string | "YWJjMTIzIT8kKiYoKSctPUB+" | |
| int32, fixed32, uint32 | number | 1, -10, 0 | JSON value will be a decimal number. Either numbers or strings are accepted. |
| int64, fixed64, uint64 | string | "1", "-10" | JSON value will be a decimal string. Either numbers or strings are accepted. |
| float, double | number | 1.1, -10.0, 0, "NaN", "Infinity" | JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". Either numbers or strings are accepted. Exponent notation is also accepted. |
| Any | object | {"@type": "url", "f": v, … } | If the Any contains a value that has a special JSON mapping, it will be converted as follows: {"@type": xxx,<wbr style="box-sizing: inherit;"> "value": yyy}. Otherwise, the value will be converted into a JSON object, and the "@type" field will be inserted to indicate the actual data type. |
| Timestamp | string | "1972-01-01T10:00:20.021Z" | Uses RFC 3339, where generated output will always be Z-normalized and uses 0, 3, 6 or 9 fractional digits. |
| Duration | string | "1.000340012s", "1s" | Generated output always contains 0, 3, 6, or 9 fractional digits, depending on required precision. Accepted are any fractional digits (also none) as long as they fit into nano-seconds precision. |
| Struct | object | { … } | Any JSON object. See struct.proto. |
| Wrapper types | various types | 2, "2", "foo", true, "true", null, 0, … | Wrappers use the same representation in JSON as the wrapped primitive type, except that null is allowed and preserved during data conversion and transfer. |
| FieldMask | string | "f.fooBar,h" | See fieldmask.proto. |
| ListValue | array | [foo, bar, …] | |
| Value | value | Any JSON value | |
| NullValue | null | JSON null |
选项
Protocol Buffer允许我们在.proto文件中定义一些常用的选项,这样可以指示Protocol Buffer编译器帮助我们生成更为匹配的目标语言代码。Protocol Buffer内置的选项被分为以下三个级别:
文件级别,这样的选项将影响当前文件中定义的所有消息和枚举。
消息级别,这样的选项仅影响某个消息及其包含的所有字段。
字段级别,这样的选项仅仅响应与其相关的字段。
下面将给出一些常用的Protocol Buffer选项。
optimize_for(文件选项):可以设置的值有SPEED、CODE_SIZE或LITE_RUNTIME,不同的选项会以下述方式影响C++代码的生成(option optimize_for = CODE_SIZE;)。
SPEED (default): protocol buffer编译器将会生成序列化,语法分析和其他高效操作消息类型的方式.这也是最高的优化选项.确定是生成的代码比较大.
CODE_SIZE: protocol buffer编译器将会生成最小的类,确定是比SPEED运行要慢
LITE_RUNTIME: protocol buffer编译器将会生成只依赖"lite" runtime library (libprotobuf-lite instead of libprotobuf)的类. lite运行时库比整个库更小但是删除了例如descriptors 和 reflection等特性. 这个选项通常用于手机平台的优化.
cc_enable_arenas(文件选项):生成的C++代码启用arena allocation内存管理deprecated(文件选项):
参考资料
Protocol Buffer官方文档
Protocol Buffer使用简介
Protocol Buffer技术详解(语言规范)
Protocol Buffers官方文档(proto3语言指南)的更多相关文章
- Protocol Buffers(Protobuf) 官方文档--Protobuf语言指南
Protocol Buffers(Protobuf) 官方文档--Protobuf语言指南 约定:为方便书写,ProtocolBuffers在下文中将已Protobuf代替. 本指南将向您描述如何使用 ...
- Protocol Buffers官方文档(开发指南)
本文是对官方文档的翻译,然后截取了一篇非常优秀的文章片段来帮助理解,本人英文水平有限,基本都是直译,如果有不理解的地方请参考英文官方文档,参考的文章链接在文章末尾 protocol buffers简介 ...
- 《KAFKA官方文档》入门指南(转)
1.入门指南 1.1简介 Apache的Kafka™是一个分布式流平台(a distributed streaming platform).这到底意味着什么? 我们认为,一个流处理平台应该具有三个关键 ...
- 官方文档 恢复备份指南一 Introduction to Backup and Recovery
1.备份分为:物理备份和逻辑备份 物理备份:备份数据文件 控制文件 归档日志文件 逻辑备份:EXP EXPDP备份等 物理备份为主,逻辑做补充 2.错误的类型 ...
- 官方文档 恢复备份指南六 Configuring the RMAN Environment: Advanced Topics
RMAN高级设置. 本章内容: Configuring Advanced Channel Options 高级通道选项 Configuring Advanced Backup Options 高级备 ...
- 官方文档 恢复备份指南三 Recovery Manager Architecture
本节讨论以下问题: About the RMAN Environment 关于RMAN环境 RMAN Command-Line Client ...
- 官方文档 恢复备份指南八 RMAN Backup Concepts
本章内容 Consistent and Inconsistent RMAN Backups Online Backups and Backup Mode Backup Sets Image Copie ...
- 官方文档 恢复备份指南四 Starting and Interacting with the RMAN Client
本章讲: Starting and Exiting RMAN Specifying the Location of RMAN Output ...
- 官方文档 恢复备份指南二 Getting Started with RMAN
本章对RMAN进行基本的熟悉和了解 1.Overview of the RMAN Environment RMAN运行时需要的最小环境: target database ...
随机推荐
- jquery获取点击标签内的子标签内容和值实例
今天有点累了,就不多做其他的描述解释.在插入的代码里相关解释也都有. <!--<%@ page language="java" import="java.ut ...
- python_unittest详解
一 整体结构概览 unittest原名为PyUnit,是由java的JUnit衍生而来.对于单元测试,需要设置预先条件,对比预期结果和实际结果. 整体结构:unittest库提供了test cases ...
- jquery侧边折叠导航栏制作,两行代码搞定
jquery侧边折叠导航栏制作,两行代码搞定 //CSS*{margin: 0;padding: 0} ul{list-style: none} .menu li ul{display: none} ...
- Delphi考虑sql注入 QuotedStr
之前只在BS架构的项目中考虑了Sql注入问题,却很少考虑到用了多年的Delphi项目也应该考虑Sql注入的问题,今天做了个实验,成功完成注入,把表里数据全部删除,以后再做Delphi项目还真的考虑这个 ...
- Python-单元测试unittest
Python中有一个自带的单元测试框架是unittest模块,用它来做单元测试,它里面封装好了一些校验返回的结果方法和一些用例执行前的初始化操作,概念见下: TestCase 也就是测试用例 Test ...
- 初次接触Servlet3.0
Servlet3.0 一.要求 MyEclipes10.0或以上版本! 发布到Tomcat7.0或以上版本!二.步骤 创建JavaEE6.0应用 --------------------------- ...
- myeclipes如何调试web项目
你可以右击项目,然后选中那个debug as,然后选择open debug dialog,在project中选择要运行的项目,sever中选择服务器,然后单击debug就ok了,,
- python字符串替换之re.sub()
re.sub(pattern, repl, string, count=0, flags=0) pattern可以是一个字符串也可以是一个正则,用于匹配要替换的字符,如果不写,字符串不做修改.\1 代 ...
- linux命令学习笔记(0):man命令
Linux提供了丰富的帮助手册,当你需要查看某个命令的参数时不必到处上网查找,只要man一下即可. Linux的man手册共有以下几个章节: 代號 代表內容 使用者在shell中可以操作的指令或可执行 ...
- Netty,Netty
Windows防火墙会自动关闭空闲的TCP链接,所以Netty需要心跳,如果发现链接断开需要进行关闭Session: 怎么来理解TCP的流式传输呢? int blocksize = buffer.re ...