protobuf序列化通信協議

一、概念
protobuf是Google開發的開源項目。protobuf是一種串行化的數據結構信息,類似xml。主要用於消息傳輸中消息格式規範,數據存儲。與xml相比,protobuf更小、更快、更簡單、更少歧義、方便生成消息存取類。
二、protocol buffer消息優點
1.性能好,效率高
主要與xml消息格式對比。
在時間開銷上,xml序列化開銷不大,但解析開銷很大,降低了系統性能;
在空間開銷上,xml爲了可讀性,使用了大量冗餘信息
protobuf是爲了解決xml性能低的問題,而重新設計的規範;
2.代碼生成機制
protobuf只需要寫個.proto文件,使用protoc.exe編譯生成消息的封裝類(包括序列化和解析),用戶可以不用自己寫解析方法。
3.兼容性好
包括向後兼容及向前兼容。
向後兼容:A版本是老版本,B版本是在A的基礎上升級後的版本,那麼A版本發出的消息,使用B版本也能接收,只需要將B版本擴充的字段設置爲可選或者賦給初始值;
向前兼容:A版本是老版本,B版本是在A的基礎上升級後的版本,那麼A版本也能夠接收B版本發送的消息,只不過擴充的字段會被忽略。
4.支持跨平臺,多語言
包括C++、java、python、javaNano、JavaScript、C#、ruby、php等等,Google官方發佈的源碼提供C++、java、python三種語言支持,其他的是開源社區補充的。
三、protobuf缺點
1、可讀性差
爲了提高性能,protobuf消息傳輸採用二進制,導致消息雙方出現問題,即使抓包也難以看懂消息。
2、缺乏自描述
沒有.proto文件很難看懂消息格式。
三、.proto文件
1.message定義
message 消息名{....}
消息名採用駱駝風格定義,或GNU風格
enum 枚舉名{類型=值...}
message和enum編譯後都會生成一個類
2.聲明字段
修飾符 字段類型 字段名=字段編碼值 [可選配置,例如default=默認值]
修飾符有required、optional、repeated
required表示不能缺省,如果build消息時,該字段缺省會拋RuntimeException,如果接收消息時,解析消息沒有該字段會拋IOException;
optional表示可以缺省;
repeated表示可以重複,在生成的封裝類裏是個list。
字段的類型可以使用protobuf規定的一些標準類型及其他message類型或枚舉型
protobuf字段標準類型:
類型 描述 打包占用字節 java對應類型
bool 布爾型 1個字節 boolean
double 64位浮點型 N double
float 32位浮點型 N float
int32 32位整型 N int
int64 64位整型 N long
uint32 無符號32整數 N int[1]
unit64 無符號64位整數 N long[1]
sint32 32位整數,處理負數效率高 N int
sint64 64位,處理負數效率高 N long
fixed32 無符號32整數 4 int[1]
fixed64 無符號64位整數 8 long[1]
sfixed32 32位整數,處理負數效率更高 4 int
sfixed64 64位整型,處理負數效率更高 8 long
string 一個字符串必須是UTF-8編碼或者7-bit ASCII編碼的文本。 N String
bytes 用於處理多字節語言的字符,如中文 N ByteString
上述列表中N表示打包時字節數不固定。
對於int32如果數值在0~127之間時使用一個字節打包。
對於int32和fixed32比較,如果值總是大於2^28時,效率比uint32高效
對於枚舉型,打包方式和uint32相似,因此對於負數效率不高,枚舉型儘量不要用負數
字段編碼值:有了該值通信雙方纔能識別消息字段。相同的編碼值的字段,其修飾符和數據類型必須相同。編碼值的取值範圍是1~2^32。
其中1~15的碼值的字段其時間和空間的效率都是最高的,值越大效率越低。其中19000-19999範圍的碼值是google protobuf內部保留值。
建議經常要傳遞值的字段的編碼值放在1~15之間。
3.導入
import"proto文件路徑";
導入類似java的import,就是允許一個proto文件裏引入其他proto文件定義的消息。當然一個proto文件裏可以定義多個消息。如果你想在一個message定義了裏引入其他message作爲字段類型,則需要在message定義前將那個message使用import導入進這個message文件。
4.嵌套類型
可以在一個消息定義內部定義一個新的消息。
message A{
message B{
}
optional B b=2;
}
嵌套類型在消息體內部可以直接作爲字段類型,也可以在外部消息內調用;
message C{
optional A.B b=1;
}
5.註釋
proto文件使用的註釋和java類似,使用//
6.更新.proto字段
出於保持與舊版本的兼容性
1.不能更改任何已有字段的編碼值
2.添加的字段都不能是required
3.非required的字段可以移除,但是編碼值不要再使用
4.int32, uint32, int64, uint64,和bool是全部兼容的,這意味着可以將這些類型中的一個轉換爲另外一個,而不會破壞向前、 向後的兼容性。如果解析出來的數字與對應的類型不相符,那麼結果就像在C++中對它進行了強制類型轉換一樣(例如,如果把一個64位數字當作int32來 讀取,那麼它就會被截斷爲32位的數字)。
5.sint32和sint64是互相兼容的,但是它們與其他整數類型不兼容。
6.string和bytes是兼容的——只要bytes是有效的UTF-8編碼
7.嵌套消息與bytes是兼容的——只要bytes包含該消息的一個編碼過的版本
8.fixed32與sfixed32是兼容的,fixed64與sfixed64是兼容的。
7.擴展.proto文件
1.在.proto文件的消息裏使用
extensions 編碼值1 to 編碼值2;
編碼值1和2表示了可擴展的編碼範圍,擴展的字段的編碼值需要在此範圍內
2.添加擴展字段
extend 消息名{
optional 字段類型 字段名=編碼值3;
}
編碼值3需要在編碼值1和2之間;
2.1可以在要擴展的消息內部定義擴展字段
message A{
extensions 編碼值1 to 編碼值2;
}
extend A{
字段類型 字段名=編碼值 [default=默認值];
}
2.2可以在其他消息內定義擴展
message B{
extend A{
字段類型 字段名=編碼值 [default=默認值];
}
}
或者
message B{
}
extend A{
字段類型 字段名=編碼值 [default=默認值];
}
8.包
防止消息類型命名衝突,可以使用package
package 包路徑;
例如:
package a;
message A{}
在其他消息類型定義中定義A類型的字段
message B{
optional a.A 字段名=編碼值 [default=默認值];
}
9.聲明編譯參數
編譯時的選項分爲文件級、消息級、字段級,文件級是放在.proto文件開頭,消息級放在消息體內部,字段級在定義字段的末尾使用[]包括。
1.option java_package="java類的包路徑";
2.option java_outer_className="生成的java文件類名";
java文件類名不能與消息名相同,不然編譯會失敗
3.option optimize_for=選項;
optimize_for是文件級別的選項,有三個可選值:
1.SPEED是在消息類型上進行序列化、語法分析及其他通用操作,生成的代碼運行效率是最高的。
但是佔用空間比較大。
2.CODE_SIZE是通過共享或基於反射的代碼實現序列化、語法分析及其他各種操作,生成的類更少,佔用的空間比SPEED小的多。
但是運行效率要低。
3.LITE_RUNTIME生成的代碼執行效率高,同時生成代碼編譯後的所佔用的空間也是非常少;
4.cc_generic_services, java_generic_services, py_generic_services這三個選項都是文件級選項,值默認是是true。表示編譯時編譯器是否應該根據服務定義
產生抽象的服務代碼。
5.message_set_wire_format是一個消息級選項。值爲true或false,如果設置爲true,編譯時會採用一種不同的二進制格式來與google內部的MessageSet老格式兼容。
對於Google外部用戶來說,此選項用不到。
6.packed 這是一個字段級選項。如果該選項在一個整型基本類型上被設置爲真,則採用更緊湊的編碼方式。當然使用該值並不會對數值造成任何損失。
optional int32 字段名=編碼值 [packed=true]
7.deprecated 這是一個字段級選項。如果該選項被設置爲true,表明該字段已經被棄用了,在新代碼中不建議使用。在多數語言中,這並沒有實際的含義。在java中,它將會變成一個 @Deprecated註釋。
optional 字段類型 字段名 = 編碼值 [deprecated=true];
8.自定義選項,所有options放在protobuf源碼裏的src/protobuf/descriptor.proto裏。
可以自定義擴展字段
10.定義服務
定義一個rpc服務
service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}
11.編譯
使用protoc.exe命令
protoc  --java_out=生成java文件根目錄 proc文件路徑 protoc文件

protoc.exe命令需要編譯源碼才能生成。可以到github上下載源碼。編譯前可以閱讀cmake文件夾裏的README文件。編譯需要visual studio環境,以及,cmake工具,缺少的話,需要自己先安裝此兩個工具。然後可以打開cmake工具界面,配置下protobuf文件夾下的cmake目錄,以及編譯後的文件的存放路徑(即安裝路徑)即可運行編譯。編譯完成後在安裝路徑下找到Release目錄或debug目錄,裏面就有protoc.exe工具了。爲方便使用建議配置環境變量。

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