最近在讀別人代碼的時候發現一個的東西,名字叫protobuf, 感覺挺好用的,寫在這里,留個記錄。那么什么是protobuf 呢?假如您在網上搜索,應該會得到類似這樣的文字介紹:
Google Protocol Buffer( 簡稱 Protobuf) 是 Google 公司內部的混合語言數據標准,目前已經正在使用的有超過 48,162 種報文格式定義和超過 12,183 個 .proto 文件。他們用於 RPC 系統和持續數據存儲系統。
Protocol Buffers 是一種輕便高效的結構化數據存儲格式,可以用於結構化數據串行化,或者說序列化。它很適合做數據存儲或 RPC 數據交換格式。可用於通訊協議、數據存儲等領域的語言無關、平台無關、可擴展的序列化結構數據格式。目前提供了 C++、Java、Python 三種語言的 API。
說的明白點,它其實是一個可以幫你生成自定義數據結構的代碼,並提供了序列化該結構的方法,並且支持多語言,跨平台等一系列優點。多說無益,直接說說該怎么用它。
首先 你需要編寫proto文件,在這個文件中 用簡單的方法定義了你想要定義的數據結構的組成信息。比如定義一個helloworld的信息,里面包含name, password, email 這三個信息。那么你的proto文件應該是這個樣子的:
package im; message helloworld { required string usrname = 1; required string passwd = 2; optional string email = 3; }
im 說明了包的名稱, helloworld 說明了 具體的結構類型。 一個比較好的習慣是認真對待 proto 文件的文件名。比如將命名規則定於如下:packageName.MessageName.proto
接下來 你需要運行一個命令來來生成該數據結構的代碼
假設您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一個目錄下,則可以使用如下命令:
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/im.helloworld.proto
那么這個時候你就會在DST_DIR下面看到im.helloworld.pb.h im.helloworld.pb.cpp
此時基本就大功告成了,你就可以在你的代碼里使用關於helloworld這個數據結構的一切了。
這個工具主要比較方便的地方是它給你提供的序列化工具。可以直接將一個數據結構序列化為字符、或者從字符反序列化為數據結構。你想,這就為很多的網絡程序提供了方便,在客戶機在向服務端傳遞消息的時候可以直接將需要傳遞的消息用一個數據結構封裝,然后用protobuf提供的序列化方法,序列化為一個字符串,然后你人為的加一個包頭(包含消息的長度和消息的類型)做一次encode;在服務端接收該消息的時候根據encode的方法對包進行接收,接收完之后再用protobuf提供的反序列化方法,從字符串再次還原為具體的數據結構信息。整個過程就不需要你去造輪子去做那些繁瑣的字符解析拼裝的工作了,是不是很爽? 這個我認為是protobuf比較有用的地方之一,另外一個比較有用的地方是,它可以根據類型信息去create對象,比如 之前我定義的helloworld結構,假如我現在在做一個服務端的程序,我想實現這樣的一個功能,在接收到helloworld消息的時候,我去執行相應的helloworld回調。當然對於helloworld的回調是在系統啟動的時候加載的。這樣有一個好處,實現整個系統的模塊化,並且耦合度也很低。沒有這個工具的話,我可能就需要在解析的時候特別的關注收到的消息類型字段,然后根據該字段去new一個相應的對象,進而調用相應的處理過程,整個過程都需要你去手寫,有了protobuf,你完全不需要造輪子了。
那么怎么樣根據消息的類型直接創建相應的對象呢?代碼如下:
google::protobuf::Message* create_message(const std::string& type_name) { google::protobuf::Message* message = NULL; const google::protobuf::Descriptor* descriptor = \ google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(type_name); if(descriptor) { const google::protobuf::Message* prototype = google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor); if(prototype) { message = prototype->New(); } } return message; }
有點設計模式里工廠模式的味道。在消息的種類不多的時候,你可以感覺不到它的好處,但在你的系統越來越龐大的時候,你自然能體會到這個小小函數的甜頭。
在你知道消息類型,並根據這個工具生成消息對象的時候,接下來就該處理了,也就是dispatch。上面我有提到過,你可以注冊每個消息類型的回調處理方法,通俗點講就是你的系統里需要有一個map,這個map里存儲了不同消息的處理方法,類似於:
std::map<std::string, message_callback_t> _message_callbacks; 這樣的一個容器。具體的方法如下:
void deal_message(google::protobuf::Message* msg) { auto iter_map = _message_callbacks.find(msg->GetTypeName()); //得到消息的具體類型,protobuf提供的內置方法 if(iter_map != _message_callbacks.end()) { iter_map->second(msg); //相應的回調處理 } else { std::cout << "message dealer no found" << std::endl; }
}
你看這樣是不是簡直帥呆了,你的系統如果用這樣的方法來執行消息分發處理,整個系統將會變得很清晰,模塊間耦合很低。
基於以上的思路,我簡單實現了一個client和server,來示例本博文的思路,源代碼地址:https://github.com/xiaopeifeng/CodeTricks/tree/master/protobuf
歡迎指正批評。