一、基礎知識
參考
Protocol Buffers 在游戲中的應用
Protobuf語言指南
android與PC,C#與Java 利用protobuf 進行無障礙通訊【Socket】
1.性能好/效率高
現(xiàn)在轴捎,俺就來說說Google公司為啥放著好端端的XML不用滤淳,非要另起爐灶月幌,重新造輪子昼弟。一個根本的原因是XML性能不夠好蕾殴。
- 先說時間開銷:XML格式化(序列化)的開銷倒還好彭羹;但是XML解析(反序列化)的開銷就不敢恭維啦黄伊。俺之前經(jīng)常碰到一些時間性能很敏感的場合,由于不堪忍受XML解析的速度派殷,棄之如敝履还最。
- 再來看空間開銷:熟悉XML語法的同學應該知道墓阀,XML格式為了有較好的可讀性,引入了一些冗余的文本信息拓轻。所以空間開銷也不是太好(不過這點缺點斯撮,俺不常碰到)。
由于Google公司賴以吹噓的就是它的海量數(shù)據(jù)和海量處理能力悦即。對于幾十萬吮成、上百萬機器的集群,動不動就是PB級的數(shù)據(jù)量辜梳,哪怕性能稍微提高0.1%也是相當可觀滴粱甫。所以Google自然無法容忍XML在性能上的明顯缺點。再加上Google從來就不缺造輪子的牛人作瞄,所以protobuf也就應運而生了茶宵。
Google對于性能的偏執(zhí),那可是出了名的宗挥。所以乌庶,俺對于Google搞出來protobuf是非常滴放心,性能上不敢說是最好契耿,但肯定不會太差瞒大。
2.代碼生成機制
除了性能好,代碼生成機制是主要吸引俺的地方搪桂。為了說明這個代碼生成機制透敌,俺舉個例子。
比如有個電子商務的系統(tǒng)(假設用C++實現(xiàn))踢械,其中的模塊A需要發(fā)送大量的訂單信息給模塊B酗电,通訊的方式使用socket。
假設訂單包括如下屬性:
-----------------------
時間:time(用整數(shù)表示)
客戶id:userid(用整數(shù)表示)
交易金額:price(用浮點數(shù)表示)
交易的描述:desc(用字符串表示)
-----------------------
如果使用protobuf實現(xiàn)内列,首先要寫一個proto文件(不妨叫Order.proto)撵术,在該文件中添加一個名為"Order"的message結構,用來描述通訊協(xié)議中的結構化數(shù)據(jù)话瞧。該文件的內容大致如下:
message Order
{
required int32 time = 1;
required int32 userid = 2;
required float price = 3;
optional string desc = 4;
}
然后嫩与,使用protobuf內置的編譯器編譯該proto。由于本例子的模塊是C++交排,你可以通過protobuf編譯器的命令行參數(shù)蕴纳,指定它生成C++語言的“訂單包裝類”。(一般來說个粱,一個message結構會生成一個包裝類)
然后你使用類似下面的代碼來序列化/解析該訂單包裝類:
// 發(fā)送方
Order order;
order.set_time(XXXX);
order.set_userid(123);
order.set_price(100.0f);
order.set_desc("a test order");
string sOrder;
order.SerailzeToString(&sOrder);
// 然后調用某種socket的通訊庫把序列化之后的字符串發(fā)送出去
// ......
--------------------------------
// 接收方
string sOrder;
// 先通過網(wǎng)絡通訊庫接收到數(shù)據(jù)古毛,存放到某字符串sOrder
// ......
Order order;
if(order.ParseFromString(sOrder)) // 解析該字符串
{
cout << "userid:" << order.userid() << endl
<< "desc:" << order.desc() << endl;
}
else
{
cerr << "parse error!" << endl;
}
有了這種代碼生成機制,開發(fā)人員再也不用吭哧吭哧地編寫那些協(xié)議解析的代碼了(干這種活是典型的吃力不討好)。
萬一將來需求發(fā)生變更稻薇,要求給訂單再增加一個“狀態(tài)”的屬性嫂冻,那只需要在Order.proto文件中增加一行代碼。對于發(fā)送方(模塊A)塞椎,只要增加一行設置狀態(tài)的代碼桨仿;對于接收方(模塊B)只要增加一行讀取狀態(tài)的代碼。哇塞案狠,簡直太輕松了服傍!
另外,如果通訊雙方使用不同的編程語言來實現(xiàn)骂铁,使用這種機制可以有效確保兩邊的模塊對于協(xié)議的處理是一致的吹零。
順便跑題一下。從某種意義上講拉庵,可以把proto文件看成是描述通訊協(xié)議的規(guī)格說明書(或者叫接口規(guī)范)灿椅。這種伎倆其實老早就有了,搞過微軟的COM編程或者接觸過CORBA的同學钞支,應該都能從中看到IDL(詳細解釋看“這里”)的影子茫蛹。它們的思想是相通滴。
3.支持“向后兼容”和“向前兼容”
還是拿剛才的例子來說事兒烁挟。為了敘述方便婴洼,俺把增加了“狀態(tài)”屬性的訂單協(xié)議成為“新版本”;之前的叫“老版本”撼嗓。
所謂的“向后兼容”(backward compatible)窃蹋,就是說,當模塊B升級了之后静稻,它能夠正確識別模塊A發(fā)出的老版本的協(xié)議。由于老版本沒有“狀態(tài)”這個屬性匈辱,在擴充協(xié)議時振湾,可以考慮把“狀態(tài)”屬性設置成非必填的,或者給“狀態(tài)”屬性設置一個缺省值
所謂的“向前兼容”(forward compatible)亡脸,就是說押搪,當模塊A升級了之后,模塊B能夠正常識別模塊A發(fā)出的新版本的協(xié)議浅碾。這時候大州,新增加的“狀態(tài)”屬性會被忽略。
“向后兼容”和“向前兼容”有啥用捏垂谢?俺舉個例子:當你維護一個很龐大的分布式系統(tǒng)時厦画,由于你無法同時升級所有模塊,為了保證在升級過程中,整個系統(tǒng)能夠盡可能不受影響根暑,就需要盡量保證通訊協(xié)議的“向后兼容”或“向前兼容”力试。
4.支持多種編程語言
俺開博以來點評的幾個開源項目(比如“Sqlite”、“cURL”)排嫌,都是支持很多種編程語言滴畸裳,這次的protobuf也不例外。在Google官方發(fā)布的源代碼中包含了C++淳地、Java怖糊、Python三種語言(正好也是俺最常用的三種,真爽)颇象。如果你平時用的就是這三種語言之一伍伤,那就好辦了。
假如你想把protobuf用于其它語言夯到,咋辦捏嚷缭?由于Google一呼百應的號召力,開源社區(qū)對protobuf響應踴躍耍贾,近期冒出很多其它編程語言的版本(比如ActionScript阅爽、C#、Lisp荐开、Erlang付翁、Perl、PHP晃听、Ruby等)百侧,有些語言還同時搞出了多個開源的項目。具體細節(jié)可以參見“這里”能扒。
不過俺有義務提醒一下在座的各位同學佣渴。如果你考慮把protobuf用于上述這些語言,一定認真評估對應的開源庫初斑。因為這些開源庫不是Google官方提供的辛润、而且出來的時間還不長。所以见秤,它們的質量砂竖、性能等方面可能還有欠缺。
5.protobuf有啥缺陷鹃答?
應用不夠廣
由于protobuf剛公布沒多久乎澄,相比XML而言,protobuf還屬于初出茅廬测摔。因此置济,在知名度、應用廣度等方面都遠不如XML。由于這個原因舟肉,假如你設計的系統(tǒng)需要提供若干對外的接口給第三方系統(tǒng)調用修噪,俺奉勸你暫時不要考慮protobuf格式。二進制格式導致可讀性差
為了提高性能路媚,protobuf采用了二進制格式進行編碼黄琼。這直接導致了可讀性差的問題(嚴格地說,是沒有可讀性)整慎。雖然protobuf提供了TextFormat這個工具類(文檔在“這里”)脏款,但終究無法徹底解決此問題。
可讀性差的危害裤园,俺再來舉個例子撤师。比如通訊雙方如果出現(xiàn)問題,極易導致扯皮(都不承認自己有問題拧揽,都說是對方的錯)剃盾。俺對付扯皮的一個簡單方法就是直接抓包并dump成log,能比較容易地看出錯誤在哪一方淤袜。但是protobuf的二進制格式痒谴,導致你抓包并直接dump出來的log難以看懂。缺乏自描述
一般來說铡羡,XML是自描述的积蔚,而protobuf格式則不是。給你一段二進制格式的協(xié)議內容烦周,如果不配合相應的proto文件尽爆,那簡直就像天書一般。
由于“缺乏自描述”读慎,再加上“二進制格式導致可讀性差”漱贱。所以在配置文件方面,protobuf是肯定無法取代XML的地位滴夭委。
二幅狮、proto3 與 proto2
Protobuf 的 proto3 與 proto2 的區(qū)別
protobuf一些注意事項
protobuf v3測試
proto3語法
1.在第一行非空白非注釋行,必須寫:syntax = "proto3";
2.字段規(guī)則移除了 “required”闰靴,并把 “optional” 改名為 “singular”
3.默認值:
string類型默認值是空字符串,不是null
bytes類型默認是空bytes
bool類型默認值是false
數(shù)字類型默認值是0
枚舉類型默認值是第一個枚舉值,即0
repeated修飾的字段,默認值是空(在對應的編程語言中通常是一個空的list)
三、js中使用
1.注意現(xiàn)在有兩個版本,cocos論壇討論里钻注,推薦decodeio的protobufjs
參考
Node.js使用google-protobuf
cocos creator中使用protobuf(dcodeIO/protobuf.js 5.0)
這里網(wǎng)上查閱資料可能會讓人混亂蚂且,其實是因為在google官方的js庫出來之前,decodeio先推出了一個庫叫protobufjs幅恋。
隨著Google的Protobuf3的發(fā)布杏死,Google終于開發(fā)了一個可以給JavaScript使用的庫。之前大家如果在node端使用了Protobuf應該用的是protobufjs這個庫,但是既然Google官方支持了JavaScript淑翼,那么我們還是要去嘗試一下的腐巢。
主要存在兩個解決方案 使用protobufjs 或者 谷歌官方的js解析(通過protoc.exe生成.proto對應的js文件直接使用),個人認為protojs更為方便玄括,如果更改.proto文件都要使用protoc重新生成對應的js文件略為繁瑣冯丙。所以這里我們直接采用的protobufjs。
區(qū)別很簡單遭京,如果用protoc --js_out這種胃惜,就是官方的庫。如果直接加載proto文件去解析哪雕,或者pbjs導出的船殉,就是decodeio的庫了。
2.官方的https://github.com/protocolbuffers/protobuf/tree/master/js
- 安裝方式:
npm install google-protobuf
- 生成文件:
protoc ./test.proto --js_out=import_style=commonjs,binary:.
這里有兩種形式斯嚎,一種是common.js利虫,一種是closure(google style).common.js生成的js要使用 require命令導入,closuer.js生成的js要使用goo.provide命令來導入堡僻。 - 用例:
前端后臺以及游戲中使用Google Protocol Buffer詳解
解決方案:在Cocos Creator1.8中使用官方的google protobuf
3.decodeio的https://github.com/protobufjs/protobuf.js
- 安裝方式:
npm install protobufjs
- 生成文件:
pbjs -t static-module -w commonjs -o compiled.js file1.proto file2.proto
- 用例:cocos creator中使用protobuf(dcodeIO/protobuf.js 5.0)
- 注意事項:在 creator 與protobuf的那些事提到了版本問題:大佬們寫的教程基本都是 基于protobuf5 也是他們說的直接在項目下面 npm install protobufjs 但是npm下來的卻是protobuf最新的版本糠惫,所以你們想學習大佬的教程一定要npm install protobufjs@5 這樣才行,不然你就只能看著大佬的教程干瞪眼了苦始。
ProtoBuf.js同時支持NodeJS和Browser寞钥,也就是說,現(xiàn)在我們可以在JS client端使用protobuf陌选!當然理郑,前提是我們使用的是高級瀏覽器,因為ProtoBuf.js依賴于ByteBuffer.js(一個對ArrayBuffer進行了封裝的類庫)咨油,所以您炉,只能say byebye to IE < 10
。
由于 JavaScript 精度問題役电,所以 int64和 uint64等類型數(shù)據(jù)會被轉換成 Long.js 對象實例赚爵,Long 并不太復雜,與 bignumber.js 類似法瑟,具體參考 Long.js API.
4.cocos論壇里“奎特爾星球代言人”提供的基于protobufjs的插件pbkiller
當creator遇上protobufjs—起步
當creator遇上protobufjs—深入
當creator遇上protobufjs—效率
當creator遇上protobufjs—pbkiller插件
自從開始寫protobufjs的分享教程冀膝,就開始堅持不懈的在CocosCreator論壇上自吹自擂,無意見被CocosCreator制作人南塔斯大神看到了霎挟。一不小心收到南大神的論壇私信窝剖,詢問我可否將protobuf的使用制作成Creator的插件,并邀我將插件入駐Creator付費商店酥夭。收到消息的第一時間赐纱,我異常興奮脊奋。第一是我的經(jīng)驗分享竟能受到Creator官方大神的關注;其次是居然還可以入駐付費商店疙描,對于程序員來說莫大的欣慰就是可以將代碼變換現(xiàn)實中的價值诚隙。
通過一段時間的Creator插件學習與protobufjs源碼的理解,再結合Creator項目經(jīng)驗起胰,終于完成了第一版插件久又。在制作插件的過程中,插件的命名是最讓我糾結的待错,因為我在曾經(jīng)的項目中大量使用xxxHelper籽孙,編寫了不少輔助工具。這次為了讓我的第一個Creator插件看起來很牛逼一點點的感覺火俄,我腦子冒出killer的字樣犯建,隨后我就叫他:pbkiller。
5.推薦方案
項目中的引用的protobuf最開始是使用奎神的pbkiller瓜客。pbkiller是基于protobufjs5.x的适瓦。寫的過程中發(fā)現(xiàn)低版本protobufjs中對bytes,repeated,int64的使用太麻煩了谱仪。然后果斷放棄了pbkiller玻熙,使用了最新的protobufjs6.8.6.
protobufjs的github:https://github.com/dcodeIO/protobuf.js
可以通過npm install -g protobufjs命令去獲取。
也可以自己動手集成google protobuf疯攒。
https://github.com/google/protobuf6
參考:http://forum.cocos.com/t/cocoscreator-protobuf/61045或者http://www.reibang.com/p/f727f78dcc76嗦随。不再贅述。
demo中提供了bytes和repeats的使用方法供參考敬尺。
最新版本6.8.8的:Cocos Creator 中使用 protobufjs
6.其他問題
當creator遇上protobufjs—青春升級
protobufjs序列化后如何拼接上消息Id
leaf 和cocos creator 游戲實戰(zhàn)(一)使用protobuf完成通訊枚尼,這個例子使用的也是protobufjs。
四砂吞、WebSocket斷粘包
1.摘自前端后臺以及游戲中使用Google Protocol Buffer詳解
websocket也是基于tcp的署恍,相當于在tcp基礎上封裝了一層。 某種程度來說tcp的性能優(yōu)于websocket蜻直,因為websocket就是在tcp的基礎上多了一層轉化盯质,但是websocket使用更簡單,用tcp的app端需要自己去讀tcp流概而,根據(jù)包頭和包體組裝數(shù)據(jù)包呼巷,而websocket不需要,因為websocket會是一個整包的數(shù)據(jù)并不是流的形式赎瑰。 具體來說王悍,后端通過緩存區(qū)把數(shù)據(jù)沖刷(flush)給前端,app端拿到tcp數(shù)據(jù)流乡范,需要根據(jù)消息頭給定的消息體長度配名,去拿取后面多少位的數(shù)據(jù),然后組裝成一個數(shù)據(jù)包晋辆。 而websocket傳輸過來就是一個個的包渠脉,也就是幀并不是數(shù)據(jù)流,所以后端在給websocket數(shù)據(jù)的時候瓶佳,必須要把一個整包芋膘,在緩沖區(qū)一次性沖刷過來,而給tcp的話就可以自由沖刷霸饲。
也就是說如果服務端同樣采用的是websocket的話(Node.js及 ws庫)为朋,我們對消息是不需要添加數(shù)據(jù)頭進行數(shù)據(jù)包的組裝的。websocket是按照包一次性讀取的厚脉。既我們不需要在手動的定義數(shù)據(jù)包頭以及添加數(shù)據(jù)包長度信息习寸。