参考资料
1.ubuntu安装proto编译器
sudo apt-get install protobuf-compiler
2.Packages
如果 .proto 文件中包含 package 定义,此文件的整个内容将被放置到对应的C++命名空间中。例如,假定 package 声明为:
package foo.bar
则此 .proto 文件中所有声明将都属于命名空间 foo::bar。
3.Massages(相当于类)
比如先定义一个消息声明如下:
massage Person{}
在编译时,protocol buffer编译器自动生成Person类,公开地(publicly)派生自 google::protobuf::Message。
4.字段规则
4.1 required: 必须赋值的字段
4.2 optional: 可有可无的字段,可以使用[default = xxx]配置默认值
如果没有为optional的元素指定默认值,就会使用与特定类型相关的默认值:对string来说,默认值是空字符串。对bool来说,默认值是false。对数值类型来说,默认值是0。对枚举来说,默认值是枚举类型定义中的第一个值。
optional int32 result_per_page = 3 [default = 10]; // 默认值为10
4.3 repeated: 可重复变长字段,类似于C++中的list。
5.示例:addressbook.proto
示例来源
注:Person 内部声明了一个enum和一个message,这类似于C++中的类内声明,Person外部的结构可以用 Person.PhoneType 的方式来使用PhoneType。当使用外部package中的结构时,要使用 pkgName.msgName.typeName 的格式,每两层之间使用’.'来连接,类似C++中的"::"。
// Filename: addressbook.proto
// 使用protobuf的编译器版本为v2
syntax="proto2";
// proto3 已舍弃 required 字段,optional 字段也无法显示使用(因为缺省默认就设置为 optional)
// 声明了一个包名,用来防止不同的消息类型命名冲突,类似于 namespace
package addressbook;
import "src/help.proto"; //举例用,编译时去掉
message Person {
required string name = 1; //字段
required int32 id = 2;
optional string email = 3;
enum PhoneType { //枚举
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME]; //为optional指定默认值
}
repeated PhoneNumber phone = 4;
}
message AddressBook {
repeated Person person_info = 1;
}
6.编译
作用:将 .proto 文件转换成对应平台(python、C++、Java)的代码文件。
编译指令如下所示:
# 在终端输入下列命令进行编译
protoc -I=$SRC_DIR --xxx_out=$DST_DIR $SRC_DIR/addressbook.proto
# 参数说明
# 1. $SRC_DIR:指定需要编译的.proto文件目录 (如没有提供则使用当前目录)
# 2. --xxx_out:xxx根据需要生成代码的类型进行设置
"""
对于 Java ,xxx = java ,即 -- java_out
对于 C++ ,xxx = cpp ,即 --cpp_out
对于 Python,xxx = python,即 --python_out
"""
# 3. $DST_DIR :编译后代码生成的目录 (通常设置与$SRC_DIR相同)
# 4. 最后的路径参数:需要编译的.proto 文件的具体路径
# 编译通过后,Protoco Buffer会根据不同平台生成对应的代码文件
# 一般来说,./代表当前目录,若编译后的.h/.cc仍放在当前目录下,可用以下命令。
protoc -I=./ --cpp_out=./ test.proto
7.编译后
7.1 通过查看头文件,可以发现针对每个字段都会大致生成如下几种函数,以number为例:
// required string number = 1;
inline bool has_number() const;
inline void clear_number();
inline const ::std::string& number() const;
inline void set_number(const ::std::string& value);
inline void set_number(const char* value);
inline ::std::string* mutable_number();
可以看出,对于每个字段会生成一个has函数(has_number)、clear清除函数(clear_number)、set函数(set_number)、get函数(number和mutable_number)。这儿解释下get函数中的两个函数的区别,对于原型为const std::string &number() const的get函数而言,返回的是常量字段,不能对其值进行修改。但是在有一些情况下,对字段进行修改是必要的,所以提供了一个mutable版的get函数,通过获取字段变量的指针,从而达到改变其值的目的。
7.2 而对于字段修饰符为repeated的字段生成的函数,则稍微有一些不同,如phone字段,则编译器会为其产生如下的代码:
// repeated .Person.PhoneNumber phone = 4;
inline int phone_size() const;
inline void clear_phone();
inline const ::google::protobuf::RepeatedPtrField< ::Person_PhoneNumber >& phone() const;
inline ::google::protobuf::RepeatedPtrField< ::Person_PhoneNumber >* mutable_phone();
inline const ::Person_PhoneNumber& phone(int index) const;
inline ::Person_PhoneNumber* mutable_phone(int index);
inline ::Person_PhoneNumber* add_phone();
可以看出,set函数变成了add函数,这个其实很好理解。上面也说过,repeated修饰的字段在高级语言中的实现可能是个数组或动态数组,所以当然通过添加的方式来加入新的字段值。而且get函数也变化很大,这个也不用多说了。