介绍
protobuf是用来对数据进行序列化和反序列化的灵活,高效,自动化的解决方案。
序列化:将数据结构转换成二进制的字节串
反序列化:将二进制串还原成数据结构
Ubuntu下编译安装
尝试安装最新版本-v3.22.1(没成功)
参照文档的安装过程
github-protocol-readme
这里在Linux下使用cmake构建,找到readm中指示的cmake的readme.md
protocol-cmake-readme
#下载源码和子模块
$ git clone https://github.com/protocolbuffers/protobuf.git
$ cd protobuf
$ git submodule update --init --recursive
#在源码路径下开始编译
$ cmake .
$ cmake --build . --parallel 10
#安装
$ sudo make install
使用:
编译时一直报错:asbl::相关的undefined,没有找到原因。
也许不应该编译submodule中的abseil-cpp模块
换版本安装-v3.20.1
网友们使用的版本大都是带初始化脚本然后再编译,我也尝试下载之前的版本进行编译
去proto git的release界面下载了v3.20.1版本源码
将之前版本残留的文件删除,默认在/usr/local/的include\lib\bin目录下
$ ./autogen.sh
报错
./autogen.sh: 41: autoreconf: not found
安装前置
$ sudo apt-get install autoconf automake libtool
##
$ ./autogen.sh
$ ./configure --prefix=/usr/local/comenv/protobuf
$ make -j4
$ sudo make install
#可以看到在指定路径下生成了lib\bin\include三个目录
#配置系统环境 or 配置工程使用环境
$ sudo vi /etc/profile
#添加:
export PATH=$PATH:/usr/local/comenv/protobuf/bin/
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/comenv/protobuf/lib
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/comenv/protobuf/lib
export CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/local/comenv/protobuf/include
export PKG_CONFIG_PATH=/usr/local/comenv/protobuf/lib/pkgconfig
#PATH 可执行文件查找目录
#LD_LIBRARY_PATH 动态链接器(ld)查找动态库的路径,即执行期间的库文件查找路径
#LIBRARY_PATH 编译期间库查找路径
#CPLUS_INCLUDE_PATH g++头文件查找路径
#PKG_CONFIG_PATH pc文件查找路径
# 或者不修改环境,只修改自己工程的查找路径
# 使用时也可以只修改工程编译选项的库、头文件路径,只是在代码IDE里就没有提示、跳转识别等
$ source /etc/comenv
or
命令行安装-没试过
sudo apt insatll protobuf-compiler libprotobuf-dev
验证
$ protoc --version
libprotoc 3.20.1
使用
一般步骤
以C++为例
- 创建proto文件,定义数据格式等
- 使用protoc工具,将proto文件编译生成一个.h和.cc文件,将源\头文件拷贝至工程使用
- 使用protobuf的接口,实现序列号、反序列化
protoc编译工具
$ protoc --cpp_out=./xxx/ ./xxx.proto
#--cpp_out表示将proto文件编译出c++使用的.cc和.h,并指定文件输出的路径
#最后一个参数是proto文件路径
#编译之后,程序包含生成的cc和h文件即可使用
proto文件
syntax = "proto3";
package SCH; //整个文件的包名,对应到C++里是这个文件所有的message类都在这个命名空间里
message Student{
//选项 类型 名称 = 标号
uint64 id = 1;
string name =2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
}
message PhoneNumber {
string number = 1;
optional PhoneType type = 2;
}
repeated PhoneNumber phone = 4;
message Remark {
string describe = 1;
int32 rank = 2;
}
Remark remark = 5;
}
-
syntax 使用的proto的版本,2和3有一些语法差别,不写默认为版本2
-
package 防止不同项目之间的命名冲突。对应到C++中,这个文件生成的类将被放置在一个与package名相同的命名空间中。
-
message 消息结构定义,一个message就是一些字段的集合,字段可以是bool\int32\double\string等基础类型,也可以是其他message类型。这里Student和PhoneNumber都会在程序中作为类名。
- message中,字段的格式为:修饰符 类型 名称 = 标识;
-
修饰符 message中的每个字段都有一个修饰符(可以缺省)
- optional 可选的,加入这个标识,会生成has_xx的接口
- repeated 可重复的(可以理解为不定长的数组)
-
标识 该字段在二进制编码中使用的唯一“标识(tag)”。标识号1-15编码所需的字节数比更大的标识号使用的字节数要少1个,所以,如果你想寻求优化,可以为经常使用或者重复的项采用1-15的标识(tag),其他经常使用的optional项采用≥16的标识(tag)。在重复的字段中,每一项都要求重编码标识号(tag number),所以重复的字段特别适用于这种优化情况。
调用接口
查看头文件中字段生成的接口:
//这里省略了很多
// uint64 id = 1;
void Student::clear_id();
uint64_t Student::id();
void Student::set_id(uint64_t value);
// string name = 2;
inline void Student::clear_name();
inline const std::string& Student::name();
template <typename ArgT0, typename... ArgT>
inline void Student::set_name(ArgT0&& arg0, ArgT... args);
inline std::string* Student::mutable_name();
inline std::string* Student::release_name();
// repeated .SCH.Student.PhoneNumber phone = 4;
inline int Student::phone_size()
inline void Student::clear_phone()
inline ::SCH::Student_PhoneNumber* Student::mutable_phone(int index)
inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SCH::Student_PhoneNumber >* Student::mutable_phone()
inline const ::SCH::Student_PhoneNumber& Student::phone(int index)
inline ::SCH::Student_PhoneNumber* Student::add_phone()
inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::SCH::Student_PhoneNumber >& Student::phone()
- 普通字段都有set_xxx、clear_xxx和xxx的接口,xxx是定义的字段名称,set_id()就是给id字段赋值,id()就是获取id字段的值
- string字段比普通字段多了mutable_xxx的接口,nutable_name()返回name字段buf的指针,为空时调用这个接口会初始化一个空字符串
- 可重复字段,多了xxx_size和add_xxx接口,取消了set_xxx接口。并且xxx()和mutable_xxx接口有了重载,可以根据index索引来获取/设置指定位置的内容。add_phone接口返回一个初始化后的字段指针,并添加到phone buf中,利用这个指针进行赋值。
标准接口
string DebugString() const; //将消息内容以可读的方式输出
bool SerializeToString(string* output) const; //将消息序列化并储存在指定的string中。注意里面的内容是二进制的,而不是文本;我们只是使用string作为一个很方便的容器。
bool ParseFromString(const string& data); //从给定的string解析消息。
bool SerializeToArray(void * data, int size) const //将消息序列化至数组
bool ParseFromArray(const void * data, int size) //从数组解析消息
bool SerializeToOstream(ostream* output) const; //将消息写入到给定的C++ ostream中。
bool ParseFromIstream(istream* input); //从给定的C++ istream解析消息。
序列号、反序列化示例
#include <iostream>
#include <string>
#include "hello.pb.h"
//序列化
int serialize(std::string &resStr) {
SCH::Student stu;
//向对象中填值
stu.set_id(23456); //普通成员会有set函数来赋值
stu.set_name("stu1");
*stu.mutable_email() = "[email protected]"; //string成员会多一些接口,mutable会返回指针
//嵌套的重复成员:
SCH::Student::PhoneNumber* pNum1 = stu.add_phone();
pNum1->set_number("1812435454");
pNum1->set_type( SCH::Student::MOBILE);
SCH::Student::PhoneNumber* pNum2 = stu.add_phone();
pNum2->set_number("1234567");
pNum2->set_type( SCH::Student::HOME);
//嵌套的单次成员
stu.mutable_remark()->set_describe("ok");
stu.mutable_remark()->set_rank(1);
//将stu序列化到string中,string中数据已经是二进制的格式,string只是它的容器
stu.SerializePartialToString(&resStr);
std::cout << "Serialize, debug string: \n" << stu.DebugString() << std::endl; //按照结构输出
return 0;
}
//反序列化
int deserialize(std::string &str) {
SCH::Student stu;
if ( false == stu.ParseFromString(str) ) {
std::cout << "parse failed" << std::endl;
return -1;
}
std::cout << "Deserialize, debug string: \n" << stu.DebugString() << std::endl;
std::cout << "-------------" << std::endl;
if (stu.has_email()) { //设置为optional的字段才会有这个接口
std::cout << "email: " << stu.email() << std::endl;
}
std::cout << "id: " << stu.id() << std::endl;
for (int i=0; i<stu.phone_size(); i++) {
SCH::Student::PhoneNumber pnum = stu.phone(i);
pnum.has_type();
std::cout << "phone: " << pnum.type() << "-" << pnum.number() << std::endl;
}
return 0;
}
int main() {
std::string SerializeStr;
serialize( SerializeStr);
std::cout << "----------" << std::endl;
deserialize(SerializeStr);
return 0;
}
编译选项需要链接:
-lprotobuf -lpthread
参考
腾讯云 - Dabelv
csdn - freshman94