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