最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

IM通訊協(xié)議專題學(xué)習(xí)(一):Protobuf從入門到精通,一篇就夠!

2022-11-10 11:39 作者:nickkckckck  | 我要投稿

本文由IBM開發(fā)者社區(qū)分享,有較多修訂和改動(dòng)。

1、引言

在當(dāng)今移動(dòng)網(wǎng)絡(luò)時(shí)代,手機(jī)流量和電量是寶貴的資源,對(duì)于移動(dòng)端最常見的即時(shí)通訊IM應(yīng)用,由于實(shí)時(shí)通信基于Socket長連接,它對(duì)于流量和電量的需求較一般應(yīng)用來說更高(詳見《移動(dòng)端IM實(shí)踐:WhatsApp、Line、微信的心跳策略分析》)。

在IM應(yīng)用中,優(yōu)化數(shù)據(jù)流量消耗過多的基本方法就是使用高度壓縮的通訊協(xié)議,而數(shù)據(jù)壓縮后流量減小帶來的自然結(jié)果也就是省電:因?yàn)榇髷?shù)據(jù)量的傳輸必然需要更久的網(wǎng)絡(luò)操作、數(shù)據(jù)序列化及反序列化操作,這些都是電量消耗過快的根源。

當(dāng)前IM應(yīng)用中最熱門的通訊協(xié)議無疑就是Google的Protobuf了,基于它的優(yōu)秀表現(xiàn),微信和手機(jī)QQ這樣的主流IM應(yīng)用也早已在使用它。

本文作為《IM通訊協(xié)議專題學(xué)習(xí)》系列文章的首篇,將從初學(xué)者的角度,用通俗簡潔的文字,從零開始為你介紹Protobuf的方方面面,特別適合新手入門。

?

學(xué)習(xí)交流:

- 移動(dòng)端IM開發(fā)入門文章:《新手入門一篇就夠:從零開發(fā)移動(dòng)端IM》

- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點(diǎn)此)

(本文已同步發(fā)布于:http://www.52im.net/thread-4080-1-1.html)

2、系列文章

本文是系列文章中的第?1?篇,本系列總目錄如下:

  • 《IM通訊協(xié)議專題學(xué)習(xí)(一):Protobuf從入門到精通,一篇就夠!》(*?本文)

  • 《IM通訊協(xié)議專題學(xué)習(xí)(二):快速理解ProtoBuf的背景、原理、使用、優(yōu)缺點(diǎn)》(稍后發(fā)布..)

  • 《IM通訊協(xié)議專題學(xué)習(xí)(三):由淺入深,從通信編解碼原理上理解Protobuf》(稍后發(fā)布..)

  • 《IM通訊協(xié)議專題學(xué)習(xí)(四):從Base64到Protobuf,詳解Protobuf的數(shù)據(jù)編碼原理》(稍后發(fā)布..)

  • 《IM通訊協(xié)議專題學(xué)習(xí)(五):Protobuf到底比JSON快幾倍?請(qǐng)看全方位實(shí)測!》(稍后發(fā)布..)

  • 《IM通訊協(xié)議專題學(xué)習(xí)(六):手把手教你如何在Android上從零使用Protobuf》(稍后發(fā)布..)

  • 《IM通訊協(xié)議專題學(xué)習(xí)(七):手把手教你如何在NodeJS中從零使用Protobuf》(稍后發(fā)布..)

  • 《IM通訊協(xié)議專題學(xué)習(xí)(八):金蝶隨手記團(tuán)隊(duì)的Protobuf應(yīng)用實(shí)踐(原理篇)??》(稍后發(fā)布..)

  • 《IM通訊協(xié)議專題學(xué)習(xí)(九):金蝶隨手記團(tuán)隊(duì)的Protobuf應(yīng)用實(shí)踐(實(shí)戰(zhàn)篇) 》(稍后發(fā)布..)

3、什么是Protocol Buffer?

什么是 Google Protocol Buffer?

假如您在網(wǎng)上搜索,應(yīng)該會(huì)得到類似于下面這樣的文字介紹:

Google Protocol Buffer(簡稱 Protobuf)是 Google 公司內(nèi)部的混合語言數(shù)據(jù)標(biāo)準(zhǔn),目前已經(jīng)正在使用的有超過 48,162 種報(bào)文格式定義和超過 12,183 個(gè) .proto 文件。他們常用于 RPC 系統(tǒng)和持續(xù)數(shù)據(jù)存儲(chǔ)系統(tǒng)等應(yīng)用場景。

實(shí)際上:Protocol Buffers(簡稱 Protobuf)是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式,可以用于結(jié)構(gòu)化數(shù)據(jù)串行化,或者說序列化。它很適合做數(shù)據(jù)存儲(chǔ)或 RPC 數(shù)據(jù)交換格式。可用于通訊協(xié)議、數(shù)據(jù)存儲(chǔ)等領(lǐng)域的語言無關(guān)、平臺(tái)無關(guān)、可擴(kuò)展的序列化結(jié)構(gòu)數(shù)據(jù)格式。

目前:Protobuf官方工程主頁上顯示的已支持的開發(fā)語言多達(dá)10種,分別有:C++、Java、Python、Objective-C、C#、Ruby、Go、PHP、Dart、Javascript,基本上主流的語言都已支持(具體詳見Protobuf工程主頁:https://github.com/protocolbuffers/protobuf)。

Protobuf已支持的開發(fā)語言如下圖:

PS:Protobuf的官網(wǎng)上有很多入門資料,有興趣一定要看看:https://developers.google.com/protocol-buffers(如果不能直接訪問,你懂的。。。)

寫到這里:或許您和我一樣,在第一次看完這些介紹后還是不明白 Protobuf 究竟是什么,那么我想一個(gè)簡單的例子應(yīng)該比較有助于理解它(請(qǐng)繼續(xù)往下閱讀)。

4、一個(gè)簡單的例子

4.1 安裝Protobuf

在網(wǎng)站?https://developers.google.com/protocol-buffers?上可以下載 Protobuf 的源代碼。然后解壓編譯安裝便可以使用它了。

安裝步驟如下所示:

tar-xzf protobuf-2.1.0.tar.gz

cdprotobuf-2.1.0

./configure--prefix=$INSTALL_DIR

make

makecheck

makeinstall

4.2 關(guān)于簡單例子的描述

我打算使用 Protobuf 和 C++ 開發(fā)一個(gè)十分簡單的例子程序。該程序由兩部分組成。第一部分被稱為 Writer,第二部分叫做 Reader。Writer 負(fù)責(zé)將一些結(jié)構(gòu)化的數(shù)據(jù)寫入一個(gè)磁盤文件,Reader 則負(fù)責(zé)從該磁盤文件中讀取結(jié)構(gòu)化數(shù)據(jù)并打印到屏幕上。

準(zhǔn)備用于演示的結(jié)構(gòu)化數(shù)據(jù)是 HelloWorld,它包含兩個(gè)基本數(shù)據(jù):

1)ID:為一個(gè)整數(shù)類型的數(shù)據(jù);

2)Str:這是一個(gè)字符串。

4.3 書寫 .proto 文件

首先我們需要編寫一個(gè) proto 文件,定義我們程序中需要處理的結(jié)構(gòu)化數(shù)據(jù),在 protobuf 的術(shù)語中,結(jié)構(gòu)化數(shù)據(jù)被稱為 Message。proto 文件非常類似 java 或者 C 語言的數(shù)據(jù)定義。代碼清單 1 顯示了例子應(yīng)用中的 proto 文件內(nèi)容。

清單 1. proto 文件:

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è)可選的成員,即消息中可以不包含該成員。

4.4 編譯 .proto 文件

寫好 proto 文件之后就可以用 Protobuf 編譯器將該文件編譯成目標(biāo)語言了。本例中我們將使用 C++。

假設(shè)您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一個(gè)目錄下,則可以使用如下命令:

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

命令將生成兩個(gè)文件:

1)lm.helloworld.pb.h:定義了 C++ 類的頭文件;

2)lm.helloworld.pb.cc: C++ 類的實(shí)現(xiàn)文件。

在生成的頭文件中,定義了一個(gè) C++ 類 helloworld,后面的 Writer 和 Reader 將使用這個(gè)類來對(duì)消息進(jìn)行操作。諸如對(duì)消息的成員進(jìn)行賦值,將消息序列化等等都有相應(yīng)的方法。

4.5 編寫 writer 和 Reader

如前所述,Writer 將把一個(gè)結(jié)構(gòu)化數(shù)據(jù)寫入磁盤,以便其他人來讀取。假如我們不使用 Protobuf,其實(shí)也有許多的選擇。一個(gè)可能的方法是將數(shù)據(jù)轉(zhuǎn)換為字符串,然后將字符串寫入磁盤。轉(zhuǎn)換為字符串的方法可以使用?sprintf(),這非常簡單。數(shù)字 123 可以變成字符串“123”。

這樣做似乎沒有什么不妥,但是仔細(xì)考慮一下就會(huì)發(fā)現(xiàn),這樣的做法對(duì)寫 Reader 的那個(gè)人的要求比較高,Reader 的作者必須了 Writer 的細(xì)節(jié)。比如“123”可以是單個(gè)數(shù)字 123,但也可以是三個(gè)數(shù)字 1、2 和 3,等等。這么說來,我們還必須讓 Writer 定義一種分隔符一樣的字符,以便 Reader 可以正確讀取。但分隔符也許還會(huì)引起其他的什么問題。最后我們發(fā)現(xiàn)一個(gè)簡單的 Helloworld 也需要寫許多處理消息格式的代碼。

如果使用 Protobuf,那么這些細(xì)節(jié)就可以不需要應(yīng)用程序來考慮了。

使用 Protobuf,Writer 的工作很簡單,需要處理的結(jié)構(gòu)化數(shù)據(jù)由?.proto?文件描述,經(jīng)過上一節(jié)中的編譯過程后,該數(shù)據(jù)化結(jié)構(gòu)對(duì)應(yīng)了一個(gè) C++ 的類,并定義在?lm.helloworld.pb.h?中。對(duì)于本例,類名為?lm::helloworld。Writer 需要 include 該頭文件,然后便可以使用這個(gè)類了。

現(xiàn)在,在 Writer 代碼中,將要存入磁盤的結(jié)構(gòu)化數(shù)據(jù)由一個(gè)?lm::helloworld?類的對(duì)象表示,它提供了一系列的 get/set 函數(shù)用來修改和讀取結(jié)構(gòu)化數(shù)據(jù)中的數(shù)據(jù)成員,或者叫 field。當(dāng)我們需要將該結(jié)構(gòu)化數(shù)據(jù)保存到磁盤上時(shí),類?lm::helloworld?已經(jīng)提供相應(yīng)的方法來把一個(gè)復(fù)雜的數(shù)據(jù)變成一個(gè)字節(jié)序列,我們可以將這個(gè)字節(jié)序列寫入磁盤。

對(duì)于想要讀取這個(gè)數(shù)據(jù)的程序來說,也只需要使用類?lm::helloworld?的相應(yīng)反序列化方法來將這個(gè)字節(jié)序列重新轉(zhuǎn)換會(huì)結(jié)構(gòu)化數(shù)據(jù)。這同我們開始時(shí)那個(gè)“123”的想法類似,不過 Protobuf 想的遠(yuǎn)遠(yuǎn)比我們那個(gè)粗糙的字符串轉(zhuǎn)換要全面,因此,我們不如放心將這類事情交給 Protobuf 吧。

程序清單 2 演示了 Writer 的主要代碼,您一定會(huì)覺得很簡單吧?

清單 2. Writer 的主要代碼:

#include "lm.helloworld.pb.h"

?

?intmain(void)

?{

?

??lm::helloworld msg1;

??msg1.set_id(101);

??msg1.set_str(“hello”);

?

??// Write the new address book back to disk.

??fstream output("./log", ios::out | ios::trunc | ios::binary);

?

??if(!msg1.SerializeToOstream(&output)) {

??????cerr << "Failed to write msg."<< endl;

??????return-1;

??}????????

??return0;

?}

Msg1 是一個(gè) helloworld 類的對(duì)象,set_id() 用來設(shè)置 id 的值。SerializeToOstream 將對(duì)象序列化后寫入一個(gè) fstream 流。

代碼清單 3 列出了 reader 的主要代碼。

清單 3. Reader:

#include "lm.helloworld.pb.h"

?voidListMsg(constlm::helloworld & msg) {

??cout << msg.id() << endl;

??cout << msg.str() << endl;

?}

?

?intmain(intargc, char* argv[]) {

?

??lm::helloworld msg1;

?

??{

????fstream input("./log", ios::in | ios::binary);

????if(!msg1.ParseFromIstream(&input)) {

??????cerr << "Failed to parse address book."<< endl;

??????return-1;

????}

??}

?

??ListMsg(msg1);

??…

?}

同樣,Reader 聲明類 helloworld 的對(duì)象 msg1,然后利用 ParseFromIstream 從一個(gè) fstream 流中讀取信息并反序列化。此后,ListMsg 中采用 get 方法讀取消息的內(nèi)部信息,并進(jìn)行打印輸出操作。

4.6 運(yùn)行結(jié)果

運(yùn)行 Writer 和 Reader 的結(jié)果如下:

>writer

>reader

101

Hello

Reader 讀取文件 log 中的序列化信息并打印到屏幕上。本文中所有的例子代碼都可以在附件中下載。您可以親身體驗(yàn)一下。

這個(gè)例子本身并無意義,但只要您稍加修改就可以將它變成更加有用的程序。比如將磁盤替換為網(wǎng)絡(luò) socket,那么就可以實(shí)現(xiàn)基于網(wǎng)絡(luò)的數(shù)據(jù)交換任務(wù)。而存儲(chǔ)和交換正是 Protobuf 最有效的應(yīng)用領(lǐng)域。

5、和其他類似技術(shù)的比較

5.1 概述

看完這個(gè)簡單的例子之后,希望您已經(jīng)能理解 Protobuf 能做什么了,那么您可能會(huì)說,世上還有很多其他的類似技術(shù)啊,比如 XML,JSON,Thrift 等等。和他們相比,Protobuf 有什么不同呢?

簡單說來 Protobuf 的主要優(yōu)點(diǎn)就是:簡單,快。這有測試為證,項(xiàng)目 thrift-protobuf-compare 比較了這些類似的技術(shù),下圖顯示了該項(xiàng)目的一項(xiàng)測試結(jié)果——Total Time。

性能測試結(jié)果:

Total Time 指一個(gè)對(duì)象操作的整個(gè)時(shí)間,包括創(chuàng)建對(duì)象,將對(duì)象序列化為內(nèi)存中的字節(jié)序列,然后再反序列化的整個(gè)過程。從測試結(jié)果可以看到 Protobuf 的成績很好,感興趣的讀者可以自行到網(wǎng)站?https://github.com/eishay/jvm-serializers/wiki上了解更詳細(xì)的測試結(jié)果。

5.2 Protobuf 的優(yōu)點(diǎn)

Protobuf 有如 XML,不過它更小、更快、也更簡單。你可以定義自己的數(shù)據(jù)結(jié)構(gòu),然后使用代碼生成器生成的代碼來讀寫這個(gè)數(shù)據(jù)結(jié)構(gòu)。你甚至可以在無需重新部署程序的情況下更新數(shù)據(jù)結(jié)構(gòu)。只需使用 Protobuf 對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行一次描述,即可利用各種不同語言或從各種不同數(shù)據(jù)流中對(duì)你的結(jié)構(gòu)化數(shù)據(jù)輕松讀寫。

它有一個(gè)非常棒的特性,即“向后”兼容性好,人們不必破壞已部署的、依靠“老”數(shù)據(jù)格式的程序就可以對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行升級(jí)。這樣您的程序就可以不必?fù)?dān)心因?yàn)橄⒔Y(jié)構(gòu)的改變而造成的大規(guī)模的代碼重構(gòu)或者遷移的問題。因?yàn)樘砑有碌南⒅械?field 并不會(huì)引起已經(jīng)發(fā)布的程序的任何改變。

Protobuf 語義更清晰,無需類似 XML 解析器的東西(因?yàn)?Protobuf 編譯器會(huì)將 .proto 文件編譯生成對(duì)應(yīng)的數(shù)據(jù)訪問類以對(duì) Protobuf 數(shù)據(jù)進(jìn)行序列化、反序列化操作)。

使用 Protobuf 無需學(xué)習(xí)復(fù)雜的文檔對(duì)象模型,Protobuf 的編程模式比較友好,簡單易學(xué),同時(shí)它擁有良好的文檔和示例,對(duì)于喜歡簡單事物的人們而言,Protobuf 比其他的技術(shù)更加有吸引力。

5.3 Protobuf 的不足

Protbuf 與 XML 相比也有不足之處。它功能簡單,無法用來表示復(fù)雜的概念。

XML 已經(jīng)成為多種行業(yè)標(biāo)準(zhǔn)的編寫工具,Protobuf 只是 Google 公司內(nèi)部使用的工具,在通用性上還差很多。

由于文本并不適合用來描述數(shù)據(jù)結(jié)構(gòu),所以 Protobuf 也不適合用來對(duì)基于文本的標(biāo)記文檔(如 HTML)建模。另外,由于 XML 具有某種程度上的自解釋性,它可以被人直接讀取編輯,在這一點(diǎn)上 Protobuf 不行,它以二進(jìn)制的方式存儲(chǔ),除非你有 .proto 定義,否則你沒法直接讀出 Protobuf 的任何內(nèi)容。

6、Protobuf 的更多細(xì)節(jié)

6.1 概述

人們一直在強(qiáng)調(diào),同 XML 相比, Protobuf 的主要優(yōu)點(diǎn)在于性能高。它以高效的二進(jìn)制方式存儲(chǔ),比 XML 小 3 到 10 倍,快 20 到 100 倍。對(duì)于這些 “小 3 到 10 倍”,“快 20 到 100 倍”的說法,嚴(yán)肅的程序員需要一個(gè)解釋。因此在本文的最后,讓我們稍微深入 Protobuf 的內(nèi)部實(shí)現(xiàn)吧。

有兩項(xiàng)技術(shù)保證了采用 Protobuf 的程序能獲得相對(duì)于 XML 極大的性能提高。

第一項(xiàng):我們可以考察 Protobuf 序列化后的信息內(nèi)容。您可以看到 Protocol Buffer 信息的表示非常緊湊,這意味著消息的體積減少,自然需要更少的資源。比如網(wǎng)絡(luò)上傳輸?shù)淖止?jié)數(shù)更少,需要的 IO 更少等,從而提高性能。

第二項(xiàng):我們需要理解 Protobuf 封解包的大致過程,從而理解為什么會(huì)比 XML 快很多。

6.2 Protobuf的Encoding

Protobuf 序列化后所生成的二進(jìn)制消息非常緊湊,這得益于 Protobuf 采用的非常巧妙的 Encoding 方法。

考察消息結(jié)構(gòu)之前,讓我首先要介紹一個(gè)叫做 Varint 的術(shù)語。Varint 是一種緊湊的表示數(shù)字的方法。它用一個(gè)或多個(gè)字節(jié)來表示一個(gè)數(shù)字,值越小的數(shù)字使用越少的字節(jié)數(shù)。這能減少用來表示數(shù)字的字節(jié)數(shù)。

比如對(duì)于 int32 類型的數(shù)字,一般需要 4 個(gè) byte 來表示。但是采用 Varint,對(duì)于很小的 int32 類型的數(shù)字,則可以用 1 個(gè) byte 來表示。當(dāng)然凡事都有好的也有不好的一面,采用 Varint 表示法,大的數(shù)字則需要 5 個(gè) byte 來表示。從統(tǒng)計(jì)的角度來說,一般不會(huì)所有的消息中的數(shù)字都是大數(shù),因此大多數(shù)情況下,采用 Varint 后,可以用更少的字節(jié)數(shù)來表示數(shù)字信息。下面就詳細(xì)介紹一下 Varint。

Varint 中的每個(gè) byte 的最高位 bit 有特殊的含義,如果該位為 1,表示后續(xù)的 byte 也是該數(shù)字的一部分,如果該位為 0,則結(jié)束。其他的 7 個(gè) bit 都用來表示數(shù)字。因此小于 128 的數(shù)字都可以用一個(gè) byte 表示。大于 128 的數(shù)字,比如 300,會(huì)用兩個(gè)字節(jié)來表示:1010 1100 0000 0010。

下圖演示了Protobuf如何解析兩個(gè) bytes。注意到最終計(jì)算前將兩個(gè) byte 的位置相互交換過一次,這是因?yàn)镻rotobuf字節(jié)序采用?little-endian(即小端字節(jié)序,詳見:《面試必考,史上最通俗大小端字節(jié)序詳解》) 的方式。

Varint 編碼:

消息經(jīng)過序列化后會(huì)成為一個(gè)二進(jìn)制數(shù)據(jù)流,該流中的數(shù)據(jù)為一系列的 Key-Value 對(duì)。如下圖所示。

Message Buffer:

采用這種 Key-Pair 結(jié)構(gòu)無需使用分隔符來分割不同的 Field。對(duì)于可選的 Field,如果消息中不存在該 field,那么在最終的 Message Buffer 中就沒有該 field,這些特性都有助于節(jié)約消息本身的大小。

以代碼清單 1 中的消息為例。假設(shè)我們生成如下的一個(gè)消息 Test1:

Test1.id = 10;

Test1.str = “hello”;

則最終的 Message Buffer 中有兩個(gè) Key-Value 對(duì),一個(gè)對(duì)應(yīng)消息中的 id;另一個(gè)對(duì)應(yīng) str。

Key 用來標(biāo)識(shí)具體的 field,在解包的時(shí)候,Protocol Buffer 根據(jù) Key 就可以知道相應(yīng)的 Value 應(yīng)該對(duì)應(yīng)于消息中的哪一個(gè) field。

Key 的定義如下:

(field_number << 3) | wire_type

可以看到 Key 由兩部分組成。第一部分是 field_number,比如消息 lm.helloworld 中 field id 的 field_number 為 1。第二部分為 wire_type。表示 Value 的傳輸類型。

Wire Type 可能的類型如下表所示:

在我們的例子當(dāng)中,field id 所采用的數(shù)據(jù)類型為 int32,因此對(duì)應(yīng)的 wire type 為 0。細(xì)心的讀者或許會(huì)看到在 Type 0 所能表示的數(shù)據(jù)類型中有 int32 和 sint32 這兩個(gè)非常類似的數(shù)據(jù)類型。Google Protocol Buffer 區(qū)別它們的主要意圖也是為了減少 encoding 后的字節(jié)數(shù)。

在計(jì)算機(jī)內(nèi),一個(gè)負(fù)數(shù)一般會(huì)被表示為一個(gè)很大的整數(shù),因?yàn)橛?jì)算機(jī)定義負(fù)數(shù)的符號(hào)位為數(shù)字的最高位。如果采用 Varint 表示一個(gè)負(fù)數(shù),那么一定需要 5 個(gè) byte。為此 Google Protocol Buffer 定義了 sint32 這種類型,采用 zigzag 編碼。

Zigzag 編碼用無符號(hào)數(shù)來表示有符號(hào)數(shù)字,正數(shù)和負(fù)數(shù)交錯(cuò),這就是 zigzag 這個(gè)詞的含義了。

ZigZag 編碼:

使用 zigzag 編碼,絕對(duì)值小的數(shù)字,無論正負(fù)都可以采用較少的 byte 來表示,充分利用了 Varint 這種技術(shù)。

其他的數(shù)據(jù)類型,比如字符串等則采用類似數(shù)據(jù)庫中的 varchar 的表示方法,即用一個(gè) varint 表示長度,然后將其余部分緊跟在這個(gè)長度部分之后即可。

通過以上對(duì) protobuf Encoding 方法的介紹,想必您也已經(jīng)發(fā)現(xiàn) protobuf 消息的內(nèi)容小,適于網(wǎng)絡(luò)傳輸。假如您對(duì)那些有關(guān)技術(shù)細(xì)節(jié)的描述缺乏耐心和興趣,那么下面這個(gè)簡單而直觀的比較應(yīng)該能給您更加深刻的印象。

對(duì)于代碼清單 1 中的消息,用 Protobuf 序列化后的字節(jié)序列為:

08 65 12 06 48 65 6C 6C 6F 77

而如果用 XML,則類似這樣:

31 30 31 3C 2F 69 64 3E 3C 6E 61 6D 65 3E 68 65

6C 6C 6F 3C 2F 6E 61 6D 65 3E 3C 2F 68 65 6C 6C

6F 77 6F 72 6C 64 3E

一共 55 個(gè)字節(jié),這些奇怪的數(shù)字需要稍微解釋一下,其含義用 ASCII 表示如下:

<helloworld>

???<id>101</id>

???<name>hello</name>

</helloworld>

6.3 封解包的速度

首先我們來了解一下 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,它只需要簡單地將一個(gè)二進(jìn)制序列,按照指定的格式讀取到 C++ 對(duì)應(yīng)的結(jié)構(gòu)類型中就可以了。從上一節(jié)的描述可以看到消息的 decoding 過程也可以通過幾個(gè)位移操作組成的表達(dá)式計(jì)算即可完成。速度非常快。

為了說明這并不是我拍腦袋隨意想出來的說法,下面讓我們簡單分析一下 Protobuf 解包的代碼流程吧。

以代碼清單 3 中的 Reader 為例,該程序首先調(diào)用 msg1 的 ParseFromIstream 方法,這個(gè)方法解析從文件讀入的二進(jìn)制數(shù)據(jù)流,并將解析出來的數(shù)據(jù)賦予 helloworld 類的相應(yīng)數(shù)據(jù)成員。

該過程可以用下圖表示解包流程圖:

整個(gè)解析過程需要 Protobuf 本身的框架代碼和由 Protobuf 編譯器生成的代碼共同完成。Protobuf 提供了基類 Message 以及 Message_lite 作為通用的 Framework,CodedInputStream 類,WireFormatLite 類等提供了對(duì)二進(jìn)制數(shù)據(jù)的 decode 功能。

Protobuf 的解碼可以通過幾個(gè)簡單的數(shù)學(xué)運(yùn)算完成,無需復(fù)雜的詞法語法分析,因此 ReadTag() 等方法都非???。 在這個(gè)調(diào)用路徑上的其他類和方法都非常簡單,感興趣的讀者可以自行閱讀。

相對(duì)于 XML 的解析過程,以上的流程圖實(shí)在是非常簡單吧?這也就是 Protobuf 效率高的第二個(gè)原因了。

7、寫在最后

往往了解越多,人們就會(huì)越覺得自己無知。我惶恐地發(fā)現(xiàn)自己竟然寫了一篇關(guān)于序列化的文章,文中必然有許多想當(dāng)然而自以為是的東西,還希望各位能夠去偽存真,更希望真的高手能不吝賜教。

另外,如果您覺得理論還不夠,以下幾篇較完整的IM編碼實(shí)操都使用了Protobuf,可以一并學(xué)習(xí):

1.?一個(gè)基于Protocol Buffer的Java代碼演示

2.?跟著源碼學(xué)IM(二):自已開發(fā)IM很難?手把手教你擼一個(gè)Andriod版IM

3.?跟著源碼學(xué)IM(四):拿起鍵盤就是干,教你徒手開發(fā)一套分布式IM系統(tǒng)

4.?跟著源碼學(xué)IM(十):基于Netty,搭建高性能IM集群(含技術(shù)思路+源碼)

8、參考資料

[1]?Protobuf官方網(wǎng)站

[2]?Protobuf通信協(xié)議詳解:代碼演示、詳細(xì)原理介紹等

[3]?如何選擇即時(shí)通訊應(yīng)用的數(shù)據(jù)傳輸格式

[4]?強(qiáng)列建議將Protobuf作為你的即時(shí)通訊應(yīng)用數(shù)據(jù)傳輸格式

[5]?APP與后臺(tái)通信數(shù)據(jù)格式的演進(jìn):從文本協(xié)議到二進(jìn)制協(xié)議

[6]?面試必考,史上最通俗大小端字節(jié)序詳解

[7]?移動(dòng)端IM開發(fā)需要面對(duì)的技術(shù)問題(含通信協(xié)議選擇)

[8]?簡述移動(dòng)端IM開發(fā)的那些坑:架構(gòu)設(shè)計(jì)、通信協(xié)議和客戶端

[9]?理論聯(lián)系實(shí)際:一套典型的IM通信協(xié)議設(shè)計(jì)詳解

[10]?58到家實(shí)時(shí)消息系統(tǒng)的協(xié)議設(shè)計(jì)等技術(shù)實(shí)踐分享

(本文已同步發(fā)布于:http://www.52im.net/thread-4080-1-1.html)

IM通訊協(xié)議專題學(xué)習(xí)(一):Protobuf從入門到精通,一篇就夠!的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
沁阳市| 介休市| 微博| 石渠县| 河西区| 喀喇| 五华县| 武义县| 威信县| 都昌县| 临安市| 聂荣县| 红桥区| 三门峡市| 东乌| 遂川县| 遂溪县| 自治县| 常德市| 长兴县| 全椒县| 柳江县| 平和县| 房产| 隆安县| 囊谦县| 白玉县| 道孚县| 武威市| 洛隆县| 沙湾县| 河西区| 许昌县| 高陵县| 海淀区| 姜堰市| 塔河县| 泽州县| 中江县| 阳原县| 内乡县|