由于项目中使用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