Google Protocol Buffers介紹和總結

簡要介紹和總結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佔兩個字節

字段類型

編寫建議

  1. 常用消息字段(尤其是repeated字段)的ID儘量分配在1~15之間。
  2. 儘可能多的(全部)使用optional字段。
  3. 命名方式
    • .proto文件名用underscore_speparated_names。
    • 消息名用CamelCaseNames。
    • 字段名用underscore_separated_names。

兼容性建議

  1. 不能修改字段的ID。
  2. 不能增刪任何required字段。
  3. 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優勢

  1. 擴展性好
    • 前後兼容
    • 引入(import)已定義的消息
    • 嵌套消息
  2. 高效 https://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking
    • 適合處理大量小數據(單個Message不超過1M)

Protobuf劣勢

  1. 沒有內置的Set, Map等容器類型。
  2. 不適合處理單個Message超過1M的情景,詳見Large Data Sets

進一步閱讀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章