簡要介紹和總結protobuf的一些關鍵點,從我之前做的ppt裏摘錄而成,希望能節省protobuf初學者的入門時間。這是一個簡單的Demo。
Protobuf 簡介
Protobuf全稱Google Protocol Buffers
- http://code.google.com/p/protobuf
- 結構化數據存儲格式(xml, json)
- 用於通信協議、數據存儲等
- 高效的序列化和反序列化
- 語言無關、平臺無關、擴展性好
- 官方支持C++, Java, Python三種語言
.proto文件
定義和使用
消息定義文件user_def.proto
package user; message UserInfo { required int64 id = 1; optional string name = 2; repeated bytes nick_name = 3; }
編譯.proto,生成解析器代碼
protoc --cpp_out . user.proto // user_def.pb.h user_def.pb.cc protoc --java_out . user.proto // user/UserInfo.java
字段ID
optional string name = 2
;
- 唯一性
- 序列化後,1~15佔一個字節,16~2047佔兩個字節
字段類型
- https://developers.google.com/protocol-buffers/docs/proto#scalar
-
string vs. bytes
.proto類型 c++類型 java類型 說明 string std::string String 必須是UTF-8或ASCII文本 bytes std::string ByteString 任意的字節序列
編寫建議
- 常用消息字段(尤其是repeated字段)的ID儘量分配在1~15之間。
- 儘可能多的(全部)使用optional字段。
- 命名方式
- .proto文件名用underscore_speparated_names。
- 消息名用CamelCaseNames。
- 字段名用underscore_separated_names。
兼容性建議
- 不能修改字段的ID。
- 不能增刪任何required字段。
- https://developers.google.com/protocol-buffers/docs/proto#updating
序列化後的protobuf消息
- 一序列的鍵值對,鍵是消息字段的ID。
- 已知消息字段(.proto文件定義)按其ID順序排列。
- 未知消息字段:
- c++和java: 排在已知字段之後且順序不定。
- python: 不保留未知字段。
- 不包含未賦值的optional消息字段。
- 使用little-endian字節序存儲。
反射
反射是protobuf的一個重要特性,涉及到的類主要有:
根據名稱創建消息
以下是一個根據消息名(包含package name)創建protobuf消息的C++函數,需要注意的是返回的消息必須在用完後delete掉。
Message* createMessage(const string &typeName) { Message *message = NULL; // 查找message的descriptor const Descriptor *descriptor = DescriptorPool::generated_pool()->FindMessageTypeByName(typeName); if (descriptor) { // 創建default message(prototype) const Message *prototype = MessageFactory::generated_factory()->GetPrototype(descriptor); if (NULL != prototype) { // 創建一個可修改的message message = prototype->New(); } } return message; }
修改消息
根據消息的字段名稱修改其值。以上面的user.UserInfo爲例,下面將一個新的UserInfo消息的其id字段設爲100。
int main() { // 使用上面的函數創建一個新的UserInfo message Message *msg = createMessage("user.UserInfo"); if (NULL == msg) { // 創建失敗,可能是消息名錯誤,也可能是編譯後message解析器 // 沒有鏈接到主程序中。 return -1; } // 獲取message的descriptor const Descriptor* descriptor = msg->GetDescriptor(); // 獲取message的反射接口,可用於獲取和修改字段的值 const Reflection* reflection = msg->GetReflection(); // 根據字段名查找message的字段descriptor const FieldDescriptor* idField = descriptor->FindFieldByName("id"); // 將id設置爲100 if (NULL != idField) { reflection->SetInt64(msg, idField, 100); } // ... 其他操作 // 最後刪除message delete msg; return 0; }
從字符串或流中讀取消息
用createMessage創建一個空的消息後,最常見的使用場景是使用Message的ParseFromString或ParseFromIstream方法從字符串或流中讀取一個序列化後的message。
Message *msg = createMessage("user.UserInfo"); if (NULL != msg) { if (!msg->ParseFromString("... serialized message string ... ")) { // 解析失敗 ... } }
Protobuf優勢
- 擴展性好
- 前後兼容
- 引入(import)已定義的消息
- 嵌套消息
- 高效 https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking
- 適合處理大量小數據(單個Message不超過1M)
Protobuf劣勢
- 沒有內置的Set, Map等容器類型。
- 不適合處理單個Message超過1M的情景,詳見Large Data Sets。
進一步閱讀
- .proto指南 https://developers.google.com/protocol-buffers/docs/proto
- .proto規範 https://developers.google.com/protocol-buffers/docs/style
- 序列化編碼方式 https://developers.google.com/protocol-buffers/docs/encoding
- 教程 https://developers.google.com/protocol-buffers/docs/tutorials
- 接口文檔 https://developers.google.com/protocol-buffers/docs/reference/overview
- Protobuf benchmarking https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking