Protobuf
Protobuf是google开发的一种独立于平台语言数据交换的格式,其提供了java、c#、c++、go和python等语言的实现,并包含了相应语言的编译器以及库文件
由于它是一种二进制的格式,效率上比xml(20倍)、json(10倍)高,可以把它应用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可用于诸如网络传输、配置文件、数据存储等诸多领域
要求:熟悉Protubu的proto配置文件内容,以及protoc的编译命令
syntax = "proto3"; // 声明了protobuf版本
package xxx; // 声明了代码所在的包(对应c++来说是namespace)
option cc_generic_services = true;
protoc test.proto --cpp_out=./
Tips:
在使用string时可以直接使用bytes类型代替,提升效率
Protobuf使用(C++)
- 在
.proto文件中定义消息格式 - 使用
protocol buffer编译器protoc - 使用C++的
protobuf API来进行消息读写
示例定义
该示例是一个简单的地址簿应用,可以从文件中写入和读取地址簿中的人员信息(name、ID、E-mail、phone)。
官方示例代码
定义消息格式
首先要编写.proto文件:为需要序列化的数据结构定义一个message(对应C++的一个类),然后为每个字段指定名称和类型。
下面是addressbook.proto文件的示例:
syntax = "proto3";
package tutorial; // 其作用类似于c++的命名空间
import "google/protobuf/timestamp.proto";
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;
google.protobuf.Timestamp last_updated = 5;
}
message AddressBook {
repeated Person people = 1;
}
接下来是对该addressbook.proto文件的解释。
文件开头是proto语法版本(本例采用proto3)和package包声明,包声明有助于防止不同项目间的命名冲突。在C++中,生成的类将被放在包名对应的命名空间中。
message消息定义:message是一组字段的集合,字段可以使用bool、int32、int64、bytes等简单的数据类型;也可以使用已经定义过的message类型作为另一个message里面的字段。例如上述文件中,message Person中包含了message PhoneNumber,并且PhoneNumber是嵌套定义在Person中。定义enum类型来固定某个字段的取值是有限且明确的
每个字段后面的=1、=2表示该字段在二进制编码中使用的唯一标签。标签1-15比更高的编号少用一个字节进行编码,因此搭配具有repeated修饰符的字段可以提高效率。
每个字段都必须使用以下修饰符之一:
optional:该字段可以设置也可以不设置。如果未设置则采用默认,则使用默认值。repeated:该字段可以重复任意次数(包含0次),protobuf会自动维护出现的顺序。可以将repeated字段是为动态大小的数组:必须提供该字段的值,否则该required(proto3)message将被视为未初始化。如果libprotobuf库在deBug模式下编译,未初始化的消息将导致断言失败。在最优化的构建中,跳过检查并直接写入消息。但是,在解析未初始化的消息的时候会失败(解析函数返回值false)。此外,required字段的行为与optional字段完全相同。
TIPS:required是永久的!。因为当系统运行一段时间之后,想要将某一个字段从
required->optional会产生问题。旧代码在解析时会认为没有该字段的消息不完整,因而将之抛弃。
编译Proto文件
根据.proto文件,运行protobuf编译器protoc生成读写addressbook消息的类。
protoc addressbook.proto --cpp_out=./
由于需要C++类,使用--cpp_out选项,其值为输出目录。如果输入的.proto文件不在当前文件在内,可以使用-I指定搜索目录
在当前目录生成生成以下文件:
[] addressbook.pb.h:声明生成类的头文件
[] addressbook.pb.cc:包含类定义的文件
Protobuf Buffers API
写出消息
使用消息类,将个人详细信息写入文件。为此,需要创建并初始化AddressBook消息类的示例,然后将其写入输出流。
addPerson.cc文件来自Protobuf官方C++用例,该程序从文件中读取一个(现有的/没有会自动创建)AddressBook对象,根据用户输入向其中添加新的Person,然后将新的AddressBook对象写回文件中
// See README.md for information and build instructions.
#include <ctime>
#include <fstream>
#include <google/protobuf/util/time_util.h>
#include <iostream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
using google::protobuf::util::TimeUtil;
// This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {
cout << "Enter person ID number: ";
int id;
cin >> id;
person->set_id(id);
cin.ignore(256, '\n');
cout << "Enter name: ";
getline(cin, *person->mutable_name());
cout << "Enter email address (blank for none): ";
string email;
getline(cin, email);
if (!email.empty()) {
person->set_email(email);
}
while (true) {
cout << "Enter a phone number (or leave blank to finish): ";
string number;
getline(cin, number);
if (number.empty()) {
break;
}
tutorial::Person::PhoneNumber* phone_number = person->add_phones();
phone_number->set_number(number);
cout << "Is this a mobile, home, or work phone? ";
string type;
getline(cin, type);
if (type == "mobile") {
phone_number->set_type(tutorial::Person::MOBILE);
} else if (type == "home") {
phone_number->set_type(tutorial::Person::HOME);
} else if (type == "work") {
phone_number->set_type(tutorial::Person::WORK);
} else {
cout << "Unknown phone type. Using default." << endl;
}
}
*person->mutable_last_updated() = TimeUtil::SecondsToTimestamp(time(NULL));
}
// Main function: Reads the entire address book from a file,
// adds one person based on user input, then writes it back out to the same
// file.
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) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
}
tutorial::AddressBook address_book;
{
// Read the existing address book.
fstream input(argv[1], ios::in | ios::binary);
if (!input) {
cout << argv[1] << ": File not found. Creating a new file." << endl;
} else if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
}
// Add an address.
PromptForAddress(address_book.add_people());
{
// Write the new address book back to disk.
fstream output(argv[1], ios::out | ios::trunc | ios::binary);
if (!address_book.SerializeToOstream(&output)) {
cerr << "Failed to write address book." << endl;
return -1;
}
}
// Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
写出消息
[listPeople.cc]
// See README.md for information and build instructions.
#include <fstream>
#include <google/protobuf/util/time_util.h>
#include <iostream>
#include <string>
#include "addressbook.pb.h"
using namespace std;
using google::protobuf::util::TimeUtil;
// Iterates though all people in the AddressBook and prints info about them.
void ListPeople(const tutorial::AddressBook& address_book) {
for (int i = 0; i < address_book.people_size(); i++) {
const tutorial::Person& person = address_book.people(i);
cout << "Person ID: " << person.id() << endl;
cout << " Name: " << person.name() << endl;
if (person.email() != "") {
cout << " E-mail address: " << person.email() << endl;
}
for (int j = 0; j < person.phones_size(); j++) {
const tutorial::Person::PhoneNumber& phone_number = person.phones(j);
switch (phone_number.type()) {
case tutorial::Person::MOBILE:
cout << " Mobile phone #: ";
break;
case tutorial::Person::HOME:
cout << " Home phone #: ";
break;
case tutorial::Person::WORK:
cout << " Work phone #: ";
break;
default:
cout << " Unknown phone #: ";
break;
}
cout << phone_number.number() << endl;
}
if (person.has_last_updated()) {
cout << " Updated: " << TimeUtil::ToString(person.last_updated()) << endl;
}
}
}
// Main function: Reads the entire address book from a file and prints all
// the information inside.
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) {
cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
return -1;
}
tutorial::AddressBook address_book;
{
// Read the existing address book.
fstream input(argv[1], ios::in | ios::binary);
if (!address_book.ParseFromIstream(&input)) {
cerr << "Failed to parse address book." << endl;
return -1;
}
}
ListPeople(address_book);
// Optional: Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
编译和运行
- 编译
g++ -o addPerson addPerson.cc addressbook.pb.cc -lprotobuf -pthread
g++ -o listPeople listPeople.cc addressbook.pb.cc -lprotobuf -pthread
- 运行
addPerson可执行文件,添加信息。
$ ./addPerson data.bin
data.bin: File not found. Creating a new file.
Enter person ID number: 1
Enter name: Alice
Enter email address (blank for none): alice@example.com
Enter a phone number (or leave blank to finish): 1234
Is this a mobile, home, or work phone? mobile
Enter a phone number (or leave blank to finish): 5678
Is this a mobile, home, or work phone? work
Enter a phone number (or leave blank to finish):
$ ./addPerson data.bin
Enter person ID number: 2
Enter name: Bob
Enter email address (blank for none): bob@example.com
Enter a phone number (or leave blank to finish): 4321
Is this a mobile, home, or work phone? home
Enter a phone number (or leave blank to finish): 8765
Is this a mobile, home, or work phone? work
Enter a phone number (or leave blank to finish):
- 运行
listPeople可执行文件,读取消息
$ ./listPeople data.bin
Person ID: 1
Name: Alice
E-mail address: alice@example.com
Mobile phone #: 1234
Work phone #: 5678
Updated: 2025-04-24T12:36:34Z
Person ID: 2
Name: Bob
E-mail address: bob@example.com
Home phone #: 4321
Work phone #: 8765
Updated: 2025-04-24T12:37:08Z