ProtoBuf学习总结
protobuf概念
protobuf 是一种平台无关、语言无关,可扩展的序列化数据格式,相比较与JSON和xml,具有高效性和灵活性。
定义Message
proto文件的定义
Message被定义为一个.proto文件
本文参考官方文档设计的Message如下:
// An highlighted block
syntax = "proto2";
package tutorial;
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}
repeated PhoneNumber phones = 4;
}
message AddressBook {
repeated Person people = 1;
}
首先,需要在声明使用的是proto协议的版本,package会被编译生成C++中的namespace;
譬如,package tutorial 会被编译生成namespace tutorial{};
每个Message 会被编译生成每个类,譬如message Person会被编译生成class Person;
Message 数字域与规则
由Message定义知,每个Message中均匀一些数字域,该数字域是唯一的;
不同的数字定义了mesage二进制编码格式的一个域;
10000—19999是系统保留的数字,不能用来定义类型
required:该消息域使用需要注意,如果在一个proto文件中将某个域定义为required,则其他使用者如果想改变该域,会导致错误;每个Message必须定义一个required
optional:如果optional域的变量为被设置,则Message在被解析时,会议默认值代替;每个Message可以定义或者不定义该域
repeated:该域能被重复0次或者多次,每次的值均会被保存
protobuf编码规则
整数编码规则
// An highlighted block
syntax = "proto2";
package Test_econd;
message Test1 {
optional int32 a = 1;
// optional string str = 2;
}
如上定义一个Message,利用protoc工具对其进行编译
编译命令为:
protoc -I=./pb --cpp_out=./pb ./pb/test.proto
-I 为包含proto的源文件路径,–cpp_out为编译生成的.h和.cc 所在的文件路径, 后面跟所要编译的proto文件
本文在实验时设置a=300;其序列化结果如下:
// An highlighted block
0000: 08ac 020a
其序列化过程如下:
- 对于a=300;其数字域为1,因此1<<3,左移三位
- 其类型为int32, 因此用type=000表示
- 1<<3 | type =01000 = 08
- 300的二进制表示为100101100
- 在protobuf编码协议中,以整数的从低位开始数起,7位为一组,并增加一个最高有效位MSB,然后将组顺序想调,最高有效位为1表示改数字字节表示结束,为0表示这属于一个整数字节表示的一部份;
- 对于300的10 0101100,调转两组变为 0101100 10,由于0101100变为300的最高字节组,因此在其最高位加1变为1010 1100也即 ac,对于10,则补0变为00000010也即02,因此300编码的结果为08ac 02
- 至于最后的0a, 则是换行符的ASCII码值,pb序列化会在字节末尾加个换行符
string编码规则
借用官网的例子,假设Message定义如下
// An highlighted block
syntax = "proto2";
package Test_econd;
message Test2 {
optional string b = 2;
}
则当将Test2的b设置为testing时,其编码过程如下:
- 字符串的类型为type=2, 数字域为2,数字域<<3
- 然后将其结果 | type
- 用可变字节表示字符串的长度,即07
- testing被编码为utf8类型的 74 65 73 74 69 6e 67
protobuf C++相关编程
- proto文件的实现,我编写测试用例命名为test.proto
其实现如下:
// An highlighted block
syntax = "proto2";
package Test_econd;
message Test1 {
optional int32 a = 1;
optional string str = 2;
}
在相应的目录下执行:
protoc -I = ./src --cpp_out == ./src ./src/test.proto
- 在src中生成test.pb.h和test.pb.cc文件
- 建立write_pb.h文件,如下
// An highlighted block
#ifndef QIN_WRITE_PB_H
#define QIN_WRITE_PB_H
#include <fstream>
#include <iostream>
#include "test.pb.h"
void PromptForTest(Test_econd::Test1* test1);
#endif
- write_pb.cpp实现如下
// An highlighted block
//
//
//
#include "write_pb.h"
void PromptForTest(Test_econd::Test1* test1) {
std::cout << "请输入一个数:" << std::endl;
int id;
std::cin >> id;
test1->set_a(id);
std::cin.ignore(256, '\n');
std::cout << "请输入一个字符串" << std::endl;
std::string str;
std::getline(std::cin, str);
test1->set_str(str);
}
- main函数实现为
// An highlighted block
//
//
//
#include <google/protobuf/text_format.h>
#include <google/protobuf/message.h>
#include "base/base64.h"
#include "write_pb.h"
int main(int argc, char* argv[]){
// Verify that the version of the library that we linked against is
// compatible with the version of the headers we compiled against.
GOOGLE_PROTOBUF_VERIFY_VERSION;
if (argc != 2) {
std::cerr << "Usage : " << "错误" << std::endl;
return -1;
}
Test_econd::Test1 test1_1;
PromptForTest(&test1_1);
{
// std::fstream output(argv[1], std::ios::out | std::ios::trunc | std::ios::binary);
std::string output;
if (!test1_1.SerializeToString(&output)) {
std::cerr << "序列化失败:" << std::endl;
return -1;
}
// base64编码
std::cout << "序列化结果:" << output << std::endl;
}
// 解析字符串
{
google::protobuf::Message* test = new Test_econd::Test1;
std::string text_string;
google::protobuf::TextFormat::PrintToString(*test, &text_string);
std::cout << "解析的文本为:" << text_string.size() << std::endl;
}
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
验证输出结果为:
[1] [ProtoBuffers] (https://developers.google.com/protocol-buffers/docs/proto)