您的位置:首页 > 编程语言 > C语言/C++

Protocol Buffer C++实践

2016-07-22 16:06 411 查看

Protocol Buffer C++实践

Protocol Buffer C实践
说明

Protocol Buffer 技术详解

安装 Google Protocol Buffer

Protocol Buffer C基础
第一步在proto文件中定义消息格式

第二步编译 proto 文件
单字型数值字段 proto2

单字符串字段 proto2

重复型数字字段数组型数字字段proto2

重复型字符串字段 数组型字符串字段 proto2

第三步编写 writer 和 Reader

说明

Protocol Buffer的C++实践。本文侧重于实践,先自行学习推荐的资料学习基本的Protocol Buffer知识。

Protocol Buffer 技术详解

本文侧重于实践,基本的理论只要看下面推荐的blog就可以了,推荐一些Protocol Buffer 技术详解的极好的资料:

中文资料:

Protocol Buffer技术详解(语言规范)

Protocol Buffer技术详解(C++实例)

Protocol Buffer技术详解(数据编码)

英文资料:

毫无疑问最佳资料是Protocol Buffer官网

安装 Google Protocol Buffer

点击下面链接下载最新版本

protobuf-2.6.1.tar.gz

上面下载太慢就点我

下载完毕,需要解压

tar -xzf protobuf-2.6.1.tar.gz


然后,编译安装便可以使用它了

cd protobuf-2.6.1
sh ./configure --prefix=$INSTALL_DIR
make
make check
make install


安装完毕之后,可以查看版本信息

protoc --version


Protocol Buffer C++基础

Protocol Buffer C++实践,将包含以下三个方面:

在.proto文件中定义消息格式

使用protocol buffer 编译程序进行编译

使用protocol buffer的接口来写和读消息

下面将以一个例子对上面三个方面进行说明。

本文使用的源代码下载

第一步,在.proto文件中定义消息格式

下面定义的是Caffe深度学习框架的Datum,命名为caffe.proto

//caffe.proto
package caffe;
//Specifies the Datum
message Datum {
optional int32 channels = 1;
optional int32 height = 2;
optional int32 width = 3;
// the actual image data, in bytes
optional bytes data = 4;
optional int32 label = 5;
// Optionally, the datum could also hold float data.
repeated float float_data = 6;
// If true data contains an encoded image that need to be decoded
optional bool encoded = 7 [default = false];
}


具体的细节,学习了推荐的资料,基本上没有任何问题的。

第二步,编译 .proto 文件

将.proto格式的文件用 Protobuf 编译器编译成目标语言。

关键的命令是:

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/caffe.proto


.proto 文件存放在 $SRC_DIR 下面,生成的文件在$DST_DIR目录下。

这里需要的C++类,使用cpp_out选项,类似的选择可以提供其他支持的语言。

命令将生成两个文件:

caffe.pb.h 定义了 C++ 类的头文件

caffe.pb.cc C++ 类的实现文件

这里,在和caffe.proto同一目录下写了一个脚本compile.sh,将输出文件也设置在这一目录下:

//compile.sh
#! /bin/sh
SRC_DIR=./
DST_DIR=./
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/caffe.proto


所以,只要在终端运行
sh compile.sh
命令即可。

这里介绍protocol buffer 编译器产生的代码。

对于给出一个简单的消息声明:

message Datum {...}


protocol buffer 编译器生成一个称为Datum的类,公有继承与 google::protobuf::Message类,见编译生成的caffe.pb.h文件中:

class Datum : public ::google::protobuf::Message {
public:
Datum();//默认构造函数。
virtual ~Datum();//默认析构函数。
Datum(const Datum& from);//复制构造函数。
//赋值操作符
inline Datum& operator=(const Datum& from) {
CopyFrom(from);
return *this;
}

//与另一个消息交换内容。
void Swap(Datum* other);
...
}


除了上面中描述的方法外,对在消息的原始文件中定义的每个字段(field ),protocol buffer编译器会生成一系列的访问方法。

单字型数值字段 (proto2)

对于这些字段定义中的任何一个:

optional int32 foo = 1;
required int32 foo = 1;


编译器将生成以下的访问方法:

bool has_foo() const: 检查字段是否被设置,如果字段被设置,则会设置为true

int32 foo() const: 返回当前字段的值

void set_foo(int32 value): 设置字段的值

void clear_foo(): 清除字段的值,这时has_foo() 返回false 而foo() 返回默认值

可以查看之前定义的optional int32 channels = 1;

// optional int32 channels = 1;
//has_channels()检查字段是否被设置
inline bool has_channels() const;
inline void clear_channels();
static const int kChannelsFieldNumber = 1;
inline ::google::protobuf::int32 channels() const;
//set_channels 设置字段的值
inline void set_channels(::google::protobuf::int32 value);


单字符串字段 (proto2)

对于单字符串字段 (proto2)

optional string foo = 1;
required string foo = 1;
optional bytes foo = 1;
required bytes foo = 1;


也会有类似上面的has_foo()等函数,这里只介绍编译器生成以下的访问方法:

// 使用C风格的NULL终止字符串设置字段的值。
//has_foo()将返回true,foo()将返回值的copy。
void set_foo(const char* value);
//像上面一样,但字符串大小显式给出,
//而不是通过寻找一个空终止符字节来确定。
void set_foo(const char* value, int size);


重复型数字字段(数组型数字字段proto2)

对于这个字段定义:

repeated int32 foo = 1;


这里介绍编译器将生成的和设置数值相关的访问方法:

//返回当前字段中的元素的数量。
int foo_size() const
//返回给定索引index对应的元素,index是基于零的。
int32 foo(int index) const
//设置给定索引index对应的元素的值value
void set_foo(int index, int32 value)
//添加一个新的元素的值到字段。
void add_foo(int32 value)
//移除字段中的所有元素。之后,foo_size()将返回零。
void clear_foo()
//返回潜在repeatedfield,它存储字段的元素。
//这个容器类提供类似STL的迭代器和其他方法。
const RepeatedField<int32>& foo() const
//类似上面,返回的是一个指针,可修改repeatedfield
RepeatedField<int32>* mutable_foo()


重复型字符串字段 (数组型字符串字段 proto2)

对于这些字段定义中的任何一个:

optional string foo = 1;
required string foo = 1;
optional bytes foo = 1;
required bytes foo = 1;


基本的函数功能是类似上面重复的数字字段访问函数的,这里只介绍编译器生成以下的访问方法:

//返回给定的基于零的索引index的元素。
const string& foo(int index) const
//在给定的基于零的索引index处设置元素的值
//注意是使用C风格的零终止字符串
void set_foo(int index, const char* value)
//像上面一样,但字符串大小显式给出,
//而不是通过寻找一个空终止符字节来确定。
void set_foo(int index, const char* value, int size)


第三步,编写 writer 和 Reader

使用下面命令创建writer.cpp和reader.cpp。

touch writer.cpp
touch reader.cpp


Writer 将把一个结构化数据写入磁盘,以便其他人来读取。

使用 Protobuf,Writer 的工作将变得很简单,需要处理的结构化数据由 .proto 文件描述,经过上一节中的编译过程后,该数据化结构对应了一个 C++ 的类,并定义在 caffe.pb.h 中。对于本例,类名为 Caffe::Datum。

Writer 需要 include 该头文件,然后便可以使用这个类了。

现在,在 Writer 代码中,将要存入磁盘的结构化数据由一个 Caffe::Datum 类的对象表示,它提供了一系列的 get/set 函数用来修改和读取结构化数据中的数据成员,或者叫 field。

当我们需要将该结构化数据保存到磁盘上时,类Caffe::Datum 已经提供相应的方法来把一个复杂的数据变成一个字节序列,我们可以将这个字节序列写入磁盘。

对于想要读取这个数据的程序来说,也只需要使用类 Caffe::Datum 的相应反序列化方法来将这个字节序列重新转换会结构化数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Protobuf