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