作爲Google出品的開源項目,其性能和效率是非常出衆的,只是支持的語言不多,普及率沒有thrift、JSon等廣,但我相信衆人拾柴火焰高,其使用率肯定會越來越高的。使用Protobuf時間不長,僅僅摘記普遍的流程方法,供大家參考,也方便自己溫故。
1.protoc協議編寫
package com.MyCompany; //包
message Person
{
required string name = 1;
required sint32 age = 2;
required string home = 3;
enum HOBBY
{
RUNNING = 0;
BADMINTION = 1;
PINGPONG = 3;
}
optional HOBBY hobby = 4[default = RUNNING];
}
message Company
{
repeated Person staff = 1[packed=true];
required Person boss = 2;
enum FIELD
{
IT = 0;
MECHINE = 1;
SECURITY = 2;
}
required FIELD field = 3;
required fixed32 staff_num = 4;
optional fixed32 rank = 5[default = 1];
repeated uint32 vecInt = 6;
}
protobuf的一些數據類型就不說了,大家可以參考別的一些資料。
從上面的例子中,大家可以知道protobuf是支持嵌套類型的,protoc2需要在聲明成員前標記修飾符,但在protoc3中除了repeated,optional和required是不需要的,以下僅就protoc2討論:
required:一個格式良好的消息一定要含有1個這種字段。表示該值是必須要設置的;
optional:消息格式中該字段可以有0個或1個值(不超過1個)。
repeated:在一個格式良好的消息中,這種字段可以重複任意多次(包括0次)。重複的值的順序會被保留。表示該值可以重複,相當於java和C++中的List。
注意:變量後的賦值 如 required FIELD field = 2;這個不是對成員field 進行賦值,只是作爲變量的位置標記,具體的成員賦值是在實例化後進行的,但修飾符爲optional時,可以設置默認值,如:
optional fixed32 rank = 4[default = 1];
分配標籤:每個field都是唯一數字的標記,這是用來標記這個field在message二進制格式中的位置的,一旦使用就不能再修改順序了。
注:標記從1-15只有一個字節編碼,包括自增長屬性(更多的見Protocol Buffer Encoding)標記從16-2047佔用兩個字節。因此儘量頻繁使用1-15,記住爲未來的擴展留下一些位置。最小的tag你可以定義爲1,最大2的29次方-1 :536870922.你同樣不能使用19000-19999(這個位置已經被GPB自己實現)。
指定field規則:由於歷史原因,repeated字段如果是基本數字類型的話,不能有效地編碼。現在代碼可以使用特殊選項[packed=true]來得到更有效率的編碼。
注: 由於required是永遠的,應該非常慎重地給message某個字段設置爲required。如果未來你希望停止寫入或者輸出某個required字段,那就會成爲問題;因爲舊的reader將以爲沒有這個字段無法初始化message,會丟掉這部分信息。一些來自google的工程師們指出使用required弊大於利,儘量使用optional和repeated。
2.編譯
寫好.proto文件後,需要用protoc命令將其進行
protoc -I=輸入目錄 - -cpp_out=輸出目錄 yourprotobuf.proto
如果yourprotobuf.proto在當前目錄下,且編譯到當前目錄,則命令爲:
protoc -I=. --cpp_out=. yourprotobuf.proto
生成兩個文件yourprotobuf.pb.cc和yourprotobuf.pb.h。
我麼只需#include “yourprotobuf.pb.h”就行。
3.序列化/反序列化
先附上代碼:
#include "yourprotobuf.pb.h"
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/pool/pool.hpp>
using namespace com::MyCompany;
using namespace std;
using namespace boost;
int main(int argc, char *argv[])
{
pool<> pl(sizeof(char) * 100);
char *pTo = static_cast<char *>(pl.malloc());
// char *pFrom = static_cast<char *>(pl.malloc());
Company company;
Person *person = company.add_staff();
person->set_age(27);
person->set_name("Kasen");
person->set_home("NB");
person->set_hobby(Person_HOBBY_BADMINTION);
Person *person1 = company.add_staff();
person1->set_age(27);
person1->set_name("wangzhijie");
person1->set_home("shandong");
person1->set_hobby(Person_HOBBY_BADMINTION);
Person *boss = new Person();
boss->set_age(50);
boss->set_name("i_do_not_know");
boss->set_home("china");
boss->set_hobby(Person_HOBBY_BADMINTION);
company.set_allocated_boss(boss);
company.set_staff_num(10000);
company.set_field(Company_FIELD_SECURITY);
company.set_rank(1);
for(int i = 0; i < 5; ++i)
company.add_vecint(5);
company.set_vecint(4,9);
int iNeedSize = company.ByteSize();
company.SerializeToArray(pTo,iNeedSize);
Company company1;
company1.ParseFromArray(pTo,iNeedSize);
cout<<company1.staff(0).name()<<endl
<<"staff number:"<<company1.staff_size()<<endl;
return 1;
}
我們需要注意一下幾點:
3.1 命令空間
如果沒有
using namespace com::MyCompany;
則對Person和Company的實例化變爲:
com::hikvision::Person person
com::hikvision::Company company
3.2 repeated和required修飾符成員賦值
如果成員是類類型的,如上述代碼中,staff和boss都是Person類型的,用repeated和required兩種修飾符進行修飾時,賦值方式不一樣。
repeated:
Person *person = company.add_staff();
已經將對象person裝入company對象的list< Person >容器中了。
required:
Person boss;
.......
company.set_allocated_boss(&boss);
生成一個Person類對象後,賦值完後再添加到對象company中.
####*3.3 類大小*
對類對象(修飾符爲required或者optional)的賦值有兩種方法,如在以上代碼中,對成員boss的賦值除了以上的方法外,還有一種:
Person *boss = company.mutable_boss();
boss->set_age(50);
boss->set_name("i_do_not_know");
boss->set_home("china");
boss->set_hobby(Person_HOBBY_BADMINTION);
切記不可如下:
Person boss;
boss.set_age(50);
boss.set_name("i_do_not_know");
boss.set_home("china");
boss.set_hobby(Person_HOBBY_BADMINTION);
company.set_allocated_boss(&boss);
這樣boss爲局部變量,函數返回時就釋放。
####*3.4 類大小*
在用接口SerializeToArray()進行序列化時,需要輸入參數類大小,從.cc文件裏我們可以看到每個類都有接口ByteSize(),可以知道對象company實際大小,而函數sizeof()只能計算類的固定大小,而不會計算對象實際用了多大的內存。