一、简介
protocol buffers 是一种灵活、高效、自动化机制的结构数据序列化方法——可类比 XML,但是比 XML 更小、更快、更为简单。你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏根据旧数据结构编译而成并且已部署的程序。
二、安装:参考官网 https://github.com/protocolbuffers/protobuf/blob/master/src/README.md
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 # refresh shared library cache.
三、数据类型
double: 浮点数
float: 单精度浮点
int32: int类型,使用可变长编码,编码负数不够高效,如果有负数那么使用sint32
sint32: int类型,使用可变长编码, 有符号的整形,比通常的int32高效
uint32: 无符号整数使用可变长编码方式
int64: long long,使用可变长编码方式。编码负数时不够高效——如果有负数,可以使用sint64
sint64: long long,使用可变长编码方式。有符号的整型值。编码时比通常的int64高效
uint64: 无符号整数使用可变长编码方式
fixed32: 总是4个字节。如果数值总是比总是比2^28大的话,这个类型会比uint32高效
fixed64: 总是8个字节。如果数值总是比总是比2^56大的话,这个类型会比uint64高效
sfixed32: 总是4个字节
sfixed64: 总是8个字节
bool: bool值
string: 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本
bytes: 可能包含任意顺序的字节数据。类似java的ByteString以及 c++ string
三、proto文件格式
创建 .proto 文件,定义数据结构
message xxx {
// 字段规则:required -> 字段只能也必须出现 1 次
// 字段规则:optional -> 字段可出现 0 次或1次
// 字段规则:repeated -> 字段可出现任意多次(包括 0)
// 类型:int32、uint32、int64、uint64、bool、string、double、float ....
// 字段编号:0 ~ 536870911(除去 19000 到 19999 之间的数字)
字段规则 类型 名称 = 字段编号;
}
注意:
- proto3 已舍弃 required 字段,optional 字段也无法显示使用(因为缺省默认就设置为 optional)。
- 1 到 15 范围内的字段编号需要一个字节进行编码,应该为非常频繁出现的 message 元素保留字段编号 1 到 15 。
- 为 .proto 文件添加注释,可以使用 C/C++ 语法风格的注释 // 和 /* ... */ 。
如果要用作字段类型的 message 类型已在另一个 .proto 文件中定义,可以通过导入来使用其他 .proto 文件中的定义。要导入另一个 .proto 的定义,可以在文件顶部添加一个 import 语句:
import "myproject/other_protos.proto";
.proto文件新增一个可选的package声明符,用来防止不同的消息类型有命名冲突。包的声明符会根据使用语言的不同影响生成的代码。对于C++,产生的类会被包装在C++的命名空间中。
package foo.bar; // package声明符,对于c++,生成的Open类位于命名空间foo::bar下
message Open { ... }
然后,你可以在定义 message 类型的字段时使用包说明符:
message Foo {
foo.bar.Open open = 1;
}
package 影响生成的代码的方式取决于你所选择的语言,在 C++ 中,生成的类包含在 C++ 命名空间中。例如,Open 将位于命名空间 foo::bar 中。
例:
person.proto
// person message
syntax = "proto3";
package alan.protobuf_test;
message Person {
string name = 1;
int32 id = 2;
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
phonebook.proto
/* phonebook message */
syntax = "proto3";
import "person.proto";
package alan.protobuf_test;
message PhoneBook {
repeated Person persons = 1;
}
四、编译.proto文件生成读写接口
运行 protocol buffer 编译器 protoc,编译 .proto 文件生成所需语言的读写接口代码,这些代码可以操作文件中描述的 message 类型,包括获取和设置字段值、将 message 序列化为输出流、以及从输入流中解析出 message。对于 C++,编译器从每个 .proto 生成一个 .h 和 .cc 文件,其中包含文件中描述的每种 message 类型对应的类。
Protocol 编译器的调用如下:
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --python_out=DST_DIR path/to/file.proto
IMPORT_PATH 指定在解析导入指令时查找 .proto 文件的目录,如果省略,则使用当前目录。可以通过多次传递 --proto_path 选项来指定多个导入目录,他们将按顺序搜索。-I = IMPORT_PATH 可以用作 --proto_path 的缩写形式。--cpp_out
在 DST_DIR
中生成 C++ 代码。
例:
编译上面.proto文件
protoc -I=proto/ --cpp_out=proto/ person.proto phonebook.proto
生成:
proto
|__person.pb.cc
|__person.pb.h
|__person.proto
|__phonebook.pb.cc
|__phonebook.pb.h
|__phonebook.proto
五、使用生成的接口
对结构数据(如json)进行序列化、反序列化以及读写。
protobuf_test.cc:
// #include <fcntl.h>
// #include "google/protobuf/text_format.h"
// #include <google/protobuf/io/zero_copy_stream_impl.h>
#include <sstream>
#include <fstream>
#include "google/protobuf/util/json_util.h"
#include "person.pb.h" // 对应的proto文件的路径
#include "phonebook.pb.h"
using Person = alan::protobuf_test::Person;
using PhoneBook = alan::protobuf_test::PhoneBook;
int main() {
// 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;
std::string file_path = "/home/alan/alan_projects/protobuf_test/phonebook.json";
// std::string file_path = "/home/alan/alan_projects/protobuf_test/phonebook.txt";
// write to json file
{
PhoneBook phonebook;
Person* person1 = phonebook.add_persons();
person1->set_name("alan");
person1->set_id(1);
person1->set_email("[email protected]");
Person::PhoneNumber* phonenumber1 = person1->add_phones();
phonenumber1->set_number("10010");
phonenumber1->set_type(Person::PhoneType::Person_PhoneType_HOME);
Person::PhoneNumber* phonenumber2 = person1->add_phones();
phonenumber2->set_number("10086");
phonenumber2->set_type(Person::PhoneType::Person_PhoneType_WORK);
Person* person2 = phonebook.add_persons();
person2->set_name("ayun");
person2->set_id(2);
person2->set_email("[email protected]");
Person::PhoneNumber* phonenumber3 = person2->add_phones();
phonenumber3->set_number("10000");
phonenumber3->set_type(Person::PhoneType::Person_PhoneType_HOME);
Person::PhoneNumber* phonenumber4 = person2->add_phones();
phonenumber4->set_number("10086");
phonenumber4->set_type(Person::PhoneType::Person_PhoneType_WORK);
std::string json_string;
google::protobuf::util::JsonPrintOptions options;
options.add_whitespace = true;
options.always_print_primitive_fields = true;
options.preserve_proto_field_names = true;
auto ret = google::protobuf::util::MessageToJsonString(phonebook, &json_string, options);
// std::cout << json_string << std::endl;
// std::string txt_string;
// google::protobuf::TextFormat::PrintToString(phonebook, &txt_string);
std::ofstream fout;
fout.open(file_path.c_str(), std::ios::ate);
fout << json_string;
// fout << txt_string;
fout.close();
}
// read from json file
{
std::ifstream fin(file_path.c_str());
if (!fin) {
std::cout << "Fail to read json file" << file_path;
return -1;
}
std::stringstream buffer;
buffer << fin.rdbuf();
PhoneBook phonebook;
google::protobuf::util::JsonParseOptions options;
options.ignore_unknown_fields = false; // proto 和 json 的域必须对应
auto status = google::protobuf::util::JsonStringToMessage(buffer.str(),
&phonebook, options);
if (!status.ok()) {
std::cout << "Fail to parse json file [" << file_path << "]: ["
<< status.error_message() << "]";
}
// PhoneBook phonebook;
// int file_descriptor = open(file_path.c_str(), O_RDONLY);
// if (file_descriptor < 0) {
// return -1;
// }
// google::protobuf::io::FileInputStream file_input_stream(file_descriptor);
// file_input_stream.SetCloseOnDelete(true);
// if (!google::protobuf::TextFormat::Parse(&file_input_stream, &phonebook) &&
// !phonebook.ParseFromZeroCopyStream(&file_input_stream)){
// return -1;
// }
for (const auto& person : phonebook.persons()) {
std::cout << "name: " << person.name();
for (const auto& phone : person.phones()) {
if (phone.type() == Person::PhoneType::Person_PhoneType_HOME) {
std::cout << " --> home number: " << phone.number() << std::endl;
}
}
}
fin.close();
}
// Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
CMakeLists.txt:
cmake_minimum_required(VERSION 2.8)
project(protobuf_test)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
find_package(Protobuf REQUIRED)
include_directories(${PROTOBUF_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS proto/person.proto proto/phonebook.proto)
add_executable(protobuf_test protobuf_test.cc ${PROTO_SRCS} ${PROTO_HDRS})
target_link_libraries(protobuf_test ${PROTOBUF_LIBRARIES})
输出的phonebook.json文件:
{
"persons": [
{
"name": "alan",
"id": 1,
"email": "[email protected]",
"phones": [
{
"number": "10010",
"type": "HOME"
},
{
"number": "10086",
"type": "WORK"
}
]
},
{
"name": "ayun",
"id": 2,
"email": "[email protected]",
"phones": [
{
"number": "10000",
"type": "HOME"
},
{
"number": "10086",
"type": "WORK"
}
]
}
]
}
输出的phonebook.txt文件:
persons {
name: "alan"
id: 1
email: "[email protected]"
phones {
number: "10010"
type: HOME
}
phones {
number: "10086"
type: WORK
}
}
persons {
name: "ayun"
id: 2
email: "[email protected]"
phones {
number: "10000"
type: HOME
}
phones {
number: "10086"
type: WORK
}
}
六、生成 JsonLines 文件
std::string json_string;
google::protobuf::util::JsonPrintOptions options;
options.add_whitespace = false; // 改这里
options.always_print_primitive_fields = true;
options.preserve_proto_field_names = true;
google::protobuf::util::MessageToJsonString(phonebook, &json_string, options);
std::ofstream fout(test.txt);
fout.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fout << json_string;
fout.close();
七、Json 和 JsonLines 文件相互转换
sudo apt-get install jq
cat test.txt | jq '.' > test.json # jsonlines->json
cat test.json | jq -c '.' > test.txt # json->jsonlines
参考:
https://developers.google.com/protocol-buffers/docs/overview
std::fstream fout("/tmp/binary_file", std::ios::out | std::ios::trunc | std::ios::binary);
phonebook.SerializeToOstream(&fout));
protoc --decode alan.protobuf_test.Persons proto/PhoneBook.proto < /tmp/binary_file > /tmp/test.json
protobuf Message的序列化和反序列化string类型(SerializeToString,ParseFromString)
std::stringstream ss;
PhoneBook phonebook;
CHECK(phonebook.ParseFromString(ss));
std::fstream fout("/tmp/test.proto", std::ios::out | std::ios::trunc | std::ios::binary);
CHECK(phonebook.SerializeToOstream(&fout));
std::ostringstream output
proto.SerializeToOstream(&output));
PhoneBook phonebook;
phonebook.clear_persons(); ??
Person person;
*phonebook.add_persons() = person;
// phonebook.add_persons(person) ??
protobuf mutable_* 函数
从该函数的实现上来看,该函数返回指向该字段的一个指针。同时将该字段置为被设置状态。
若该对象存在,则直接返回该对象,若不存在则新new 一个。
message Cover {
string title = 1;
int32 page = 2;
}
message PhoneBook {
repeated Person persons = 1;
Cover cover = 2;
}
PhoneBook phonebook;
phonebook.mutable_cover()->set_page(0)