一、描述proto文件
proto文件名稱爲addressbook.proto。
syntax = "proto3";
import "google/protobuf/any.proto";
// package類似於namespace,可以避免命名衝突
package AddressBookInfo;
// message類似於class
message Person
{
string name = 1;
int32 id = 2;
string email = 3;
// 枚舉類型
enum PhoneType
{
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber
{
string number = 1;
// proto3中enum沒有default選項,把第一個值作爲default
PhoneType type = 2;
}
// repeated表示message或者filed可以重複多次
repeated PhoneNumber phones = 4;
}
message Address
{
string address = 1;
}
message AddressBook
{
string owner = 1;
repeated Person person_infos = 2;
/*
** oneof類似於union類型,某一個時刻只能設置一個field,所有的field共享同一段內存。
** 設置oneof字段將自動清除oneof的所有其他字段,即只能同時設置(set_)一個,不然就會core dump。
** 可以在oneof內部添加和刪除field,但是刪除和添加oneof要小心。
** oneof中數據成員的編號建議承上啓下,儘量不要隨意編號。
*/
oneof PayType
{
string type_ali = 3;
string type_wx = 4;
}
/*
** map是key-value類型,key可以是int或者string,value可以是自定義message。
** Any用來實現泛型,可以表示任意類型。
*/
map<string, google.protobuf.Any> owner_address = 5;
}
二、編譯proto文件
使用protoc編譯器對proto文件進行編譯,生成addressbook.pb.h
和addressbook.pb.cc
。
三、生成protobuf API
打開addressbook.pb.h
和addressbook.pb.cc
,可以看到自動生成了很多的API,後續可以使用這些API讀寫數據。
四、使用API寫入和讀取數據
addressbook.cpp
文件中闡述瞭如何使用protobuf自動生成的API讀寫數據,基本上覆蓋了常用的消息類型。
具體的API及使用方式可以參考:https://developers.google.cn/protocol-buffers/docs/reference/cpp-generated
#include <iostream>
#include <fstream>
#include "addressbook.pb.h"
#include <google/protobuf/any.h>
using namespace std;
void SaveInfo()
{
AddressBookInfo::AddressBook adbook;
// 使用set_設置message中filed的值,函數名都是小寫(即使在proto中是大寫)
adbook.set_owner("eric");
// Person1
// 使用add_添加message,返回的是指針類型
AddressBookInfo::Person *person1 = adbook.add_person_infos();
person1->set_name("mark");
person1->set_id(123456);
person1->set_email("[email protected]");
AddressBookInfo::Person::PhoneNumber *person1_phone = person1->add_phones();
person1_phone->set_number("12345678");
// 枚舉類型中的元素名稱是唯一的,所以可以直接用作用域限定符訪問元素,不需要通過枚舉名訪問
person1_phone->set_type(AddressBookInfo::Person::HOME);
person1_phone = person1->add_phones();
person1_phone->set_number("1234");
person1_phone->set_type(AddressBookInfo::Person::WORK);
// Person2
AddressBookInfo::Person *person2 = adbook.add_person_infos();
person2->set_name("mike");
person2->set_id(654321);
person2->set_email("[email protected]");
AddressBookInfo::Person::PhoneNumber *person2_phone = person2->add_phones();
person2_phone->set_number("87654321");
person2_phone->set_type(AddressBookInfo::Person::HOME);
person2_phone = person2->add_phones();
person2_phone->set_number("5678");
person2_phone->set_type(AddressBookInfo::Person::WORK);
// map和any類型的初始化,mutable返回的是非const指針類型
google::protobuf::Map<string, google::protobuf::Any> *owner_address = adbook.mutable_owner_address();
// 定義一個Any類型,用於接收message
google::protobuf::Any *any = new google::protobuf::Any;
AddressBookInfo::Address adbook_address;
adbook_address.set_address("HB");
// 使用PackFrom將message類型存儲爲Any類型
any->PackFrom(adbook_address);
// map類型的初始化方式和STL中的map類似
(*owner_address)[adbook.owner()] = *any;
// 設置oneof中某一個成員的值,之後如果再使用set_則會core dump
adbook.set_type_ali("AliPay");
fstream output("address_book_file", ios::out | ios::trunc | ios::binary);
// 使用SerializeToOstream將序列化後的數據寫入文件中
if (!adbook.SerializeToOstream(&output))
{
cerr << "Failed to write address book." << endl;
}
}
void ShowMsg(const AddressBookInfo::AddressBook &adbook)
{
cout << adbook.owner() << endl;
// 對於重複message,_size表示有多少個重複message,使用索引取出每一個message
for (int i = 0; i < adbook.person_infos_size(); ++i)
{
const AddressBookInfo::Person &person = adbook.person_infos(i);
// 取出各個字段的值
cout << person.name() << endl;
cout << person.id() << endl;
cout << person.email() << endl;
for (int j = 0; j < person.phones_size(); ++j)
{
// 使用索引獲取枚舉類型的所有值
const AddressBookInfo::Person::PhoneNumber &person_phone = person.phones(j);
switch(person_phone.type())
{
case AddressBookInfo::Person::MOBILE :
cout << "MOBILE phone #: ";
break;
case AddressBookInfo::Person::HOME :
cout << "HOME phone #: ";
break;
case AddressBookInfo::Person::WORK :
cout << "WORK phone #: ";
break;
}
cout << person_phone.number() << endl;
}
}
cout << adbook.type_ali() << endl;
}
void ShowMapMsg(const AddressBookInfo::AddressBook &adbook)
{
// 取出map類型的字段成員
const google::protobuf::Map<string, google::protobuf::Any> &adbook_map = adbook.owner_address();
// 使用迭代器訪問map(和STL中的map類似)
auto iter = adbook_map.begin();
cout << iter->first << endl;
google::protobuf::Any any = iter->second;
AddressBookInfo::Address adbook_address;
// 使用UnpackTo從Any類型解析出message,注意參數中有一個&
if (any.UnpackTo(&adbook_address))
{
cout << adbook_address.address() << endl;
}
else
{
cout << "UnpackTo data error" << endl;
}
}
void LoadInfo()
{
AddressBookInfo::AddressBook adbook;
fstream input("address_book_file", ios::in | ios::binary);
// 使用ParseFromIstream從文件中反序列化
if (!input)
{
cout << ": File not found. Creating a new file." << endl;
}
else if (!adbook.ParseFromIstream(&input))
{
cout << "Failed to parse address book." << endl;
}
ShowMsg(adbook);
cout << endl;
ShowMapMsg(adbook);
}
int main(int argc, char const *argv[])
{
SaveInfo();
LoadInfo();
// 刪除所有已分配的內存(注意any是堆上的內存,清除內存的操作要小心)
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
五、編譯所有的CPP文件
使用g++編譯所有的CPP文件,注意編譯參數需要加上:-std=c++11、-lprotobuf、-lpthread
。
六、編譯和運行中遇到的問題
(1)如果沒有添加-std=c++11選項則會出現以下問題:
(2)針對oneof類型,如果同時設置了多個字段或者字段編號混亂,則會出現以下問題: