PB-進階-2

keywords

  • protobuf
  • proto兼容問題

0. 引言

Protocol buffers(下文簡稱PB) 是一種靈活,高效,自動化機制的結構數據序列化方法-可類比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更為簡單。實際使用過程中,我們會遇到PB的proto文件新老協議兼容的問題,本文主要簡述新老proto兼容相關的問題以及使用注意事項,入門篇參考PB-入門-1

1. proto兼容

本文暫不深挖協議,主要介紹實際case,后續文章會深挖PB相關的協議。首先我們來看看本文測試使用到的proto文件定義

syntax = "proto2";
package test.tutorial;

message Student {
  optional uint64 id = 1;
  optional string name = 2;
  optional string email = 3;
}

message Student2 {
  optional uint64 id = 1;
  optional string name = 2;
  optional string email_2 = 3;
  optional uint64 ex = 4;
}

此外,介紹一個基本知識:proto文件生成的類都公開繼承自google::protobuf::Message,比如一些序列化和反序列化的方法

bool SerializeToString(string* output) const; //將消息序列化并儲存在指定的string中。注意里面的內容是二進制的,而不是文本;我們只是使用string作為一個很方便的容器。
bool ParseFromString(const string& data); //從給定的string解析消息。
bool SerializeToArray(void * data, int size) const  //將消息序列化至數組
bool ParseFromArray(const void * data, int size)    //從數組解析消息
bool SerializeToOstream(ostream* output) const; //將消息寫入到給定的C++ ostream中。
bool ParseFromIstream(istream* input); //從給定的C++ istream解析消息。

1.1. 老的序列化,新的反序列化

使用老的proto文件序列化得到二進制數據,使用新的proto文件反序列化,直接看測試代碼和結果

#include <iostream>
#include <string>
#include "build/Student.pb.h"

int main(int argc, char* argv[]) {
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  test::tutorial::Student Student;

  Student.set_id(1);
  *Student.mutable_name() = "kk";
  Student.set_email("kk@tencent.com");

  std::string serializedStr;
  Student.SerializeToString(&serializedStr);
  std::cout << std::endl << "before debugString:\r\n" << Student.DebugString();

  std::cout << "----------上面是序列化,下面是反序列化----------" << std::endl;
  test::tutorial::Student2 test;
  test.ParseFromString(serializedStr);
  std::cout << "after debugString:\r\n" << test.DebugString() << std::endl;

  google::protobuf::ShutdownProtobufLibrary();
}
before debugString:
id: 1
name: "kk"
email: "kk@tencent.com"
----------上面是序列化,下面是反序列化----------
after debugString:
id: 1
name: "kk"
email_2: "kk@tencent.com"

從上面看,反序列化后的數據正常(仔細看,發現沒有:第3個name是email_2,這里是因為PB協議,傳遞的知識type(按照編號和數據類型規則生成的,參考Protobuf通信協議詳解:代碼演示、詳細原理介紹等),名字對于協議來說,并不傳遞。

1.2. 新的序列化,老的反序列化

代碼如下:

#include <iostream>
#include <string>
#include "build/Student.pb.h"

int main(int argc, char* argv[]) {
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  test::tutorial::Student2 Student;

  Student.set_id(1);
  *Student.mutable_name() = "kk";
  Student.set_email_2("kk@tencent.com");
  Student.set_ex(2);

  std::string serializedStr;
  Student.SerializeToString(&serializedStr);
  std::cout << std::endl << "before debugString:\r\n" << Student.DebugString();

  std::cout << "----------上面是序列化,下面是反序列化----------" << std::endl;
  test::tutorial::Student test;
  test.ParseFromString(serializedStr);
  std::cout << "after debugString:\r\n" << test.DebugString() << std::endl;

  google::protobuf::ShutdownProtobufLibrary();
}

結果

before debugString:
id: 1
name: "kk"
email_2: "kk@tencent.com"
ex: 2
----------上面是序列化,下面是反序列化----------
after debugString:
id: 1
name: "kk"
email: "kk@tencent.com"
4: 2

注意反序列化的打印,有一個 “4:2”,這里是有問題的,因為test::tutorial::Student的空間,并沒有這么大,這里實際上已經內存越界訪問了,在很多情況下會出現預期之外的異常。

1.3. 在序列化后的數據上,多出一些數據

#include <iostream>
#include <string>
#include "build/Student.pb.h"

int main(int argc, char* argv[]) {
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  test::tutorial::Student Student;

  Student.set_id(1);
  *Student.mutable_name() = "kk";
  Student.set_email("kk@tencent.com");

  std::string serializedStr;
  Student.SerializeToString(&serializedStr);
  std::cout << std::endl << "before debugString:\r\n" << Student.DebugString();

  std::cout << "----------上面是序列化,下面是反序列化----------" << std::endl;
  test::tutorial::Student2 test;
  test.ParseFromString(serializedStr + "kk");
  std::cout << "after debugString:\r\n" << test.DebugString() << std::endl;

  google::protobuf::ShutdownProtobufLibrary();
}

結果

before debugString:
id: 1
name: "kk"
email: "kk@tencent.com"
----------上面是序列化,下面是反序列化----------
after debugString:
id: 1
name: "kk"
email_2: "kk@tencent.com"
13 {
  13 {
  }
}

這個跟上面case一樣,是有問題的,存在內存越界訪問

1.4. 序列化的數據丟失部分

#include <iostream>
#include <string>
#include "build/Student.pb.h"

int main(int argc, char* argv[]) {
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  test::tutorial::Student Student;

  Student.set_id(1);
  *Student.mutable_name() = "kk";
  Student.set_email("kk@tencent.com");

  std::string serializedStr;
  Student.SerializeToString(&serializedStr);
  std::cout << std::endl << "before debugString:\r\n" << Student.DebugString();

  std::cout << "----------上面是序列化,下面是反序列化----------" << std::endl;
  test::tutorial::Student2 test;
  test.ParseFromString(serializedStr.substr(0, serializedStr.size() - 3));
  std::cout << "after debugString:\r\n" << test.DebugString() << std::endl;

  google::protobuf::ShutdownProtobufLibrary();
}

結果

before debugString:
id: 1
name: "kk"
email: "kk@tencent.com"
----------上面是序列化,下面是反序列化----------
after debugString:
id: 1
name: "kk"
email_2: "kk@tencent.\000\000\000"

這里結果其實也不難理解,因為按照PB的編碼協議,內容的長度字段是正常的,內容確實部分,PB填充了0

對于丟失這個場景,丟失的數據不一樣結果也會不一樣。

2. 結論

PB協議的兼容是:

  • 新的proto可以兼容老的proto序列化
  • 新的proto序列化出來的二進制序列不能用老的proto解析
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容