由于项目中使用protobuf,因此本文学习总结protobuf-C++的基本使用,试验环境为centos7-x64.
protobuf简介
什么是 Google Protocol Buffer? 假如您在网上搜索,应该会得到类似这样的文字介绍:
Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。
protobuf安装
protobufgit clone或者下载后,执行:
./autogen.sh
./configure --prefix=/usr/local/protobuf
make
make check
make install
以下命令需要root权限,如不是root登录需要sudo
vim /etc/profile
添加
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib
保存执行
source /etc/profile
当然也可以直接执行两次export语句。
此时我们执行protoc --version就可以看到protobuf的版本信息:
[root@centos-linux-7 protobuf]# protoc --version
libprotoc 3.6.0
如果提示命令protoc不存在,则在~/.profile中添加上面两行代码。
配置动态链接库路径
vi /etc/ld.so.conf
插入:
/usr/local/protobuf/lib
执行:
ldconfig
为什么要使用protobuf
假如我们要实现write将把一个结构化数据写入磁盘,以便其他人来读取。如果我们不使用 Protobuf,一个可能的方法是将数据转换为字符串,然后将字符串写入磁盘。转换为字符串的方法可以使用 sprintf(),这非常简单。数字 123 可以变成字符串”123”。
这样做似乎没有什么不妥,但是仔细考虑一下就会发现,这样的做法对read 的那个人的要求比较高。比如”123”可以是单个数字 123,但也可以是三个数字 1,2 和 3,等等。这么说来,我们还必须让 write 定义一种分隔符一样的字符,以便 read可以正确读取。但分隔符也许还会引起其他的什么问题。最后我们发现一个简单的 Helloworld 也需要写许多处理消息格式的代码。
如果使用 Protobuf,那么这些细节就可以不需要应用程序来考虑了。
使用 Protobuf,write的工作很简单,需要处理的结构化数据由 .proto 文件描述,并定义在 lm.helloworld.pb.h 中。对于本例,类名为 lm::helloworld。Writer 需要 include 该头文件,然后便可以使用这个类了。
当我们需要将该结构化数据保存到磁盘上时,类 lm::helloworld 已经提供相应的方法来把一个复杂的数据变成一个字节序列,我们可以将这个字节序列写入磁盘。
对于想要读取这个数据的程序来说,也只需要使用类 lm::helloworld 的相应反序列化方法来将这个字节序列重新转换会结构化数据。这同我们开始时那个“123”的想法类似,不过 Protobuf 想的远远比我们那个粗糙的字符串转换要全面,因此,我们不如放心将这类事情交给 Protobuf 吧。
msg.proto
syntax="proto3";
package lm;
message helloworld
{
int32 id = 1; // ID
string str = 2; // str
int32 opt = 3; //optional field
}
写好 proto 文件之后就可以用 Protobuf 编译器将该文件编译成目标语言了。本例中我们将使用 C++。
使用如下命令可以生成我们所需要的.cc与.h
protoc --cpp_out=. ./msg.proto
执行上述命令将生成msg.pb.cc与msg.pb.h,执行时需要在文件msg.proto所在的目录下执行。
write.cc
#include "msg.pb.h"
#include <fstream>
#include <iostream>
using namespace std;
int main(void)
{
lm::helloworld msg1;
msg1.set_id(101);
msg1.set_str("hello");
fstream output("./log", ios::out | ios::trunc | ios::binary);
if (!msg1.SerializeToOstream(&output)) {
cerr << "Failed to write msg." << endl;
return -1;
}
return 0;
}
Msg1 是一个 helloworld 类的对象,set_id() 用来设置 id 的值。SerializeToOstream 将对象序列化后写入一个 fstream 流。
read.cc
#include "msg.pb.h"
#include <fstream>
#include <iostream>
using namespace std;
void ListMsg(const lm::helloworld & msg) {
cout << msg.id() << endl;
cout << msg.str() << endl;
}
int main(int argc, char* argv[]) {
lm::helloworld msg1;
{
fstream input("./log", ios::in | ios::binary);
if (!msg1.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
}
ListMsg(msg1);
}
对应的Makefile:
all: write read
clean:
@rm -f write read msg.*.cc msg.*.h *.o log
write: msg.pb.cc write.cc
g++ -std=c++11 msg.pb.cc write.cc -o write `pkg-config --cflags --libs protobuf`
read: msg.pb.cc read.cc
g++ -std=c++11 msg.pb.cc read.cc -o read `pkg-config --cflags --libs protobuf`
msg.pb.cc msg.pb.h :msg.proto
protoc --cpp_out=. ./msg.proto
执行make之后,生成可执行文件write 和read.
执行结果:
[root@centos-linux-7 protobuf]# make
make: 对“all”无需做任何事。
[root@centos-linux-7 protobuf]# ./write
[root@centos-linux-7 protobuf]# ./read
101
hello
常见错误及处理:
1.error: #error This file requires compiler and library support for the ISO C++ 2011 standard. This support is currently experimental, and must be enabled with the -std=c++11 or -std=gnu++11 compiler options.
#error This file requires compiler and library support for the
这个错误出现的话需要在Makefile中指定-std=c++11
更多C++的例子可以参考官方文档:protobuf cpptutorial
真的感谢,这算是protobuf最清晰的一个入门介绍了.期待更多这个系列的文章.