protocolbuffer(以下簡(jiǎn)稱PB)是google 的一種數(shù)據(jù)交換的格式仅叫,它獨(dú)立于語言膏斤,獨(dú)立于平臺(tái)盅惜。google 提供了多種語言的實(shí)現(xiàn):java中剩、c#、c++抒寂、go 和 python结啼,每一種實(shí)現(xiàn)都包含了相應(yīng)語言的編譯器以及庫文件。由于它是一種二進(jìn)制的格式屈芜,比使用 xml 進(jìn)行數(shù)據(jù)交換快許多郊愧。可以把它用于分布式應(yīng)用之間的數(shù)據(jù)通信或者異構(gòu)環(huán)境下的數(shù)據(jù)交換井佑。作為一種效率和兼容性都很優(yōu)秀的二進(jìn)制數(shù)據(jù)傳輸格式属铁,可以用于諸如網(wǎng)絡(luò)傳輸、配置文件躬翁、數(shù)據(jù)存儲(chǔ)等諸多領(lǐng)域焦蘑。
1.ProtoBuf協(xié)議說明
proto文件定義了協(xié)議數(shù)據(jù)中的實(shí)體結(jié)構(gòu)(message ,field)
- 關(guān)鍵字message: 代表了實(shí)體結(jié)構(gòu),由多個(gè)消息字段(field)組成姆另。
- 消息字段(field): 包括數(shù)據(jù)類型喇肋、字段名坟乾、字段規(guī)則迹辐、字段唯一標(biāo)識(shí)、默認(rèn)值
- 數(shù)據(jù)類型:如下圖所示
- 字段規(guī)則:
required:必須初始化字段甚侣,如果沒有賦值明吩,在數(shù)據(jù)序列化時(shí)會(huì)拋出異常
optional:可選字段,可以不必初始化殷费。
repeated:數(shù)據(jù)可以重復(fù)(相當(dāng)于java 中的Array或List)
字段唯一標(biāo)識(shí):序列化和反序列化將會(huì)使用到印荔。
- 默認(rèn)值:在定義消息字段時(shí)可以給出默認(rèn)值低葫。
2.ProtoBuf的使用流程
1.定義.proto文件
首先我們需要編寫一個(gè) proto 文件,定義我們程序中需要處理的結(jié)構(gòu)化數(shù)據(jù)仍律,在 protobuf 的術(shù)語中嘿悬,結(jié)構(gòu)化數(shù)據(jù)被稱為 Message。proto 文件非常類似 java 或者 C 語言的數(shù)據(jù)定義水泉。下面代碼顯示了例子應(yīng)用中的 proto 文件內(nèi)容:
package lm;
message helloworld
{
required int32 id = 1; // ID
required string str = 2; // str
optional int32 opt = 3; //optional field
}
一個(gè)比較好的習(xí)慣是認(rèn)真對(duì)待 proto 文件的文件名善涨。比如將命名規(guī)則定于如下:
packageName.MessageName.proto
在上例中,package 名字叫做 lm草则,定義了一個(gè)消息 helloworld钢拧,該消息有三個(gè)成員,類型為 int32 的 id炕横,另一個(gè)為類型為 string 的成員 str源内。opt 是一個(gè)可選的成員,即消息中可以不包含該成員份殿。
2.編譯.proto文件
寫好 proto 文件之后就可以用 Protobuf 編譯器將該文件編譯成目標(biāo)語言了膜钓。可以根據(jù)不同的語言來選擇不同的編譯方式
在項(xiàng)目中我使用到的是分別是JavaScript和Java伯铣,前者是客戶端腳本語言呻此,后者服務(wù)端語言。將proto文件編譯生成java文件腔寡,這里有一個(gè)很好的案例:《Protobuf協(xié)議的Java應(yīng)用例子》焚鲜,當(dāng)然在項(xiàng)目中一般是不會(huì)這么干的,
3.序列化和反序列化
public class Test {
public static void main(String[] args) throws IOException {
//模擬將對(duì)象轉(zhuǎn)成byte[]放前,方便傳輸
PersonEntity.Person.Builder builder = PersonEntity.Person.newBuilder();
builder.setId(1);
builder.setName("ant");
builder.setEmail("ghb@soecode.com");
PersonEntity.Person person = builder.build();
System.out.println("before :"+ person.toString());
System.out.println("===========Person Byte==========");
for(byte b : person.toByteArray()){
System.out.print(b);
}
System.out.println();
System.out.println(person.toByteString());
System.out.println("================================");
//模擬接收Byte[]忿磅,反序列化成Person類
byte[] byteArray =person.toByteArray();
Person p2 = Person.parseFrom(byteArray);
System.out.println("after :" +p2.toString());
}
}
從上面可以總結(jié)出protobuf的使用過程可以分為以下三個(gè),準(zhǔn)備好數(shù)據(jù)凭语,通過build()方法來組裝成protobuf包葱她,然后通過toByteArray()來將protobuf轉(zhuǎn)換成二進(jìn)制序列流文件(序列化)。
反序列化的過程剛好與之相反似扔,接收到的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成二進(jìn)制數(shù)組byte[]吨些,然后調(diào)用protobuf的parseFrom()方法即可實(shí)現(xiàn)反序列化。
3.protoBuf數(shù)據(jù)協(xié)議的優(yōu)勢(shì)
- 平臺(tái)無關(guān)炒辉,語言無關(guān)豪墅,可擴(kuò)展;
- 提供了友好的動(dòng)態(tài)庫黔寇,使用簡(jiǎn)單偶器;
- 解析速度快,比對(duì)應(yīng)的XML快約20-100倍;
- 序列化數(shù)據(jù)非常簡(jiǎn)潔屏轰、緊湊颊郎,與XML相比,其序列化之后的數(shù)據(jù)量約為1/3到1/10霎苗。
說明:
數(shù)據(jù)量小是因?yàn)槟房裕琍rotobuf 序列化后所生成的二進(jìn)制消息非常緊湊,這得益于 Protobuf 采用的非常巧妙的little-endian編碼方法唁盏。
轉(zhuǎn)換速度快猾编。首先我們來了解一下 XML 的封解包過程。XML 需要從文件中讀取出字符串升敲,再轉(zhuǎn)換為 XML 文檔對(duì)象結(jié)構(gòu)模型答倡。之后,再從 XML 文檔對(duì)象結(jié)構(gòu)模型中讀取指定節(jié)點(diǎn)的字符串驴党,最后再將這個(gè)字符串轉(zhuǎn)換成指定類型的變量瘪撇。這個(gè)過程非常復(fù)雜,其中將 XML 文件轉(zhuǎn)換為文檔對(duì)象結(jié)構(gòu)模型的過程通常需要完成詞法文法分析等大量消耗 CPU 的復(fù)雜計(jì)算港庄。
反觀 Protobuf倔既,它只需要簡(jiǎn)單地將一個(gè)二進(jìn)制序列,按照指定的格式讀取到 C++ 對(duì)應(yīng)的結(jié)構(gòu)類型中就可以了鹏氧。從上一節(jié)的描述可以看到消息的 decoding 過程也可以通過幾個(gè)位移操作組成的表達(dá)式計(jì)算即可完成渤涌。速度非常快把还。