protobuf使用實例

一、描述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.haddressbook.pb.cc
在這裏插入圖片描述

三、生成protobuf API

  打開addressbook.pb.haddressbook.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類型,如果同時設置了多個字段或者字段編號混亂,則會出現以下問題:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章