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解析