Protobuf語(yǔ)法介紹
何為Protobuf
我們先看看官方文檔給出的定義和描述
protocol buffers 是一種語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)、可擴(kuò)展的序列化結(jié)構(gòu)數(shù)據(jù)的方法,它可用于(數(shù)據(jù))通信協(xié)議、數(shù)據(jù)存儲(chǔ)等。
Protocol Buffers 是一種靈活,高效,自動(dòng)化機(jī)制的結(jié)構(gòu)數(shù)據(jù)序列化方法-可類(lèi)比 XML,但是比 XML 更?。? ~ 10倍)、更快(20 ~ 100倍)、更為簡(jiǎn)單。
你可以定義數(shù)據(jù)的結(jié)構(gòu),然后使用特殊生成的源代碼輕松的在各種數(shù)據(jù)流中使用各種語(yǔ)言進(jìn)行編寫(xiě)和讀取結(jié)構(gòu)數(shù)據(jù)。你甚至可以更新數(shù)據(jù)結(jié)構(gòu),而不破壞由舊數(shù)據(jù)結(jié)構(gòu)編譯的已部署程序。
簡(jiǎn)單來(lái)講, ProtoBuf 是結(jié)構(gòu)數(shù)據(jù)序列化方法,可簡(jiǎn)單類(lèi)比于,其具有以下特點(diǎn):
- 語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)。即 ProtoBuf 支持 Java、C++、Python 等多種語(yǔ)言,支持多個(gè)平臺(tái)
- 高效。即比 XML 更?。? ~ 10倍)、更快(20 ~ 100倍)、更為簡(jiǎn)單
- 擴(kuò)展性、兼容性好。你可以更新數(shù)據(jù)結(jié)構(gòu),而不影響和破壞原有的舊程序
使用Protobuf
在protobuf中,協(xié)議是由一系列的消息組成的。因此最重要的就是定義通信時(shí)使用到的消息格式。一個(gè)Protobuf 消息(對(duì)應(yīng)JAVA類(lèi)),由至少一個(gè)字段(對(duì)應(yīng)Java類(lèi)屬性)組合而成。
消息的定義
消息的定義很簡(jiǎn)單,就是message關(guān)鍵字加上消息的名字
message xxx{
}
字段的定義
字段定義格式:
限定修飾符 | 數(shù)據(jù)類(lèi)型 | 字段名稱(chēng) | = | 字段編碼
required string room_id = 1; // 直播間id
限定修飾符(包含required、optional、Repeated)
required:
表示是一個(gè)必須字段,必須相對(duì)于發(fā)送方,在發(fā)送消息之前必須設(shè)置該字段的值,對(duì)于接收方,必須能夠識(shí)別該字段的意思。發(fā)送之前沒(méi)有設(shè)置required字段或者無(wú)法識(shí)別required字段都會(huì)引發(fā)編解碼異常,導(dǎo)致消息被丟棄。
optional:
表示是一個(gè)可選字段,可選對(duì)于發(fā)送方,在發(fā)送消息時(shí),可以有選擇性的設(shè)置或者不設(shè)置該字段的值。對(duì)于接收方,如果能夠識(shí)別可選字段就進(jìn)行相應(yīng)的處理,如果無(wú)法識(shí)別,則忽略該字段,消息中的其它字段正常處理。---因?yàn)閛ptional字段的特性,很多接口在升級(jí)版本中都把后來(lái)添加的字段都統(tǒng)一的設(shè)置為optional字段,這樣老的版本無(wú)需升級(jí)程序也可以正常的與新的軟件進(jìn)行通信,只不過(guò)新的字段無(wú)法識(shí)別而已,因?yàn)椴⒉皇敲總€(gè)節(jié)點(diǎn)都需要新的功能,因此可以做到按需升級(jí)和平滑過(guò)渡。
repeated:
表示該字段可以包含0~N個(gè)元素。其特性和optional一樣,但是每一次可以包含多個(gè)值??梢钥醋魇窃趥鬟f一個(gè)數(shù)組的值。類(lèi)比于Java這邊的List
數(shù)據(jù)類(lèi)型
protobuf定義了一套基本的數(shù)據(jù)類(lèi)型,幾乎都映射了Java語(yǔ)言的基礎(chǔ)數(shù)據(jù)類(lèi)型(主要以Java為例)
詳見(jiàn)下面表格

字段的默認(rèn)值
- 對(duì)于strings,默認(rèn)是一個(gè)空string
- 對(duì)于bytes,默認(rèn)是一個(gè)空的bytes
- 對(duì)于bools,默認(rèn)是false
- 對(duì)于數(shù)值類(lèi)型,默認(rèn)是0
- 對(duì)于枚舉,默認(rèn)是第一個(gè)定義的枚舉值,必須為0
字段名稱(chēng)
字段的命名方式與Java的命名方式大致一致
字段編碼值
字段編碼是一個(gè)序列化和反序列化的標(biāo)記值,有了該值,通信雙方才能互相識(shí)別對(duì)方的字段。當(dāng)然相同的編碼值,其限定修飾符和數(shù)據(jù)類(lèi)型必須相同。編碼值的取值范圍為 1~2^32(4294967296)
其中 1~15的編碼時(shí)間和空間效率都是最高的,編碼值越大,其編碼的時(shí)間和空間效率就越低(相對(duì)于1-15),當(dāng)然一般情況下相鄰的2個(gè)值編碼效率的是相同的,除非2個(gè)值恰好實(shí)在4字節(jié),12字節(jié),20字節(jié)等的臨界區(qū)。比如15和16
1900~2000編碼值為Google protobuf 系統(tǒng)內(nèi)部保留值,建議不要在自己的項(xiàng)目中使用。protobuf 還建議把經(jīng)常要傳遞的值把其字段編碼設(shè)置為1-15之間的值
完整的消息定義示例
message CSSendLiveRoomMsgReq
{
required string room_id = 1; // 直播間id
required uint32 local_msgid = 2; // 發(fā)送客戶端本地的消息id,用以處理回執(zhí)消息
required ImMsgBody im_message_body = 3; // 消息內(nèi)容
optional string from_username = 4;
optional uint32 priority = 5; // 消息優(yōu)先級(jí)別
}
枚舉值
枚舉的定義和Java 相同,使用enum關(guān)鍵字,但是有一些限制。
枚舉值必須大于等于0的整數(shù)。
使用分號(hào);分隔枚舉變量而不是Java 語(yǔ)言中的逗號(hào),
示例:
enum ACCOUNT_TYPE
{
ACCOUNT_TYPE_IM_ACCOUNT = 1;
ACCOUNT_TYPE_VIVO_OPENID = 2;
ACCOUNT_TYPE_ANONYMOUS_ACCOUNT = 3;
}
使用其他消息類(lèi)型
你可以將其他消息類(lèi)型用作字段類(lèi)型。已我們現(xiàn)在IM的為例,在每一個(gè)CSSendLiveRoomMsgReq消息中包含ImMsgBody消息,此時(shí)可以在相同的.proto文件中定義一個(gè)ImMsgBody消息類(lèi)型,然后在CSSendLiveRoomMsgReq消息中指定一個(gè)ImMsgBody類(lèi)型的字段
示例:
// 直播間消息下發(fā)
message CSNotifyLiveRoomMsg
{
required string room_id = 1; // 直播間id
required uint64 msg_id = 2; //消息ID
required uint64 timestamp = 3; //消息生成的時(shí)間戳
required ImMsgBody im_message_body = 4;
optional string from_username = 5;
}
message ImMsgBody
{
required int32 message_type = 1;
optional uint32 message_flag = 2; //消息標(biāo)志位,留待后續(xù)試用,可以用來(lái)做一些特殊處理的標(biāo)志,比如要不要做離線緩存等
oneof real_message
{
ImTextMessage text_message = 3;
ImVoiceMessage voice_message = 4;
ImAppMessage app_message = 5;
ImImageMessage image_message = 6;
ImVideoMessage video_message = 7;
ImFileMessage file_message = 8;
ImLocateMessage locate_message = 9;
ImH5Message h5_message = 10;
}
}
Oneof
如果你的消息中有很多可選字段, 并且同時(shí)至多一個(gè)字段會(huì)被設(shè)置, 你可以加強(qiáng)這個(gè)行為,使用oneof特性節(jié)省內(nèi)存,Oneof字段就像可選字段, 除了它們會(huì)共享內(nèi)存, 至多一個(gè)字段會(huì)被設(shè)置。 設(shè)置其中一個(gè)字段會(huì)清除其它字段。
為了在.proto定義Oneof字段, 你需要在名字前面加上oneof關(guān)鍵字, 比如下面例子的ImMsgBody:
message ImMsgBody
{
required int32 message_type = 1;
optional uint32 message_flag = 2;
oneof real_message
{
ImTextMessage text_message = 3;
ImVoiceMessage voice_message = 4;
ImAppMessage app_message = 5;
ImImageMessage image_message = 6;
ImVideoMessage video_message = 7;
ImFileMessage file_message = 8;
ImLocateMessage locate_message = 9;
ImH5Message h5_message = 10;
}
}
oneof的特性
- 設(shè)置oneof會(huì)自動(dòng)清楚其它oneof字段的值. 所以設(shè)置多次后,只有最后一次設(shè)置的字段有值
- 如果解析器遇到同一個(gè)oneof中有多個(gè)成員,只有最后一個(gè)會(huì)被解析成消息
- oneof不支持repeated