PB-進(jìn)階-2

keywords

  • protobuf
  • proto兼容問(wèn)題

0. 引言

Protocol buffers(下文簡(jiǎn)稱PB) 是一種靈活,高效,自動(dòng)化機(jī)制的結(jié)構(gòu)數(shù)據(jù)序列化方法-可類比 XML,但是比 XML 更?。? ~ 10倍)、更快(20 ~ 100倍)、更為簡(jiǎn)單。實(shí)際使用過(guò)程中,我們會(huì)遇到PB的proto文件新老協(xié)議兼容的問(wèn)題,本文主要簡(jiǎn)述新老proto兼容相關(guān)的問(wèn)題以及使用注意事項(xiàng),入門(mén)篇參考PB-入門(mén)-1

1. proto兼容

本文暫不深挖協(xié)議,主要介紹實(shí)際case,后續(xù)文章會(huì)深挖PB相關(guān)的協(xié)議。首先我們來(lái)看看本文測(cè)試使用到的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;
}

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

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

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

使用老的proto文件序列化得到二進(jìn)制數(shù)據(jù),使用新的proto文件反序列化,直接看測(cè)試代碼和結(jié)果

#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"

從上面看,反序列化后的數(shù)據(jù)正常(仔細(xì)看,發(fā)現(xiàn)沒(méi)有:第3個(gè)name是email_2,這里是因?yàn)镻B協(xié)議,傳遞的知識(shí)type(按照編號(hào)和數(shù)據(jù)類型規(guī)則生成的,參考Protobuf通信協(xié)議詳解:代碼演示、詳細(xì)原理介紹等),名字對(duì)于協(xié)議來(lái)說(shuō),并不傳遞。

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();
}

結(jié)果

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

注意反序列化的打印,有一個(gè) “4:2”,這里是有問(wèn)題的,因?yàn)閠est::tutorial::Student的空間,并沒(méi)有這么大,這里實(shí)際上已經(jīng)內(nèi)存越界訪問(wèn)了,在很多情況下會(huì)出現(xiàn)預(yù)期之外的異常。

1.3. 在序列化后的數(shù)據(jù)上,多出一些數(shù)據(jù)

#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();
}

結(jié)果

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

這個(gè)跟上面case一樣,是有問(wèn)題的,存在內(nèi)存越界訪問(wèn)

1.4. 序列化的數(shù)據(jù)丟失部分

#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();
}

結(jié)果

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

這里結(jié)果其實(shí)也不難理解,因?yàn)榘凑誔B的編碼協(xié)議,內(nèi)容的長(zhǎng)度字段是正常的,內(nèi)容確實(shí)部分,PB填充了0

對(duì)于丟失這個(gè)場(chǎng)景,丟失的數(shù)據(jù)不一樣結(jié)果也會(huì)不一樣。

2. 結(jié)論

PB協(xié)議的兼容是:

  • 新的proto可以兼容老的proto序列化
  • 新的proto序列化出來(lái)的二進(jìn)制序列不能用老的proto解析
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容