syntax
第一个非注释的语句必须是syntax ,它表示使用什么版本的语法,如果没有使用默认是proto2版本语法
Message
Message含义
Mesage就像c++的一个类,其列(field)变量就像它的成员,当列变量是整形(内置类型)时,它就像有个相应变量名的函数,当列变量是Message类型时,它就像有个成员变量,这个成员变量也是一个类。
Message语法
message Foo{
关键字 类型 变量名 = field_number;
enum Color{
BLACK=0;
RED=1;
}
oneof test_oneof{
类型 变量名 = field_number;
}
map<KeyType,ValueType> object \
= field_number;
required关键字
在使用protobuf编译器生成的文件中,必须去设置这个值
optional关键字
这个在使用这个关键字的时候,可以设置一次或者不设置它的值,不能设置多次。
对于optional 关键字,它还有一个独特的功能就是设置默认值
message Foo{
optional string name = 1[default = "YuTian"];
}
repeated关键字
这个关键字表示可以设置多个值,它的使用就像数组一样,大家可以试试看看生成代码的接口。还有一点就是对于这个接口大家使用的时候,对于标量类型的时候,其后面应该加上[packed=true]来增加其编码性能
repeated int32 samples = 4 [packed=true];
reserved Values用法
当我们使用枚举类型在protobuf中的时候,发现以前的设计不合理我们可能会屏蔽或者删除掉标识符或者变量,这个时候可能会发生一些bug。
old.proto
message Car{
optional int32 far = 1 [default = 2];
}
now.proto
message Car{
// optional int32 far =1 [default = 2];
}
future.proto
message Car {
optional int32 far = 1 [default = 10];
}
正确姿势
message Car {
reserved "far";
reserved 1,2,3 to10 ;
}
使用reserved 修饰后未来在使用这个标识符就会编译报错,所以它的语义表示被保留的我们不能随意使用
field_number
field_number是用Varint编码的,所以它的值越大越占用编码,最小为1,最大为2^29 - 1 ,其中19000 - 19999 是不能定义的,因为这个是保留给编译器的
Message可定义的类型
标量
枚举
1.它的成员必须是 int32位,它是采用Varint编码的,所以负数很浪费空间。
2.第二个它可以在一个Message内定义,在另一个message内使用,方法是Message.enum
3.它支持不同的常量赋同一个值,但是前提是设置了allow_alias选项
enum EnumAllowingAlias {
option allow_alias = true;
UNKNOWN = 0;
STARTED = 1;
RUNNING = 1;
}
枚举的reserved使用
这个跟 reserved的含义一样。如果我们删除某个枚举成员,那么未来的开发者可能重用这个枚举成员,这个时候可能就造成 bug , 所以我们可以使用reserved 修饰。
enum Old_Color{
BLACK = 0;
RED = 1;
}
enum Now_Color{
RED = 1;
}
enum New_Color{
RED = 1;
BLACK = 10;
}
当未来的开发者写的新代码读到老的 protoc文件就会出现 bug…
Message 本身
Message本身也可以定义成一个内部变量
Message teacher{
}
Message school{
optional teacher Peter = 1;
}
生成的代码大概是这样的:
class school{
private:
class teacher Peter_;
}
嵌套定义
嵌套定义的语法就很像c++ 的内部类
message SearchResponse {
message Result {
required string url = 1;
optional string title = 2;
repeated string snippets = 3;
}
}
message SomeOtherMessage {
optional SearchRespons.Result result = 1;
}
message Zoo {
message Cat {
}
}
message Home {
message Cat {
}
}
oneof 类型
它很类似于Union类型,其次需要注意的点:
- 需要注意的点是它内部定义的成员不能有关键字修饰
- 当多次设置该内容的时候,会自动清理前面已经设定过
- 可以用 CHECK宏查看是否设置值
- 在c++中可以使用swap 来交换同一 message 类型内定义的 Oneof变量的内容
Message 对于oneof 类型生成的代码,在生成的时候它首先定义一个相应Union的成员变量,然后通过Message的成员函数来设置这个成员变量Union的内容。
message SampleMessage{
oneof test_oneof{
string name = 1;
int id = 2;
}
}
main.cc
SampleMessage message;
SampleMessage message2;
message.set_name("name");
message.set_id(1);// message 的test_oneof内容变为int 1
message2.swap(&message);
Map 类型
map < key_type,value_type> dict = 1;
- key_type 只能是标量类型(但是浮点数和 bytes除外)
- value_type 可以是任何类型除了map自己
- 不能被关键字修饰
- 在c++中map的使用方法跟std::map很像,甚至可以做转化
message Test{
map<int32,int32> weight = 1;
}
main.cc :
Test mymessage;
std::map<int32_t,int32_t> standard_map(mymessage.weight.begin(),mymessage.weight.end()); //深拷贝
google::protobuf::Map<int32,int32> StdmaptoProtoMap(standard_map.begin(),standard_map.end());
5 Map生成的c++代码
template<typename Key, typename T> {
class Map {
// Member types
typedef Key key_type;
typedef T mapped_type;
typedef MapPair< Key, T > value_type;
// Iterators
iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;
iterator end();
const_iterator end() const;
const_iterator cend() const;
// Capacity
int size() const;
bool empty() const;
// Element access
T& operator[](const Key& key);
const T& at(const Key& key) const;
T& at(const Key& key);
// Lookup
int count(const Key& key) const;
const_iterator find(const Key& key) const;
iterator find(const Key& key);
// Modifiers
pair<iterator, bool> insert(const value_type& value);
template<class InputIt>
void insert(InputIt first, InputIt last);
size_type erase(const Key& Key);
iterator erase(const_iterator pos);
iterator erase(const_iterator first, const_iterator last);
void clear();
// Copy
Map(const Map& other);
Map& operator=(const Map& other);
}
packages
packages使用场景
一般用于当import其他 .proto文件的时候。这个时候可能import的文件跟当前文件有message 的命名冲突的时候,protoc编译会报错说有重复命名message,这个时候如果我们在其中一个.proto文件前加 package xxx.xxx 即可。被添加package的文件内的message是这样生成代码的:
test.protc
package foo.bar;
message Open{}
test.pb.h :
namespace foo{
namespace bar{
class Open;
}
}
可见把包的命字都当做成了一个个作用域
package 的用法
test1.protoc
syntax="proto2";
package foo.bar;
message Open{
}
test2.protoc
syntax="proto2";
message Foo{
foo.bar.Open object = 1;
}
package 的一些规则
- 一个.proto文件只能定义一个package
- 子包的定义方法是import父包文件,然后再这样定义 package parent.child
test.proto:
package Parent;
test2.proto:
import "test.proto";
package Parent.child;
package 中的变量命名解析
它遵循于c++的命名解析即局部作用域绑定,也就是不同作用域出现相同的标识符,它绑定离它最近的。那么 packages 的命名解析(绑定)的时候也是这个原理,它先从子包查找相应的变量名绑定,如果找到了就不在父包查找。
test.proto :
package foo;
message Open{
}
test.proto :
package foo.bar;
message Open{
}
message Close{
.foo.bar.Open object = 1;
}
Extensions
它是messages里面的占符位。一般是如下格式,使用extensions的时候要求这些数字不能是原有message已定义的field_number。它一般用于当一个 .proto文件 import 一个其他.proto文件的时候,可以扩展其他.proto文件中的message里extensions 声明的那些field_number(field)。所以我们 extensions 的这些数字都是用于其他第三方.proto文件的扩展。不直接扩展是为了防止在扩展的时候,写了重复的field_number而发生错误。
A.proto
message Foo{
extensions 100 to 199;
}
B.proto
extend Foo {
optional int32 bar = 199;
}
#include"B.pb.h"
int main()
{
Foo object;
object.SetExtension(bar,20);
return 0;
}
使用extension生成的c++代码和普通的message生成的c++代码使用方法有些不同
如上面定义
Foo object;
object.SetExtension(bar,20);
嵌套Extensions
扩展也可以在类内部进行扩展,但是使用的时候得加上相应类的作用域
test.proto :
message Foo{
extensions 100 to 199;
}
test2.proto:
message Baz {
extend Foo{
optional int 32 bar = 126;
}
}
Foo foo;
foo.SetExtension(Baz::bar , 15);
options
它分为 file-level , message-level ,field-level 。file-level 只能放入文件顶部,不可放入message/service/enum等内。
file-level
optimize_for 它可被设置以下值
1. SPEED : 默认这个选项,它生成高度优化的代码
2. CODE_SIZE : 生成的代码量少于SPEED,但速度慢于SPEED,适用场景一个app有大量的.proto文件,但它不要求所有的.proto文件都生成最快的代码,就可以设置这个选项以此节约硬盘
3. LITE_RUNTIME : 选择这个选项,protobuf 的编译器将会使用 libprotobuf-lite.so ,它比原本的动态库更小而且它的速度跟原本SPEED下的一样,但是它会缺少一些属性像 reflection(反射) 和 descriptors 。
option optimize_for = CODE_SIZE;
4.cc_enable_arenas 这个只针对c++,表示是否打开protobuf的空间配置器选项
message-level
1.message_set_wire_format 一般用户用不到,它只是Google内部为了兼容旧的格式(MessageSet)
field-level
- packed : 它用于一个被repeated修饰的数字变量,当打开这个选项的时候,编码的效率会更高。但是在 2.3.0之前不安全,在2.3.0后很安全。
repeated int32 a = 1;[packed = true]
import
这个是导入其他.proto文件的时候使用的语法
syntax="proto2";
import "other.proto";
message Teacher {
}
import public
有的时候被导入的文件如果想换一个位置,那么我们需要一个一个更新使用到这个被导入文件的文件
//client.proto
syntax = "proto2";
import "old.proto";
.....
cp old.proto New_Dir/old.proto
如上面这个列子,我们可以看出是需要一个一个更新使用到这个文件的文件的。
那 import public 的作用就出来了,它就是专门为了来处理这个场景的。import public 可以用来转发到新的位置
//client.proto
syntax = "proto2";
import "old.proto";
.....
cp old.proto New_Dir/old.proto
//old.proto
import public "New_Dir/old.proto";
....
如果 protobuf 想向后兼容需要做的一些事情
- 别修改已存在的列变量
- 新添加新的列变量,可以用 optional or repeated 修饰。
- 非required 字段可以用reservered 删除
- int32, uint32, int64, uint64, 和 bool 可以互相兼容,这个就像c++的类型转换
- sint32 , sint64 可以互相转换但是和其他类型不能转换
proto2 与 proto3
- proto2 与 proto3 的message 类型可以互相import 来使用,但是 proto2版本的枚举类型不能使用在proto3中
- proto3只有俩个关键字修饰符,singular 与 repeated(默认packed = true 在proto3中) 。singular 和 optional 是一样的,required 被取消了。
- proto3中定义的列变量不是必须得使用关键字修饰符去修饰,这个在proto2中是必须的
- proto3中关键字修饰符只是附加的属性,如果想组成一个复杂的类型,可以使用它 5.