这里写目录标题
protobuf3语法模板
syntax = "proto3";//文件第一行指定使用的protobuf版本,如果不指定,默认使用proto2。如果指定,则必须在文件的非空非注释的第一行
package protobuf;//定义proto包名,可以为.proto文件新增一个可选的package声明符作为生成语言的namespace,用来防止不同的消息类型有命名冲突
import public “other_protos.proto”;//引入其他protobuf文件
import “google/protobuf/any.proto”;
option optimize_for = SPEED;//可以被设置为 SPEED, CODE_SIZE,or LITE_RUNTIME。这些值将影响C++代码的生成,默认是SPEED,一般不需要设置。
//messaage可以理解为C++中的class关键字
message Person {
//变量(字段)的定义格式为:[修饰符(可选)][数据类型][变量名(字段名)] = [唯一标识符] ,其中唯一标识符是用来标识字段的,同一个message中字段的标识符不能相同
string var1 = 1;
//string var2 = 1;//该变量定义会编译报错,因为编号1已经被使用了
<span class="token comment">/*
protobuf中的基本数据类型----对应的c++基础数据类型
int32 ---- int
int64 ---- long
double ---- double
float ---- float
bytes ---- string
bool ---- bool
此外,还有:
uint32 ---- int
uint64 ---- long
sint32 ---- int
sint64 ---- long
fixed32 ---- int
fixed64 ---- long
sfixed32 ---- int
sfixed64 ---- long
*/</span>
<span class="token comment">/*
proto3取消了required和optional两个关键字
repeated用来定义数组
*/</span>
repeated string list <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>
<span class="token comment">/*
使用map
*/</span>
map<span class="token operator"><</span>string<span class="token punctuation">,</span> string<span class="token operator">></span> projects <span class="token operator">=</span> <span class="token number">23</span><span class="token punctuation">;</span>
<span class="token comment">/*
有时候你需要保留一些你以后要用到的编号或者变量名,使用reserved关键字
*/</span>
reserved <span class="token number">3</span><span class="token punctuation">,</span> <span class="token number">15</span><span class="token punctuation">,</span> <span class="token number">9</span> to <span class="token number">11</span><span class="token punctuation">;</span>
reserved <span class="token string">"foo"</span><span class="token punctuation">,</span> <span class="token string">"bar"</span><span class="token punctuation">;</span>
<span class="token comment">//string var2 = 3;//编译会报错,因为3被保留了</span>
<span class="token comment">//string var3 = 10;//编译会报错,因为10被保留了</span>
<span class="token comment">//string foo = 12;//编译会报错,因为foo被保留了</span>
<span class="token comment">/*
由于一些历史原因,基本数值类型的repeated的字段并没有被尽可能地高效编码。在新的代码中,用户应该使用特殊选项[packed=true]来保证更高效的编码。
注意[packed=true]只能用在 repeated修饰的数字类型中
*/</span>
repeated int32 var11 <span class="token operator">=</span> <span class="token number">28</span> <span class="token punctuation">[</span>packed<span class="token operator">=</span><span class="token boolean">true</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token comment">/*
关于字段的默认值:
string类型的变量,默认值是空字符串
bytes类型的变量,默认值是空byte数组
bool类型的变量,默认值是false
数字类型的变量,默认值是0
枚举类型的变量,默认值是第一个枚举值,而且这个第一个枚举值的数字值必须是0
*/</span>
<span class="token comment">/*
定义枚举
一个enum类型的字段只能用指定的常量集中的一个值作为其值(如果尝试指定不同的值,解析器就会把它当作一个未知的字段来对待)
*/</span>
<span class="token keyword">enum</span> <span class="token class-name">Corpus</span> <span class="token punctuation">{<!-- --></span>
UNIVERSAL <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span><span class="token comment">//第一个枚举值,这里的数字必须是0,不然编译不通过</span>
WEB <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token comment">//WEB1 = 1;//这里编译不通过,数字1只能对应一个枚举值。</span>
IMAGES <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>
LOCAL <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
NEWS <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span>
PRODUCTS <span class="token operator">=</span> <span class="token number">5</span><span class="token punctuation">;</span>
VIDEO <span class="token operator">=</span> <span class="token number">6</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
Corpus corpus <span class="token operator">=</span> <span class="token number">4</span><span class="token punctuation">;</span>
<span class="token comment">/*
你可以为枚举常量定义别名。 需要设置allow_alias option 为 true, 否则 protocol编译器会产生错误信息。
*/</span>
<span class="token keyword">enum</span> <span class="token class-name">EnumAllowingAlias</span> <span class="token punctuation">{<!-- --></span>
option allow_alias <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
UNKNOWN <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
STARTED <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
RUNNING <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">/*
Message Type 作为变量
*/</span>
Test test <span class="token operator">=</span> <span class="token number">14</span><span class="token punctuation">;</span><span class="token comment">//同一个包下的other_protos.proto文件中的message Test作为变量的类型</span>
<span class="token comment">/*
嵌套的message,message可以无限嵌套
*/</span>
message Result <span class="token punctuation">{<!-- --></span>
string url <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
string title <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span>
repeated string snippets <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
repeated Result results <span class="token operator">=</span> <span class="token number">16</span><span class="token punctuation">;</span>
repeated Person<span class="token punctuation">.</span>Result results1 <span class="token operator">=</span> <span class="token number">17</span><span class="token punctuation">;</span><span class="token comment">//也可以这样定义</span>
<span class="token comment">/*
使用Any变量,用于定义任意的值
*/</span>
repeated google<span class="token punctuation">.</span>protobuf<span class="token punctuation">.</span>Any details <span class="token operator">=</span> <span class="token number">21</span><span class="token punctuation">;</span>
<span class="token comment">/*
使用Oneof变量
如果你的消息中有很多可选字段, 并且同时至多一个字段会被设置, 你可以加强这个行为,使用oneof特性节省内存.
Oneof字段就像可选字段, 除了它们会共享内存, 至多一个字段会被设置。 设置其中一个字段会清除其它oneof字段。 你可以使用case()或者WhichOneof() 方法检查哪个oneof字段被设置, 看你使用什么语言了.
你可以增加任意类型的字段, 但是不能使用 required, optional, repeated 关键字.
在产生的代码中, oneof字段拥有同样的 getters 和setters, 就像正常的可选字段一样. 也有一个特殊的方法来检查到底那个字段被设置. 你可以在相应的语言API中找到oneof API介绍
设置oneof会自动清楚其它oneof字段的值. 所以设置多次后,只有最后一次设置的字段有值.
*/</span>
oneof test_oneof <span class="token punctuation">{<!-- --></span>
string name <span class="token operator">=</span> <span class="token number">24</span><span class="token punctuation">;</span>
Result sub_message <span class="token operator">=</span> <span class="token number">29</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
}
/*
定义服务
*/
//service SearchService {
// rpc Search (SearchRequest) returns (SearchResponse);
//}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
定义一个消息类型
假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:
syntax = "proto3";
package services;
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
修饰符
字段的修饰符可以是以下值:
- singular(默认):在一个格式良好的消息应该有0个或者1个这种字段(但是不能超过1个)。
- repeated:repeated一般用来定义数组。在一个格式良好的消息中,这种字段可以重复任意多次(包括0次),重复的值的顺序会被保留。默认情况下,标量数值类型的repeated字段使用packed的编码方式。
数据类型
标量数据类型
一个标量消息字段可以含有一个如下的类型:double、float、int32、uint32、uint64、sint32、sint64fixed32、fixed64、sfixed32、sfixed64、bool、string、bytes。
枚举数据类型
当想为一个字段指定某“预定义值序列”中的一个值时,向消息定义中添加一个枚举(enum)类型就可以了。enum类型里的第一个枚举值后面的标识符必须为0。一个enum类型的字段只能用指定的常量集中的一个值作为其值。
你可以为枚举常量定义别名。 需要设置allow_alias option 为 true, 否则 protocol编译器会产生错误信息。
message SearchRequest {
//定义一个枚举类型Corpus
enum Corpus {
option allow_alias = true;
UNIVERSAL = 0;//第一个枚举值,这里的数字必须是0,不然编译不通过
WEB = 1;
//WEB1 = 1;这里编译不通过,数字1只能对应一个枚举值。
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
//定义一个Corpus类型的corpus
Corpus corpus = 4;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
map
如果你希望创建一个关联映射,protocol buffer提供了一种快捷的语法:
map<key_type, value_type> map_field = N;
- 1
其中key_type可以是除了floating和bytes的任意标量类型,value_type可以是任意类型。
例如,如果你希望创建一个project的映射,每个Projecct使用一个string作为key,你可以像下面这样定义:
map<string, Project> projects = 23;
- 1
数据类型的默认值
当一个消息被解析的时候,如果在编码后的消息结构中某字段没有初始值,相应的字段在被解析的对象中会被设置默认值。这些默认值都是类型相关的。
- 字符串默认值为空字符串。
- 字节类型默认值是空字节。
- 布尔类型默认值为 false。
- 数值类型默认值为 0。
- 枚举类型默认值是其定义中的第一个值,它必须为 0。
- 消息类型的默认值没有设置。它的具体值与使用的编程语言有关。
repeated字段的默认值为空(通常是相应编程语言的空列表)。
注意:对于标量消息字段,当消息被解析时,我们没有办法知道某个字段是否被显示地设定为默认值(例如一个布尔类型的字段值是否被设置为 false),也许这个字段压根就没有被设定值。当我们定义一个消息类型时,我们需要牢记这点。例如,如果一个布尔类型的字段在其值被设置为false时,会导致某种行为的发生,而我们并不想让这种行为在默认情况下也会发生,那么我们就不要定义这个bool类型的字段。 还要注意的是,在序列化的时候,如果标量消息字段的值设为默认值,这个值是不会被序列化的。
唯一标识符
在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。最小的标识符可以从1开始,最大到229 - 1, or 536,870,911。不可以使用其中的[19000-19999]的标识符。
注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识符。
切记:要为将来有可能添加的、频繁出现的标识号预留一些标识符。
预留标识符(Reserved)
假如一个旧版本.proto文件将某个字段完全删除或注释,之后生成了一个新版本的.proto文件。用户在对新版本.proto文件进行操作时,可能会重用那些已经被删除的字段的标识符或名称,如果以后加载相同.proto文件的旧版本,这可能会导致严重问题,比如数据损坏。
为了确保不会发生这种情况,我们指定已删除字段的字段编号或字段名称为“reserved”。 如果将来的任何用户尝试使用这些字段标识符,则编译会出错。
message Foo {
reserved 2, 15, 9 to 11;//预留字段名称
reserved "foo", "bar";//预留字段标识符
}
- 1
- 2
- 3
- 4
- 5
注意:不能在同一 “reserved” 语句中将字段名称和字段标识符混合在一起指定。
在一个.proto文件中使用多个message
直接定义多个message
在一个.proto文件中可以定义多个消息类型。你可以将其他message类型用作字段类型。例如,假设在每一个SearchResponse消息中包含Result消息,此时可以在同一个.proto文件中定义一个Result消息类型,然后在SearchResponse消息中指定一个Result类型的字段如:
message SearchResponse {
repeated Result results = 1;
}
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
导入message
在上面的例子中,Result消息类型与SearchResponse是定义在同一文件中的。如果想要使用的message已经在其他.proto文件中已经定义过了呢?
你可以通过导入(importing)其他.proto文件中的定义来使用其他文件的message。要导入其他.proto文件的message的定义,你需要在你的文件中添加一个导入声明,如:
import "myproject/other_protos.proto";
- 1
默认情况下你只能使用直接导入的.proto文件中的定义。然而, 有时候你需要移动一个.proto文件到一个新的位置。现在,你可以在旧位置放置一个虚拟 .proto 文件,以使用命令 import public将所有导入转发到新位置,而不是直接移动 .proto 文件并在一次更改中更新所有调用点。导入包含 import public 语句的 proto 的任何人都可以导入公共依赖项。例如:
// new.proto
// All definitions are moved here
- 1
- 2
// old.proto
// This is the proto that all clients are importing.
import public "new.proto";
import "other.proto";
- 1
- 2
- 3
- 4
// client.proto
import "old.proto";
// You use definitions from old.proto and new.proto, but not other.proto
- 1
- 2
- 3
使用命令 -I/–proto_path
让 protocol 编译器在指定的一组目录中搜索要导入的文件。如果没有给出这个命令选项,它将查找调用编译器所在的目录。通常,你应将 --proto_path 设置为项目的根目录,并对所有导入使用完全正确的名称。
使用protobuf2的message
在你的proto3message中导入proto2的message也是可以的,反之亦然,但是proto2枚举不可以直接在proto3的标识符中使用(如果仅仅在proto2消息中使用是可以的)。
嵌套message
你可以在其他 message 类型中定义和使用 message 类型,如下例所示,此处Result消息在SearchResponse 消息中嵌套定义,SearchResponse 消息为Result消息的父消息。
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如果要在其父消息类型之外重用此消息类型, 使用的格式为Parent.Type:
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
- 1
- 2
- 3
- 4
.proto支持嵌套多层消息:
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;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
Any字段
使用Any,可以定义任意类型的字段。指定消息类型的默认类型URL是type.googleapis.com/packagename.messagename.
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 21;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在c++中有PackFrom()和UnpackTo()方法以typesafe方式打包和解压缩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 ...
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
oneof字段
如果你的 message 包含许多可选字段,并且最多只能同时设置其中一个字段,则可以使用 oneof 功能强制执行此行为并节省内存。
Oneof 共享内存中的所有字段,并且最多只能同时设置一个字段。设置 oneof 的任何成员会自动清除所有其他成员。你可以使用特殊的 case() 或 WhichOneof() 方法检查 oneof 字段中当前是哪个值(如果有)被设置,具体方法取决于你选择的语言。
oneof的使用
要在 .proto 中定义 oneof,请使用 oneof 关键字,后跟你的 oneof 名称,在本例中为 test_oneof:
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
然后,将 oneof 字段添加到test_oneof的定义中。你可以在test_oneof添加任何类型的字段,但不能使用 required,optional 或 repeated 关键字。如果需要向 oneof 添加重复字段,可以使用包含重复字段的 message。
在生成的代码中,oneof 字段与常规 optional 方法具有相同的 getter 和 setter。你还可以使用特殊方法检查 oneof 中的值(如果有)。
oneof的特性
- 设置 oneof 字段将自动清除 oneof 的所有其他成员。因此,如果你设置了多个字段,则只有你设置的最后一个字段仍然具有值。
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
- 1
- 2
- 3
- 4
- 5
- 如果解析器遇到同一个 oneof 的多个成员,则在解析的消息仅使用看到的最后一个成员。
- oneof 不支持扩展
- oneof 不能使用 repeated
- 反射 API 适用于 oneof 字段
- 如果你使用的是 C++,请确保你的代码不会导致内存崩溃。以下示例代码将崩溃,因为已通过调用 set_name() 方法删除了 sub_message。
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name"); // Will delete sub_message
sub_message->set_... // Crashes here
- 1
- 2
- 3
- 4
- 同样在 C++中,如果你使用 Swap() 交换了两条 oneofs 消息,则每条消息将以另一条消息的 oneof 实例结束:在下面的示例中,msg1 将具有 sub_message 而 msg2 将具有 name。
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
- 1
- 2
- 3
- 4
- 5
- 6
- 7
定义服务(RPC)
如果要将 message 类型与 RPC(远程过程调用)系统一起使用,则可以在 .proto 文件中定义 RPC 服务接口,protocol buffer 编译器将以你选择的语言生成服务接口和stub(桩)。因此,例如,如果要定义一个 RPC 服务,其中包含一个根据 SearchRequest 返回 SearchResponse 的方法,可以在 .proto 文件中定义它,如下所示:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
string result = 1;
}
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
与 ProtoBuf 直接搭配使用的 RPC 系统是 gRPC :一个 Google 开发的平台无关语言无关的开源 RPC 系统。gRPC 和 ProtoBuf 能够非常完美的配合,你可以使用专门的 ProtoBuf 编译插件直接从.proto 文件生成相关 RPC 代码。
包(Packages)
你可以将可选的package说明符添加到 .proto 文件作为生成语言的namespace,以防止不同message 类型之间的名称冲突。
package foo.bar;
message Open { ... }
- 1
- 2
你可以在定义 message 类型的字段时使用package说明符:
message Foo {
...
required foo.bar.Open open = 1;
...
}
- 1
- 2
- 3
- 4
- 5
package 对生成的代码的影响取决于你所选择的语言,在 C++ 中,生成的类包含在 C++ 命名空间中。例如,Open 将位于命名空间 foo::bar 中。
Protocol buffer语言中类型名称的解析与C++是一致的:首先从最内部开始查找,依次向外进行,每个包会被看作是其父类包的内部类。当然对于 (foo.bar.Baz)这样以“.”分隔的意味着是从最外围开始的。
ProtocolBuffer编译器会解析.proto文件中定义的所有类型名。 对于不同语言的代码生成器会知道如何来指向每个具体的类型,即使它们使用了不同的规则。
选项(Options)
在定义.proto文件时能够标注一系列的options。Options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式。完整的可用选项可以在google/protobuf/descriptor.proto找到。
一些选项是文件级别的,意味着它可以作用于最外范围,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,意味着它可以用在消息定义的内部。当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并没有一种有效的选项能作用于所有的类型。