周日本來要去爬山的蜜宪,但是沒去成,突然想寫點東西圃验,但本人文采不好掉伏,只能閑扯一點技術(shù)方面的文章,整理了下有道筆記澳窑,然后最近一直在開發(fā)protobuf的協(xié)議接口斧散,就寫寫ProtoBuf相關(guān)的東西吧。
本文精髓:
? ?protobuf的消息設計
? ?消息分發(fā)設計Message Dispatch
? ?針對程序升級的proto設計
文章末尾對著三點做詳細說明摊聋。
?????最近一段時間在寫linux服務端接口程序鸡捐,剛開始如果按照需求的話,大概有十個接口左右麻裁,但是后面慢慢分解需求后,其實真正就只有五個接口煎源。
????編碼工作早早完成,進入到測試階段脚草,一般情況可能會等web實現(xiàn)完成后原献,然后借助web client,在做調(diào)試姑隅,但是這期間工作效率不高,而且存在很多問題慕趴,可能后臺接口沒實現(xiàn)好,也有可能web沒實現(xiàn)好冕房。作為linux后臺服務開發(fā),需要會模擬客戶單發(fā)送數(shù)據(jù)耙册,如果接口很簡單的話,可以直接使用telnet工具帝际。
????現(xiàn)在最為流行的后臺服務端通信的協(xié)議有:JSON饶辙、XML、ProtoBuf弃揽,當協(xié)議為這三種的時候,簡單的telnet就不能勝任了披粟,使用JSON和ProtoBuf的話咒锻,先借助工具做序列化工作,使用XML的話蒿辙,也要事先編寫好XML滨巴。
????為了更加高效的對后臺服務接口做好單元測試,也為了在web開發(fā)調(diào)試的時候恭取,提供穩(wěn)定的后臺服務接口,自己利用MFC寫了一個小的調(diào)試工具
參數(shù)設置里面輸入的是json串耗跛,因為這個有很多工具方便序列化攒发,如:https://www.bejson.com/jsoneditoronline/
????這個工具很簡單,從界面就三個輸入羔砾,IP、port姜凄、消息類型,主要工作就是模擬客戶端向服務端發(fā)送消息:
????????需要借用服務端的proto協(xié)議文件
????????序列化protobuf
????????根據(jù)消息類型做消息分發(fā)
????這里用到protobuf态秧,先大概說一下它的使用與原理
????1,介紹安裝
????????直接去百度空扎,這里就跳過
????2润讥,編寫 .proto文件
????????來個例子:
????????package lm;
????????message helloworld
????????{
????????required int32 ? ? id = 1; ?// ID
????????required string ? ?str = 2; ?// str
????????optional int32 ? ? opt = 3; ?//optional field
????????}
限定修飾符 + 數(shù)據(jù)類型 + 字段名稱 = 字段編碼值
盡量養(yǎng)成良好測編程習慣,對proto文件命名與消息名做規(guī)范的命名
????在上例中楚殿,package 名字叫做 lm,定義了一個消息 helloworld砌溺,該消息有三個成員变隔,類型為 int32 的 id规伐,另一個為類型為 string 的成員 str匣缘。opt 是一個可選的成員,即消息中可以不包含該成員培慌。
????3柑爸,編譯.proto文件
????寫好 proto 文件之后就可以用 Protobuf 編譯器將該文件編譯成目標語言了。本例中我們將使用 C++表鳍。假設您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一個目錄下进胯,則可以使用如下命令:
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
命令將生成兩個文件:
????????lm.helloworld.pb.h , 定義了 C++ 類的頭文件
????????lm.helloworld.pb.cc 偎血, C++ 類的實現(xiàn)文件
????在生成的頭文件中,定義了一個 C++ 類 helloworld
????4颇玷,序列化
????在第三部編譯生成的cc文件中,有一系列的SerializeToXXX方法谒亦,如SerializeToArray空郊,可以根據(jù)具體情況用這一系列方法進行序列化份招。
????5狞甚,反序列化
????在第3個步驟編譯生成的cc文件中,有一系列的ParseFromXXX方法谐腰,如ParseFromArray涩盾,可以根據(jù)具體情況用這一系列方法進行反序列化。
????掌握以上幾個步驟基本就能搞定proto的開發(fā)了春霍,進階的話可以去掌握proto的數(shù)據(jù)類型以及requried、optional终畅、repeated限定修飾符竟闪,和message的嵌套(message嵌套可以設計出更多復雜的協(xié)議,滿足更復雜的需求)妖爷。
????protobuf的優(yōu)劣自己去百度理朋,JSON,XML我也有用過嗽上,但是相對來說,谷歌的protobuf是我用起來最方便高效的兽愤。
這是網(wǎng)上的一個測試結(jié)果:http://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking
????回到文章開頭部分說的精髓,現(xiàn)在逐一到來
????一逐沙,proto的設計:
????一般設計規(guī)則如下
????message Request
????{
????required fixed64 msgtype = 1;
????required bytes bodys = 2;
????}
????一個消息類型加一個消息體,但這不能滿足復雜的業(yè)務需求棚赔,所以復雜的系統(tǒng)里面一般拆成這樣:
????message Header
????{
????required fixed64 msgtype = 1;
????}
????message HelloworldRequest
????{
????required int32 ? ? id = 1; ?// ID
????required string ? ?str = 2; ?// str
????optional int32 ? ? opt = 3; ?//optional field
????}
????一個消息類型(單獨定義一個文件)對應一個請求消息徘郭,一個請求消息對應一個接口,消息里面的字段對應接口所需要的參數(shù)残揉,這是常用的設計方法,能滿足所有的業(yè)務需求冲甘。
????二,消息分發(fā)的設計
這里的message dispatch是指程序根據(jù)不同的msgtype濒憋,反序列化proto和做不同的業(yè)務邏輯處理陶夜。
????古老而又傳統(tǒng)的設計是采用switch case來做(我曾經(jīng)看到有在用if else的),這種方法有很多不足之處条辟,我這里舉幾個很常見的:
????1,代碼臃腫本姥,隨著消息類型的增加,會有n多的case
????2婚惫,假設case里漏掉了break魂爪;那就不妙了。
????3滓侍,代碼維護差,每次增加msgtype捺球,除了實現(xiàn)對應的業(yè)務邏輯處理街图,還要到消息入口增加對應的case懒构。
在c語言里面,有一種很實用的辦法絮姆,那就是函數(shù)指針秩霍,很多開源的和上層應用的回調(diào)函數(shù)或方法的底層都是封裝了c語言的函數(shù)指針,這里提到函數(shù)指針铃绒,我簡單介紹一下(本文沒有用很大篇幅來說明函數(shù)指針,掌握其基本定義颠悬,慢慢學會衍生到復雜的概念):
????????從概念上說,函數(shù)指針是指向函數(shù)的指針變量诞外,它本質(zhì)上是一個指針變量灾票。
????????其廣泛的定義是: int (*f) (int x);
????????復制和調(diào)用: int func(int x); ? f = func;
????那么函數(shù)指針在Message Dispath 如何設計呢,還是來一個簡單的例子:
????定義一個結(jié)構(gòu)體
????????struct SMsgCmd {
????????int msgtype; ? ? ? ? ? ? ? ? ? ? ? ? ? ? /*msg type*/
????????int (*func)(const char *argv); ? ? ? ? ? /* handler * 函數(shù)指針/
????????};
????定義一個結(jié)構(gòu)體數(shù)組刊苍,并初始化
????????static struct SMsgCmd commands[] = {
????????{ 1, ? Request1},
????????{ 2, ? Request1},
????????};
????在服務程序的消息入口處,遍歷改數(shù)組即可
????????for ()
????????{
????????if (msgtype == commands[i].msgtype){
????????(*commands[i].func)(argv);
????????}
????????}
在c++11里面啥纸,可以利用std::function?和std::bind埠忘,其原理跟函數(shù)指針一個道理。
????三莹妒,針對程序升級的proto設計
????程序升級是常有的事绰上,但我們升級的時候需要考慮兼容性,之前有看到過同個版本號如
????if (version == 1){
????}
????else if (version > 2)
????.........
????這樣做的缺陷我就不在過多的說明了鉴腻。
????對于proto的協(xié)議來說迷扇,我們只要做到以下幾點爽哎,就會完美兼容新舊版本
1,不要隨意添加或刪除 required限定詞修飾的字段
????????2厨内,不要隨意改變現(xiàn)有字段編碼值
????????3渺贤,若需要新增字段,請用optional限定詞修飾
本文到此就算結(jié)束了志鞍,若有錯誤之處,請多多指教固棚!
歡迎關(guān)注本人微信公眾號:lzyTalk江湖,不只是談江湖唆缴,還會分享很多技術(shù)干貨哦黍翎!