ProtoBuf简述
protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,可用于通信协议、数据存储等。
使用流程:
- 创建 .proto 文件,定义数据结构
- 编译 .proto文件生成接口
- 调用接口实现序列化、反序列化以及读写
安装
不同版本的protobuf无法互相兼容,ROS安装时会一并安装,路径为
/usr/bin/protoc
,若需要依赖其他版本可安装在不同路径下
-
安装依赖
sudo apt-get install autoconf automake libtool curl make g++
-
生成Makefile
./autogen.sh && ./configure
如需多版本并存或修改安装路径,可设置目录
/configure --prefix=/usr/protobuf
-
检查编译结果
make check
-
安装到系统路径下
sudo make install
-
设置系统环境
单版本安装
Ubuntu默认安装在/usr/local/lib目录下
- 创建文件
sudo gedit /etc/ld.so.conf.d/libprotobuf.conf
- 填入内容
/usr/local/lib
(若修改了路径,使用修改过的) - 更新
sudo ldconfig
多版本安装
- 创建软链接
sudo ln -s /usr/protobuf/bin/protoc /usr/local/bin/protoc3.6
- 更新
sudo ldconfig
- 创建文件
-
测试
protoc --version
ProtoBuf语法
-
定义结构体
message <name> {}
- 结构体语法 : 字段规则 类型 名称 = 字段编号
message Person { string name = 1; int32 id = 2; string email = 3; }
-
定义枚举
enum <name> {}
- 枚举语法 : 名称 = 枚举编号
- 枚举常量值必须在32位整数范围内,负值无效
enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; }
-
字段规则
注意:proto3已舍弃required字段,optional字段也无法显示使用(因为缺省默认就设置为optional),同时弃用默认值设置,默认值大多为0、空、false。
- proto2
- [default = XXX] 设置默认值
- required 字段只能也必须出现1次
- optional 字段可出现0次或1次
- repeated 字段可出现任意多次(包括 0),
[packed = true]
选项可提高编码效率
- proto3
- singular 字段可出现0个或1个该字段。这是 proto3 语法的默认字段规则,一般无需声明
- repeated 字段可出现任意多次(包括 0),可视为动态大小的数组
- reserved 字段设置为保留项,主要用于已经删除字段的禁用
- proto2
-
字段类型
protobuf属性 C++属性 备注 bool bool string string 一个字符串必须是utf-8编码或者7-bit的ascii编码的文本,长度不能超过 2^32 bytes string 可能包含任意顺序的字节数据,但长度不能超过 2^32 double double 固定8个字节 float float 固定4个字节 int32 int32 使用变长编码,对于负数编码效率较低,如果经常使用负数,建议使用sint32 int64 int64 使用变长编码,对于负数编码效率较低,如果经常使用负数,建议使用sint64 uint32 uint32 使用变长编码 uint64 uint64 使用变长编码 sint32 int32 使用变长编码,符号整型。负值的编码效率高于常规的 int32 类型 sint64 int64 使用变长编码,符号整型。负值的编码效率高于常规的 int64 类型 fixed32 uint32 定长4字节,如果数据>2^28,编码效率高于unit32 fixed64 uint64 定长8字节,如果数据>2^56,编码效率高于unit64 sfixed32 int32 总是4字节 sfixed64 int64 总是8字节 -
字段编号
- 0 ~ 536870911(除去 19000 到 19999 之间的数字)
- 标签号1-15比起更大数字需要少一个字节进行编码,可将常用或重复的元素设置为此类标签
ProtoBuf生成
通过命令
protoc -I=<SRC_DIR> --cpp_out=<DST_DIR> <SRC_DIR>/xxx.proto
- SRC_DIR: .proto所在的源目录
- –cpp_out: 生成c++代码
- –python_out:生成python代码,
_pb2.py
文件 - DST_DIR: 生成代码的目标目录
- xxx.proto: 要针对哪个proto文件生成接口代码
# 寻找 Protobuf 库
find_package(Protobuf REQUIRED)
# 生成protobuf的lib
file(GLOB PROTO_LIST "proto/*.cc")
add_library(proto
STATIC
${PROTO_LIST})
target_include_directories(proto PUBLIC ${PROTOBUF_INCLUDE_DIRS})
target_link_libraries(proto PUBLIC ${PROTOBUF_LIBRARIES})
# 链接执行文件
add_executable(protobuf_test
src/protobuf_test.cpp)
target_include_directories(protobuf_test PUBLIC ${PROJECT_SOURCE_DIR}/proto)
target_link_libraries(protobuf_test proto)
通过CMake
自动生成对应的C++头文件和源文件
protobuf_generate_cpp (<SRCS> <HDRS>
[DESCRIPTORS <DESC>] [EXPORT_MACRO <MACRO>] [<ARGN>...])
- SRCS Variable to define with autogenerated source files
- HDRS Variable to define with autogenerated header files
- DESCRIPTORS Variable to define with autogenerated descriptor files, if requested.
- EXPORT_MACRO is a macro which should expand to
__declspec(dllexport)
or__declspec(dllimport)
depending on what is being compiled. - ARGN
.proto
files
protobuf消息编译库proto
,后续调用直接使用该库即可。
project(proto)
# 寻找 Protobuf 库
find_package(Protobuf REQUIRED)
# 检测是否找到 Protobuf
if(PROTOBUF_FOUND)
message("** protobuf found")
else()
message(FATAL_ERROR "Cannot find Protobuf")
endif()
# 寻找所有的proto文件
file(GLOB PROTO_FILES ${PROJECT_SOURCE_DIR}/*.proto)
message("** PROTO_FILES = ${PROTO_FILES}")
# Generate the .h and .cxx files 必须配合 add_executable() 或者 add_library() 才能正常使用
protobuf_generate_cpp(PROTOBUF_SRCS PROTOBUF_HDRS ${PROTO_FILES})
# 增加 proto 描述文件的静态库
add_library(${PROJECT_NAME} STATIC ${PROTOBUF_SRCS} ${PROTOBUF_HDRS})
target_include_directories(${PROJECT_NAME} PUBLIC ${PROTOBUF_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} PUBLIC ${PROTOBUF_LIBRARIES})
接口调用
构造protobuf数据
在生成的C++接口内,会根据设置数据类型生成对应的函数接口,singular(单组数据)和repeated(多组数据)定义的类型生成的api存在差异。
singular
-
double/int
// double x = 1; void clear_x(); static const int kXFieldNumber = 1; double x() const; void set_x(double value); // int32 id = 2; void clear_id(); static const int kIdFieldNumber = 2; ::google::protobuf::int32 id() const; void set_id(::google::protobuf::int32 value);
-
bytes/string
在C++中bytes和string的实现都是std::string类型,区别在于string类型序列化调用了
VerifyUTF8StringNamedField
函数检验string中是否有非法的UTF-8字符,而bytes类型没有检验。string类型不能直接通过set赋值,需设置string变量后,将string变量通过set进行赋值。
// bytes data = 1; void clear_data(); static const int kDataFieldNumber = 1; const ::std::string& data() const; void set_data(const ::std::string& value); #if LANG_CXX11 void set_data(::std::string&& value); #endif void set_data(const char* value); void set_data(const void* value, size_t size); ::std::string* mutable_data(); ::std::string* release_data(); void set_allocated_data(::std::string* data);
-
嵌套message
// .test.test_2_msg c = 3; bool has_c() const; void clear_c(); static const int kCFieldNumber = 3; const ::test::test_2_msg& c() const; ::test::test_2_msg* release_c(); ::test::test_2_msg* mutable_c(); void set_allocated_c(::test::test_2_msg* c);
通过
set_allocated_
和mutable_
传入已定义值,在关闭程序时会引起的double free core dump,参考c++ Protobuf中set_allocated引起的double free core dump正确定义:
int good_case1(){ a* aa = new a(); b bb; aa->set_aa(1); bb.set_allocated_aaa(aa); return 0; }
void good_case2(){ a aa; b bb; aa.set_aa(1); bb.mutable_aaa()->MergeFrom(aa); }
-
descriptor
#include <google/protobuf/message.h> namespace google::protobuf const Descriptor* descriptor = msg->GetDescriptor();
repeated
-
double/int
// repeated int32 e = 5; int e_size() const; void clear_e(); static const int kEFieldNumber = 5; ::google::protobuf::int32 e(int index) const; void set_e(int index, ::google::protobuf::int32 value); void add_e(::google::protobuf::int32 value); const ::google::protobuf::RepeatedField< ::google::protobuf::int32 >& e() const; ::google::protobuf::RepeatedField< ::google::protobuf::int32 >* mutable_e();
-
嵌套message
// repeated .test.test_2_msg d = 4; int d_size() const; void clear_d(); static const int kDFieldNumber = 4; ::test::test_2_msg* mutable_d(int index); ::google::protobuf::RepeatedPtrField< ::test::test_2_msg >* mutable_d(); const ::test::test_2_msg& d(int index) const; ::test::test_2_msg* add_d(); const ::google::protobuf::RepeatedPtrField< ::test::test_2_msg >& d() const;
序列化和反序列化
调用接口实现序列化、反序列化以及读写
-
序列化
-
bool SerializeToString(string* output) const
序列化消息并将字节存储在给定的字符串中。请注意,字节是二进制的,而不是文本,只是使用
string
类作为容器。文件中显示为乱码,通过gedit修改会损坏数据,可以通过vim修改。
-
bool SerializeToOstream(ostream* output) const
将message写入给定的C++的ostream
-
-
反序列化
-
bool ParseFromString(const string& data)
解析给定字符串到message
-
bool ParseFromIstream(istream* input)
解析给定C++ istream到message
-