0. 前言
Protocol Buffer是一个跨语言、跨平台、可扩展的用于序列化和结构化数据的工具,常用于用于通信协议,数据存储等。值得注意的是,protobuf是以二进制来存储数据的。相对于JSON和XML具有以下优点:
- 1,简洁
- 2,
体积小 :消息大小只需要XML的1/10 ~ 1/3; - 3,
速度快 :解析速度比XML快20 ~ 100倍; - 4,json\xml都是基于文本格式,protobuf是二进制格式;
- 5,更好的兼容性,Protobuf设计的一个原则就是要能够很好的支持向下或向上兼容。
Protobuf 有两个大的版本:proto2 和 proto3,类似于python 的 2.x 和 3.x 版本,如果是新接触的话,同样建议直接入手 proto3 版本。proto3 相对 proto2 而言,支持更多的语言(Ruby、C#等)、删除了一些复杂的语法和特性、引入了更多的约定等。
protobuf 是用来对数据进行序列化和反序列化。
- 序列化 (Serialization):将数据结构或对象转换成二进制串的过程。
- 反序列化(Deserialization):将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
参考资料如下:
1. 安装
sudo apt-get install autoconf automake libtool curl make g++ unzip
git clone https://github.com/protocolbuffers/protobuf.git
cd protobuf
git submodule update --init --recursive
./autogen.sh
./configure
make
make check
sudo make install
sudo ldconfig
protoc -h
2. ProtoBuf数据类型
下面只展示了C++的数据类型。其它语言的类型大同小异,详情可以查阅官方。
proto文件消息类型 | C++ 类型 | 说明 |
---|
double | double 双精度浮点型 | | float | float | 单精度浮点型 | int32 | int32 | 使用可变长编码方式,负数时不够高效,应该使用sint32 | int64 | int64 | 同上 | uint32 | uint32 | 使用可变长编码方式 | uint64 | uint64 | 同上 | sint32 | int32 | 使用可变长编码方式,有符号的整型值,负数编码时比通常的int32高效 | sint64 | sint64 | 同上 | fixed32 | uint32 | 总是4个字节,如果数值总是比2^28大的话,这个类型会比uint32高效 | fixed64 | uint64 | 总是8个字节,如果数值总是比2^56大的话,这个类型会比uint64高效 | sfixed32 | int32 | 总是4个字节 | sfixed64 | int64 | 总是8个字节 | bool | bool | | string | string | 一个字符串必须是utf-8编码或者7-bit的ascii编码的文本 | bytes | string | 可能包含任意顺序的字节数据 |
2. 一个简单例子
2.1 proto文件格式讲解
我们先来看一个简单例子:simple.proto
syntax = "proto3";
package tutorial;
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
-
第一行指定protobuf的版本 ,这里是以proto3格式定义。还可以指定为proto2。如果没有指定,默认以proto2格式定义 。新版的proto必须指定是proto2还是proto3。 -
.proto 文件以一个 package 声明开始,这个声明是为了防止不同项目之间的命名冲突。类似于C++中的命名空间。 -
每一个字段都要求编码标识号,且唯一 。 -
它定义了一个message类型: SearchRequest, 它包含三个字段(field):query、page_number、result_per_page -
message会被编译成不同的编程语言的相应对象,比如C++中的class 、Go中的struct等。 -
字段前面可以添加修饰,比如required、optional和repeated等。
- required:必须提供字段值,否则对应的消息会被认为是“未初始化的”。
- optional:字段值指定与否都可以。如果没有指定一个 optional 的字段值,它就会使用默认值。
- repeated:字段会重复 N 次,类似于动态数组。
2.2 proto文件运行
在当前的目录下执行:
protoc -I=. -I/usr/local/include --cpp_out=. simple.proto
可以将这个proto编译成C++的代码 ,因为这里我们使用了C++输出格式。go_out用来生成GO代码,java_out产生Java代码,python_out产生python代码,类似地还有csharp_out、objc_out、ruby_out、php_out等参数。
生成的代码我们指定放在本地文件夹中(–cpp_out=.)。这里用.来表示本地文件夹 。
会生成simple.pb.cc和simple.pb.h文件,如下图所示:
- simple.pb.cc:生成类的头文件
- simple.pb.cc:类的实现
3. proto函数解释
3.1 生成的函数
以string password = 3;为例:
- clear_password:清空设置
- password:获得该字段
- set_password:设置该字段
- mutable_password:返回指向该字段的一个指针
- has_password:是否set过
3.2 标准消息函数(Standard Message Methods)
bool IsInitialized() const;
void CopyFrom(const Person& from);
void Clear();
int ByteSize() const;
3.3 关于 Debug 的 API
string DebugString() const;
string ShortDebugString() const;
string Utf8DebugString() const;
void PrintDebugString() const;
3.4序列化与反序列化
bool SerializeToString(string* output) const;
bool ParseFromString(const string& data);
bool SerializeToArray(void * data, int size) const
bool ParseFromArray(const void * data, int size)
bool SerializeToOstream(ostream* output) const;
bool ParseFromIstream(istream* input);
4. 简单的序列化与反序列化实例
4.1 写project.proto并编译出cc和h文件
syntax = "proto3";
message Account {
uint64 ID = 1;
string name = 2;
string password = 3;
}
编译出pb.cc和pb.h文件
protoc -I=. -I/usr/local/include --cpp_out=. project.proto
4.2 序列化与反序列化使用
#include <iostream>
#include <string>
#include "project.pb.h"
int main()
{
Account account;
account.set_id(1000);
account.set_name("name");
account.set_password("password");
std::string s = account.SerializeAsString();
if(s.size() == 0) {
std::cout << "error in SerializeAsString" << std::endl;
}
Account nAccount;
if(nAccount.ParseFromString(s)) {
std::cout << nAccount.id() << std::endl;
std::cout << nAccount.name() << std::endl;
std::cout << nAccount.password() << std::endl;
} else {
std::cout << "error in ParseFromString" << std::endl;
}
return 0;
}
g++ demo.cpp project.pb.cc -lprotobuf -o main
./main
1000
name
password
5. 稍复杂的使用
5.1 student.proto文件与编译
syntax = "proto2";
package tutorial;
message Student{
required uint64 id = 1;
required string name =2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phone = 4;
}
protoc -I=. -I/usr/local/include --cpp_out=. student.proto
5.2 使用
#include <iostream>
#include <string>
#include "student.pb.h"
using namespace std;
int main(int argc, char* argv[]){
GOOGLE_PROTOBUF_VERIFY_VERSION;
tutorial::Student student;
student.set_id(201421031059);
*student.mutable_name()="dablelv";
student.set_email("dablelv@tencent.com");
tutorial::Student::PhoneNumber* phone_number = student.add_phone();
phone_number->set_number("15813354925");
phone_number->set_type(tutorial::Student::MOBILE);
tutorial::Student::PhoneNumber* phone_number1 = student.add_phone();
phone_number1->set_number("0564-4762652");
phone_number1->set_type(tutorial::Student::HOME);
string serializedStr;
student.SerializeToString(&serializedStr);
cout<<"serialization result:"<<serializedStr<<endl;
cout<<endl<<"debugString:"<<student.DebugString();
tutorial::Student deserializedStudent;
if(!deserializedStudent.ParseFromString(serializedStr)){
cerr << "Failed to parse student." << endl;
return -1;
}
cout<<"-------------上面是序列化,下面是反序列化---------------"<<endl;
cout<<"deserializedStudent debugString:"<<deserializedStudent.DebugString();
cout <<endl<<"Student ID: " << deserializedStudent.id() << endl;
cout <<"Name: " << deserializedStudent.name() << endl;
if (deserializedStudent.has_email()){
cout << "E-mail address: " << deserializedStudent.email() << endl;
}
for (int j = 0; j < deserializedStudent.phone_size(); j++){
const tutorial::Student::PhoneNumber& phone_number = deserializedStudent.phone(j);
switch (phone_number.type()) {
case tutorial::Student::MOBILE:
cout << "Mobile phone #: ";
break;
case tutorial::Student::HOME:
cout << "Home phone #: ";
break;
}
cout <<phone_number.number()<<endl;
}
google::protobuf::ShutdownProtobufLibrary();
}
g++ test.cpp student.pb.cc -lprotobuf -o main
./main
输出结果:
|