C/C++ProtoBuf使用小結
一.什么是ProtoBuf
1.序列化和反序列化概念
序列化:把對象轉變?yōu)樽止?jié)序列的過程,稱為系列化。
反序列化:把字節(jié)序列的內(nèi)容恢復為對象的過程,稱為反序列化。
2.什么情況下需要序列化和反序列化
存儲數(shù)據(jù):將內(nèi)存中的對象狀態(tài)保存在文件中或存儲在數(shù)據(jù)庫中時。
網(wǎng)絡傳輸:網(wǎng)絡傳輸數(shù)據(jù)時,無法直接傳輸對象,需要在傳輸前序列化,傳輸完成后反 序列化成對象,就像學習Socket編程中發(fā)送與接收時。
3.如何實現(xiàn)序列化
xml,json,protoBuf這三種工具都可以實現(xiàn)序列化和反序列化
4.ProtoBuf是什么
ProtoBuf是讓數(shù)據(jù)結構序列化的方法,具有以下特點:
- 語言無關、平臺無關:即 ProtoBuf 支持 Java、C++、Python 等多種語言,支持多個平臺。
- 高效:即比 XML 更小、更快、更為簡單。
- 擴展性、兼容性好:你可以更新數(shù)據(jù)結構,而不影響和破壞原有的舊程序。
二.ProtoBuf簡單語法知識
1.文件規(guī)范
創(chuàng)建一個ProtoBuf文件,后綴一定以.proto結尾,文件命名全小寫字母,多個字母之間以_為分隔符,添加注釋的方法和C/C++的一毛一樣。
2.ProtoBuf語法分類
ProtoBuf有對個版本,在這里我們使用最新的版本,protobuf3的語法,簡稱proto3,它是最新的ProtoBuf語法版本。proto3 簡化了 ProtoBuf 語言,既易于使用,又可以在更廣泛的編程語言中使用。它允許你使用 Java,C++,Python等多種語言生成 protocol buffer 代碼。
3.ProtoBuf3語法
syntax
我們在創(chuàng)建一個文件時,一定要在文件開頭致命你所使用的語法,如果沒有指定,默認使用Protobuf2的語法來使用,
比如:
syntax = "proto3";
syntax為指定語法,一定要有,就像函數(shù)中一定要指明函數(shù)返回類型一樣。
package(聲明符)
package 是一個可選的聲明符,能表示 .proto 文件的命名空間,在項目中要有唯一性。它的作用是為了避免我們定義的消息出現(xiàn)沖突,就像c++中的namespace一樣,指定一個域,防止沖突。
比如:
package person;
message(定義消息)
消息(message):要定義的結構化對象,可以給這個結構化對象中定義對用的屬性內(nèi)容,就像C//C++中的結構體一樣,只能夠定義對象。
這里強調(diào)下為什么要定義對象:
網(wǎng)絡傳輸中,需要傳輸雙方定制協(xié)議,定制協(xié)議就是定制結構體或者結構化數(shù)據(jù),比如:Tcp,Udp。而且將數(shù)據(jù)存儲在數(shù)據(jù)庫時,需將數(shù)據(jù)統(tǒng)一為對象組織起來,在進行存儲。
所以ProtoBuf就是以message的方式支持我們定義協(xié)議字段的。比如:
syntax="proto3";
package=package;
message people
{
;
}定義字段消息
在 message 中我們可以定義其屬性字段,字段定義格式為:字段類型 字段名 = 字段唯一編號,比如:
syntax="proto3";
package=package;
message people
{
int32 age=1;
}int32為字段類型 age為字段名 1為字段唯一編號
下面是protobuf創(chuàng)建類型及所對應的C/C++類型:

注:[1] 變長編碼是指:經(jīng)過protobuf 編碼后,原本4字節(jié)或8字節(jié)的數(shù)可能會被變?yōu)槠渌止?jié)數(shù)。
比如:
syntax="proto3"
package sss
message person
{
string name=1;
int32 age=2;
string sex=3;
}注意:字段唯一編號范圍:
2^0~2^29-1,其中,19000~19999不可用,在 Protobuf 協(xié)議的實現(xiàn)中,對這些數(shù)進行了預留。如果非要在.proto文件中使用這些預留標識號,例如將 name 字段的編號設置為19000,編譯時就會報警。值得一提的是,范圍為 1 ~ 15 的字段編號需要一個字節(jié)進行編碼, 16 ~ 2047 內(nèi)的數(shù)字需要兩個字節(jié)進行編碼。編碼后的字節(jié)不僅只包含了編號,還包含了字段類型。所以 1 ~ 15 要用來標記出現(xiàn)非常頻繁的字段,要為將來有可能添加的、頻繁出現(xiàn)的字段預留一些出來。
通過上面的學習,我們來實現(xiàn)一個簡單的test.proto文件,
syntax="proto3";
package sss;
message person
{
string name=1;
int32 age=2;
string sex=3;
}編輯protobuf命令如下:proto --cpp_out. [文件名]
我們使用的是C++語言,所以編譯之后生成兩個文件:test.pb.h和test.pb.cc。
對于編譯生成的 C++ 代碼,包含了以下內(nèi)容 :
- 對于每個 message ,都會生成一個對應的消息類。
- 在消息類中,編譯器為每個字段提供了獲取和設置方法,以及一下其他能夠操作字段的方法。
- 編輯器會針對于每個 .proto 文件生成.h 和 .cc 文件,分別用來存放類的聲明與類的實現(xiàn)。
代碼過長,就不展示了,感興趣的自己做下?。。?/p>
編譯命令行格式
protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto
protoc:是protobuf提供的命令行編輯工具。
--proto_path: 指定被編輯的所在目錄,可多次指定,可簡寫為-I。如不指定,則在當前目錄下進行搜索。
--cpp_out=: 指定編譯后的文件為c++文件。
DST_DIR; 文件路徑
path/to/file.proto:要編譯的文件。
上述的例子中,每個字段都有設置和獲取的方法,getter 的名稱與小寫字段完全相同,setter 方法以 set_ 開頭,每個字段都有一個clear_的方法,可以將字段設置為empty狀態(tài)。比如:
#include <iostream>
#include "test.ph.h"
using namespace sss;
using namespace std;
int main()
{
person p1;
p1.set_name("張三");
p1.set_age(11);
p1.set_sex("nan");
return 0;
}在消息類的父類MessageLift中,提供了讀寫消息實例的方法,包括序列化方法和反序列化方法。
class MessageLite {
public:
//序列化:
bool SerializeToOstream(ostream* output) const; // 將序列化后數(shù)據(jù)寫入文件
//流
bool SerializeToArray(void *data, int size) const;
bool SerializeToString(string* output) const;
//反序列化:
bool ParseFromIstream(istream* input); // 從流中讀取數(shù)據(jù),再進行反序列化
//動作
bool ParseFromArray(const void* data, int size);
bool ParseFromString(const string& data);
};注意:
序列化的方法為二進制方法,非文本格式;以上三種序列化的方法沒有本質(zhì)區(qū)別,只是序列化之后輸出的格式不同,可以提供不同場景使用,序列化的 API 函數(shù)均為const成員函數(shù),因為序列化不會改變類對象的內(nèi)容, 而是將序列化的結果保存到函數(shù)入?yún)⒅付ǖ牡刂分小?/p>
更多詳細API函數(shù)參考:message.h | Protocol Buffers Documentation (protobuf.dev)
序列化和反序列化的使用:看結果
#include "contact.pb.h"
using namespace std;
int main()
{
string people_str;
contact::PersonInit p1;
p1.set_age(100);
p1.set_name("張三");
p1.set_sex("boy");
if (!p1.SerializeToString(&people_str))
{
cout << "序列化聯(lián)系人失敗." << endl;
}
// 打印序列化結果
cout << "序列化后的 people_str: " << people_str << endl;
if(!p1.SerializePartialToString(&people_str))
{
cout<<"反序列化失敗:"<<endl;
}
cout<<"反序列化成功:"<<endl<<people_str<<endl;
return 0;
}這就是簡單的Protobuf的應用。
接下來是ProtoBuf的語法詳解。
三.ProtoBuf語法詳解
在這部分,我們使用一個簡單的項目推進的方式來講解語法內(nèi)容,通過一個通訊錄的實現(xiàn),完成我們從最基礎的內(nèi)容到網(wǎng)絡版本的知識。
1.字段規(guī)則
singular :消息中可以包含該字段零次或一次(不超過一次)。 proto3 語法中,字段默認使用該規(guī)則,可以不用設置。
repeated :消息中可以包含該字段任意多次(包括零次),其中重復值的順序會被保留??梢岳斫鉃槎x了一個數(shù)組。
比如:
syntax="proto3";
package contacts2;
message person
{
string name=1;
int32 age=2;
repeated string phone_num=3;
}2.消息類型的定義與使用
在單個.proto文件中,可以定義多個message,也可以嵌套定義,每個message中的字段編號可以重復,并且,消息字段也可以作為字段類型來使用,比如:
syntax="proto3";
package contacts2;
// message Phone{
// string phone_num=1;
// }
message person
{
string name=1;
int32 age=2;
//第一種,單獨寫法
//repeated Phone=3;
//第二種,嵌套寫法
message Phone{
string phone_num=1;
}
repeated Phone=3;
}也可以將其他.proto文件中定義的消息導入并使用,但一定要加import 比如:
syntax="proto3";
package contacts2;
import "Photo.proto";//將其他文件中的消息字段導入
message person
{
string name=1;
int32 age=2;
//第三種
repeated Phone.Phone phone=3;
}
syntax="proto3";
package Phone;
message Phone{
string phone_num=1;
}注:在 proto3 文件中可以導入 proto2 消息類型并使用它們,反之亦然
編譯
我們可以將以上的內(nèi)容稍加修改,變?yōu)槲覀兊牡谝话嫱ㄓ嶄洠?/p>
syntax="proto3";
package contacts2;
//import "Photo.proto";//將其他文件中的消息字段導入
message peopleInfo
{
string name=1;
int32 age=2;
message Phone{
string phone_num=1;
}
repeated Phone=3;
}
message Contact{
repeated PeopleInfo contact=1;
}然后編譯,編譯后生成的 contacts.pb.h contacts.pb.cc 會將老版本的代碼覆蓋掉,由于代碼過長,就不展示啦?。?!
上述的代碼中:
- 每個字段都有一個 clear_ 方法,可以將字段重新設置回 empty 狀態(tài)。
- 每個字段都有設置和獲取的方法, 獲取方法的方法名稱與小寫字段名稱完全相同。但如果是消息類型的字段,其設置方法為 mutable_ 方法,返回值為消息類型的指針,這類方法會為我們開辟好空間,可以直接對這塊空間的內(nèi)容進行修改。
- 對于使用 repeated 修飾的字段,也就是數(shù)組類型,pb 為我們提供了 add_ 方法來新增一個值,并且提供了 _size 方法來判斷數(shù)組存放元素的個數(shù)。
我們可以對編譯好的代碼試用一下:
#include <iostream>
#include <fstream>
#include "contact.pb.h"
using namespace std;
using namespace contacts;
void AddPeopleInfo(PeopleInfo *people_info_ptr)
{
cout << "-------------新增聯(lián)系人-------------" << endl;
cout << "請輸入聯(lián)系人姓名: ";
string name;
getline(cin, name);
people_info_ptr->set_name(name);//輸入用set_函數(shù)
cout << "請輸入聯(lián)系人年齡: ";
int age;
cin >> age;
people_info_ptr->set_age(age);
cin.ignore(256, '\n');
for (int i = 1;; i++)
{
cout << "請輸入聯(lián)系人電話" << i << "(只輸入回車完成電話新增): ";
string number;
getline(cin, number);
if (number.empty())
{
break;
}
//number需要添加才可以用
PeopleInfo_Phone *phone = people_info_ptr->add_phone();
phone->set_number(number);
}
cout << "-----------添加聯(lián)系人成功-----------" << endl;
}
int main()
{
Contacts contacts;
// 先讀取已存在的 contacts
fstream input("text.bin",ios::in | ios::binary);
if (!input)
{
cout << ": File not found. Creating a new file." << endl;
}
else if (!contacts.ParseFromIstream(&input))//序列化
{
cerr << "Failed to parse contacts." << endl;
input.close();
return -1;
}
// 新增一個聯(lián)系人
AddPeopleInfo(contacts.add_contacts());
//向磁盤文件寫入新的 contacts
fstream output("test.bin",ios::out | ios::trunc | ios::binary);
if (!contacts.SerializeToOstream(&output))
{
cerr << "Failed to write contacts." << endl;
input.close();
output.close();
return -1;
}
input.close();
//output.close();
return 0;
}3:enum類型的使用
語法支持我們定義枚舉類型來使用,我們可以定義一個enum類型的使用,
syntax="proto3";
package Phone;
message{
enum PhoneType{
MP=0;//移動電話
TEL=1;//固定電話
}
}要注意枚舉類型的定義有以下幾種規(guī)則:
1. 0 值常量必須存在,且要作為第一個元素。這是為了與 proto2 的語義兼容:第一個元素作為默認值,且值為 0。
2. 枚舉類型可以在消息外定義,也可以在消息體內(nèi)定義(嵌套)。
3. 枚舉的常量值在 32 位整數(shù)的范圍內(nèi)。但因負值無效因而不建議使用(與編碼規(guī)則有關)。定義時注意:
將兩個 ‘具有相同枚舉值名稱’ 的枚舉類型放在單個 .proto 文件下測試時,編譯后會報錯:某某某常量已經(jīng)被定義!所以這里要注意:
• 同級(同層)的枚舉類型,各個枚舉類型中的常量不能重名。
• 單個 .proto 文件下,最外層枚舉類型和嵌套枚舉類型,不算同級。
• 多個 .proto 文件下,若一個文件引入了其他文件,且每個文件都未聲明 package,每個 proto 文件中的枚舉類型都在最外層,算同級。
• 多個 .proto 文件下,若一個文件引入了其他文件,且每個文件都聲明了 package,不算同級。
下面我們來使用一下:
message Address
{
string home_address=1;
string unit_address=2;
}
message PeopleInfo
{
string name=1;
int32 age=2;
message Phone
{
string number=1;
enum PhoneType
{
MP = 0; // 移動電話
TEL = 1; // 固定電話
}
PhoneType type=2;
}
//多組電話
repeated Phone phone=3;
}編譯生成后的文件:對于在.proto文件中定義的枚舉類型,編譯生成的代碼中會含有與之對應的枚舉類型、校驗枚舉值是否有效的方法 _IsValid、以及獲取枚舉值名稱的方法 _Name。對于使用了枚舉類型的字段,包含設置和獲取字段的方法,已經(jīng)清空字段的方法clear_;
4.any類型
字段還可以聲明為 Any 類型,可以理解為泛型類型。使用時可以在 Any 中存儲任意消息類型。Any 類型的字段也用 repeated 來修飾,Any 類型是 google 已經(jīng)幫我們定義好的類型,在安裝 ProtoBuf 時,其中的 include 目錄下查找所有google 已經(jīng)定義好的 .proto 文件,在此目錄下查找:/usr/local/protobuf/include/google/protobuf/

我們可以來使用一下:
message Address
{
string home_address=1;
string unit_address=2;
}
message PeopleInfo
{
string name=1;
int32 age=2;
message Phone
{
string number=1;
enum PhoneType
{
MP = 0; // 移動電話
TEL = 1; // 固定電話
}
PhoneType type=2;
}
//多組電話
repeated Phone phone=3;
google.protobuf.Any data = 4;
oneof other_contact
{
// repeated string qq = 5; // 不能使用 repeated
string qq = 5;
string wechat = 6;
}
//備注信息
map<string,string> remake=7;
}上述的代碼中,對于 Any 類型字段:
設置和獲?。韩@取方法的方法名稱與小寫字段名稱完全相同。設置方法可以使用 mutable_ 方
法,返回值為Any類型的指針,這類方法會為我們開辟好空間,可以直接對這塊空間的內(nèi)容進行
修改;之前說過,我們可以在 Any 字段中存儲任意消息類型,這就要涉及到任意消息類型 和 Any 類型的互轉。這部分代碼就在 Google為我們寫好的頭文件 any.pb.h 中。使用方法:
- 使用 PackFrom() 方法可以將任意消息類型轉為 Any 類型。
- 使用 UnpackTo() 方法可以將 Any 類型轉回之前設置的任意消息類型。
- 使用 Is() 方法可以用來判斷存放的消息類型是否為 typename T。
方法使用:
Address address;
cout << "請輸入聯(lián)系人家庭地址: ";
string home_address;
getline(cin, home_address);
address.set_home_address(home_address);
cout << "請輸入聯(lián)系人單位地址: ";
string unit_address;
getline(cin, unit_address);
address.set_unit_address(unit_address);
google::protobuf::Any * data = people_info_ptr->mutable_data();
data->PackFrom(address);
if (people.has_data() && people.data().Is<Address>())
{
Address address;
people.data().UnpackTo(&address);
if (!address.home_address().empty())
{
cout << "家庭地址:" << address.home_address() << endl;
}
if (!address.unit_address().empty())
{
cout << "單位地址:" << address.unit_address() << endl;
}
}5.oneof 類型
如果消息中有很多可選字段, 并且將來同時只有一個字段會被設置, 那么就可以使用 oneof 加強這個行為,也能有節(jié)約內(nèi)存的效果。
oneof other_contact
{
// repeated string qq = 5; // 不能使用 repeated
string qq = 5;
string wechat = 6;
}注意:
- 可選字段中的字段編號,不能與非可選字段的編號沖突。
- 不能在 oneof 中使用 repeated 字段。
- 將來在設置 oneof 字段中值時,如果將 oneof 中的字段設置多個,那么只會保留最后一次設置的成員,之前設置的 oneof 成員會自動清除。
- 上述的代碼中,對于 oneof 字段:會將 oneof 中的多個字段定義為一個枚舉類型。設置和獲取:對 oneof 內(nèi)的字段進行常規(guī)的設置和獲取即可,但要注意只能設置一個。如果設置多個,那么只會保留最后一次設置的成員。清空oneof字段:clear_ 方法,獲取當前設置了哪個字段:_case 方法。
std::cout << "請選擇要添加的其他聯(lián)系方式(1、qq 2、wechat):";
int other_contact;
std::cin>>other_contact;
std::cin.ignore(256, '\n');
if(other_contact==1)
{
std::cout<<"輸入QQ: ";
std::string qq;
getline(std::cin, qq);
people->set_qq(qq);
}
else if(other_contact==2)
{
std::cout<<"輸入wechat: ";
std::string wechat;
getline(std::cin, wechat);
people->set_wechat(wechat);
}
else std::cout<<"輸入錯誤!"<<std::endl;
switch (people.other_contact_case())
{
case PeopleInfo::OtherContactCase::kQq:
cout << "qq號: " << people.qq() << endl;
break;
case PeopleInfo::OtherContactCase::kWeixin:
cout << "微信號: " << people.weixin() << endl;
break;
case PeopleInfo::OtherContactCase::OTHER_CONTACT_NOT_SET:
break;
}6.map類型
語法支持創(chuàng)建一個關聯(lián)映射字段,也就是可以使用 map 類型去聲明字段類型,格式為:
map<key_type, value_type> map_field = N;
要注意的是:
• key_type 是除了 float 和 bytes 類型以外的任意標量類型。 value_type 可以是任意類型。
• map 字段不可以用 repeated 修飾。
• map 中存入的元素是無序的。
使用:
map<string, string> remark = 7; // 備注
清空map: clear_ 方法
• 設置和獲?。韩@取方法的方法名稱與小寫字段名稱完全相同。設置方法為 mutable_ 方法,返回
值為Map類型的指針,這類方法會為我們開辟好空間,可以直接對這塊空間的內(nèi)容進行修改。
7.默認值
反序列化消息時,如果被反序列化的二進制序列中不包含某個字段,反序列化對象中相應字段時,就會設置為該字段的默認值。不同的類型對應的默認值不同:
- 對于字符串,默認值為空字符串。
- 對于字節(jié),默認值為空字節(jié)。
- 對于布爾值,默認值為 false。
- 對于數(shù)值類型,默認值為 0。
- 對于枚舉,默認值是第一個定義的枚舉值, 必須為 0。
- 對于消息字段,未設置該字段。它的取值是依賴于語言。
- 對于設置了 repeated 的字段的默認值是空的( 通常是相應語言的一個空列表 )。
- 對于消息字段、oneof字段和any字段,C++ 和 Java 語言中都有 has_ 方法來檢測當前字段是否被設置。
8.更新消息
更新規(guī)則·
- 禁止修改任何已有字段的字段編號。
- 若是移除老字段,要保證不再使用移除字段的字段編號。正確的做法是保留字段編號(reserved),以確保該編號將不能被重復使用。不建議直接刪除或注釋掉字段。
- int32, uint32, int64, uint64 和 bool 是完全兼容的??梢詮倪@些類型中的一個改為另一個,而不破壞前后兼容性。若解析出來的數(shù)值與相應的類型不匹配,會采用與 C++ 一致的處理方案(例如,若將 64 位整數(shù)當做 32 位進行讀取,它將被截斷為 32 位)。
- sint32 和 sint64 相互兼容但不與其他的整型兼容。
- string 和 bytes 在合法 UTF-8 字節(jié)前提下也是兼容的。
- bytes 包含消息編碼版本的情況下,嵌套消息與 bytes 也是兼容的。
- fixed32 與 sfixed32 兼容, fixed64 與 sfixed64兼容。
- enum 與 int32,uint32, int64 和 uint64 兼容(注意若值不匹配會被截斷)。但要注意當反序列化消息時會根據(jù)語言采用不同的處理方案:例如,未識別的 proto3 枚舉類型會被保存在消息中,但是當消息反序列化時如何表示是依賴于編程語言的。整型字段總是會保持其的值。
- oneof:
- 將一個單獨的值更改為 新 oneof 類型成員之一是安全和二進制兼容的。若確定沒有代碼一次性設置多個值那么將多個字段移入一個新 oneof 類型也是可行的。將任何字段移入已存在的 oneof 類型是不安全的。
保留字段:reserved
- 如果通過 刪除 或 注釋掉 字段來更新消息類型,未來的用戶在添加新字段時,有可能會使用以前已經(jīng)存在,但已經(jīng)被刪除或注釋掉的字段編號。將來使用該 .proto 的舊版本時的程序會引發(fā)很多問題:數(shù)據(jù)損壞、隱私錯誤等等。
- 確保不會發(fā)生這種情況的一種方法是:使用 reserved 將指定字段的編號或名稱設置為保留項 當我們再使用這些編號或名稱時,protocol buffer 的編譯器將會警告這些編號或名稱不可用。
未知字段
未知字段:解析結構良好的 protocol buffer 已序列化數(shù)據(jù)中的未識別字段的表示方式。例如,當舊程序解析帶有新字段的數(shù)據(jù)時,這些新字段就會成為舊程序的未知字段。
本來,proto3 在解析消息時總是會丟棄未知字段,但在 3.5 版本中重新引入了對未知字段的保留機制。所以在 3.5 或更高版本中,未知字段在反序列化時會被保留,同時也會包含在序列化的結果中。
9.UnknownFieldSet 類介紹
- UnknownFieldSet 包含在分析消息時遇到但未由其類型定義的所有字段。
- 若要將 UnknownFieldSet 附加到任何消息,請調(diào)用 Reflection::GetUnknownFields()。
- 類定義在 unknown_field_set.h 中。
10.UnknownField 類介紹
- 表示未知字段集中的一個字段。
- 類定義在 unknown_field_set.h 中。
使用:
const Reflection *reflection = PeopleInfo::GetReflection();
const UnknownFieldSet &unknowSet = reflection->GetUnknownFields(people);
for (int j = 0; j < unknowSet.field_count(); j++)
{
const UnknownField &unknow_field = unknowSet.field(j);
cout << "未知字段" << j + 1 << ":"
<< " 字段編號: " << unknow_field.number()
<< " 類型: " << unknow_field.type();
switch (unknow_field.type())
{
case UnknownField::Type::TYPE_VARINT:
cout << " 值: " << unknow_field.varint() << endl;
break;
case UnknownField::Type::TYPE_LENGTH_DELIMITED:
cout << " 值: " << unknow_field.length_delimited() << endl;
break;
}
}11.前后兼容性
前后兼容的作用,當我們維護一個很大的分布式系統(tǒng)時,由于無法同時升級說有模塊,為了在保證升級過程中,整個系統(tǒng)能夠盡可能不受影響,就需要盡量保證通訊協(xié)議的“向后兼容”或“向前兼容”。
12.選項 option
proto 文件中可以聲明許多選項,使用 option 標注。選項能影響 proto 編譯器的某些處理方式。
選項的完整列表在google/protobuf/descriptor.proto中定義。文件分為字段級,消息級,文件級,但并沒有所有的選項能作用于所有類型,常見舉例
optimize_for:為文件選項,可以設置 protoc 編譯器的優(yōu)化級別,分別為 SPEED 、
CODE_SIZE 、LITE_RUNTIME 。受該選項影響,設置不同的優(yōu)化級別,編譯 .proto 文件后生成的代碼內(nèi)容不同。
- SPEED:protoc編譯器將生成的代碼是高度優(yōu)化的,代碼運行效率高,但生成的代碼更占空間,SPEED為默認選項。
- CODE_SIZE : proto 編譯器將生成最少的類,會占用更少的空間,是依賴基于反射的代碼來實現(xiàn)序列化、反序列化和各種其他操作。但和SPEED 恰恰相反,它的代碼運行效率較低。這種方式適合用在包含大量的.proto文件,但并不盲目追求速度的應用中。
- LITE_RUNTIME : 生成的代碼執(zhí)行效率高,同時生成代碼編譯后的所占用的空間也是非常少。這是以犧牲Protocol Buffer提供的反射功能為代價的,僅僅提供 encoding+序列化 功能,所以我們在鏈接 BP 庫時僅需鏈接libprotobuf-lite,而非libprotobuf。這種模式通常用于資源有限的平臺,例如移動手機平臺中。
- allow_alias : 允許將相同的常量值分配給不同的枚舉常量,用來定義別名。該選項為枚舉選項。
本地版本代碼實現(xiàn):
四:網(wǎng)絡版本通訊錄的實現(xiàn)
Protobuf 還常用于通訊協(xié)議、服務端數(shù)據(jù)交換場景。那么在這個示例中,我們將實現(xiàn)一個網(wǎng)絡版本通訊錄,模擬實現(xiàn)客戶端與服務端的交互,通過 Protobuf 來實現(xiàn)各端之間的協(xié)議序列化。
1.環(huán)境搭建
Httplib 庫:cpp-httplib 是個開源的庫,是一個c++封裝的http庫,使用這個庫可以在linux、
windows平臺下完成http客戶端、http服務端的搭建。使用起來非常方便,只需要包含頭文件
httplib.h 即可。編譯程序時,需要帶上 -lpthread 選項。
鏡像倉庫:weixin_30886717 / cpp-httplib · GitCodecentos 下編寫的注意事項:
如果使用 centOS 環(huán)境,yum源帶的 g++ 最新版本是4.8.5,發(fā)布于2015年,年代久遠。編譯該項目會出現(xiàn)異常。將 gcc/g++ 升級為更高版本可解決問題。
實現(xiàn):
// 自定義異常類
class ContactException
{
private:
std::string message;
public:
ContactException(std::string str = "A problem") : message{str} {}
std::string what() const { return message; }
};
#include <iostream>
#include "contact.pb.h"
#include "ContactException.h"
void menu()
{
std::cout << "-----------------------------------------------------" << std::endl
<< "--------------- 請選擇對通訊錄的操作 ----------------" << std::endl
<< "------------------ 1、新增聯(lián)系人 --------------------" << std::endl
<< "------------------ 2、刪除聯(lián)系人 --------------------" << std::endl
<< "------------------ 3、查看聯(lián)系人列表 ----------------" << std::endl
<< "------------------ 4、查看聯(lián)系人詳細信息 ------------" << std::endl
<< "------------------ 0、退出 --------------------------" << std::endl
<< "-----------------------------------------------------" << std::endl;
}
int main()
{
enum OPERATE
{
QUIT = 0,
ADD,
DEL,
FIND_ALL,
FIND_ONE
};
while (true)
{
menu();
std::cout << "---> 請選擇:";
int choose;
std::cin >> choose;
std::cin.ignore(256, '\n');
try
{
switch (choose)
{
case OPERATE::ADD:
contactsServer.addContact();
break;
case OPERATE::DEL:
contactsServer.delContact();
break;
case OPERATE::FIND_ALL:
contactsServer.findContacts();
break;
case OPERATE::FIND_ONE:
contactsServer.findContact();
break;
case 0:
std::cout << "---> 程序已退出" << std::endl;
return 0;
default:
std::cout << "---> 無此選項,請重新選擇!" << std::endl;
break;
}
}
catch (const ContactException &e)
{
std::cerr << "---> 操作通訊錄時發(fā)現(xiàn)異常!??!" << std::endl
<< "---> 異常信息:" << e.what() << std::endl;
}
catch (const std::exception &e)
{
std::cerr << "---> 操作通訊錄時發(fā)現(xiàn)異常!??!" << std::endl
<< "---> 異常信息:" << e.what() << std::endl;
}
}
}
class ContactsServer
{
public:
void addContact();
void delContact();
void findContacts();
void findContact();
private:
void buildAddContactRequest(add_contact_req::AddContactRequest* req);
void printFindOneContactResponse(find_one_contact_resp::FindOneContactResponse&
resp);
void printFindAllContactsResponse(find_all_contacts_resp::FindAllContactsResponse&
resp);
};五:總結:

到此這篇關于C/C++ProtoBuf使用的文章就介紹到這了,更多相關C++ProtoBuf使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
windows系統(tǒng)下C++調(diào)用matlab程序的方法詳解
這篇文章主要給大家介紹了關于在windows系統(tǒng)下C++調(diào)用matlab程序的方法,文中通過示例代碼介紹的非常詳細,對大家學習或者使用C++具有一定的參考學習價值,需要的朋友們下面跟著小編來一起學習學習吧。2017-08-08
C++?Socket實現(xiàn)TCP與UDP網(wǎng)絡編程
本文主要介紹了C++?Socket實現(xiàn)TCP與UDP網(wǎng)絡編程,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01

