上一篇博文讲解了protobuf的安装和.proto文件的定义,并且可以生成C++版本的.cc和.h文件,python的.py文件。那么本文就利用生成的这些数据访问类,进行对象的序列化和反序列化。
上篇博文地址:【Protobuf】Protobuf下载安装和.proto文件定义
.proto文件定义
syntax = "proto2";
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2;
}
message Address {
optional string country = 1;
optional string detail = 2;
}
message Person {
required int32 id =1;
required string name = 2;
optional int32 age = 3;
repeated string email = 4;
repeated PhoneNumber phone = 5;
optional Address address = 6;
}
C++版本
认识数据访问类
查看生成的person.pb.h,会看到在.proto文件中指定的每条message都有一个类。而且每个message的字段,都被定义成类中的成员变量,并且还提供了一些方法对这些字段进行修改等操作。
以上文的.proto为例,每条message都对应设置为一个类,枚举message也同样被设置了:
class PhoneNumber;
class Address;
class Person;
enum PhoneType {
MOBILE = 0,
HOME = 1,
WORK = 2
};
以Person为例,查看类中的几个成员变量,囊括了message中定义的字段:
::std::string* name_;
::google::protobuf::int32 id_;
::google::protobuf::int32 age_;
::google::protobuf::RepeatedPtrField< ::std::string> email_;
::google::protobuf::RepeatedPtrField< ::PhoneNumber > phone_;
::Address* address_;
对message中的这些字段,person.pb.h也提供了一些方法来进行修改等操作:
// required int32 id = 1;
inline bool has_id() const;
inline void clear_id();
static const int kIdFieldNumber = 1;
inline ::google::protobuf::int32 id() const;
inline void set_id(::google::protobuf::int32 value);
// required string name = 2;
inline bool has_name() const;
inline void clear_name();
static const int kNameFieldNumber = 2;
inline const ::std::string& name() const;
inline void set_name(const ::std::string& value);
inline void set_name(const char* value);
inline void set_name(const char* value, size_t size);
inline ::std::string* mutable_name();
inline ::std::string* release_name();
inline void set_allocated_name(::std::string* name);
// optional int32 age = 3;
inline bool has_age() const;
inline void clear_age();
static const int kAgeFieldNumber = 3;
inline ::google::protobuf::int32 age() const;
inline void set_age(::google::protobuf::int32 value);
// repeated string email = 4;
inline int email_size() const;
inline void clear_email();
static const int kEmailFieldNumber = 4;
inline const ::std::string& email(int index) const;
inline ::std::string* mutable_email(int index);
inline void set_email(int index, const ::std::string& value);
inline void set_email(int index, const char* value);
inline void set_email(int index, const char* value, size_t size);
inline ::std::string* add_email();
inline void add_email(const ::std::string& value);
inline void add_email(const char* value);
inline void add_email(const char* value, size_t size);
inline const ::google::protobuf::RepeatedPtrField< ::std::string>& email() const;
inline ::google::protobuf::RepeatedPtrField< ::std::string>* mutable_email();
// repeated .PhoneNumber phone = 5;
inline int phone_size() const;
inline void clear_phone();
static const int kPhoneFieldNumber = 5;
inline const ::PhoneNumber& phone(int index) const;
inline ::PhoneNumber* mutable_phone(int index);
inline ::PhoneNumber* add_phone();
inline const ::google::protobuf::RepeatedPtrField< ::PhoneNumber >&
phone() const;
inline ::google::protobuf::RepeatedPtrField< ::PhoneNumber >*
mutable_phone();
// optional .Address address = 6;
inline bool has_address() const;
inline void clear_address();
static const int kAddressFieldNumber = 6;
inline const ::Address& address() const;
inline ::Address* mutable_address();
inline ::Address* release_address();
inline void set_allocated_address(::Address* address);
对message中字段进行修改等操作的方法,大致可以看出如下规律:
- 对于基本proto类型,非repeated字段,提供了与字段名相同的方法名来获取该字段的内容,提供了set方法来设置该字段的内容;而repeated字段,则需要根据index来获取字段的内容,提供了add方法来添加该字段的内容,或者指定index来进行set;
- 对于自定义的message类型,非repeated字段,提供了与字段名相同的方法名来获取该字段的内容,提供了mutable方法来设置该字段的内容;而repeated字段,则需要根据index来获取字段的内容,提供了add方法来添加该字段的内容,或者指定index来进行mutable。
序列化、反序列化
对于序列化和反序列化的方法,都定义在message.h中:
bool ParseFromIstream(std::istream* input);
bool SerializeToOstream(std::ostream* output) const;
bool ParseFromString(const std::string& data);
bool SerializeToString(std::string* output) const;
bool SerializeToArray(void* data, int size) const;
std::string GetTypeName() const override;
size_t ByteSizeLong() const override;
实例:
实例内容:将person的信息利用protobuf,序列化保存到文件main.proto(这里的.proto文件是二进制文件,和.proto结构声明文件仅仅后缀名相同而已)中;再读取该文件,反序列化成对象打印出来。
#include <iostream>
#include <fstream>
#include "person.pb.h"
int main(int argc, char const *argv[])
{
Person *person = new Person();
person->set_id(1);
person->set_name("zhangsan");
person->set_age(18);
person->add_email("1.qq.com");
person->add_email("2.qq.com");
PhoneNumber *phone1 = person->add_phone();
phone1->set_number("123456");
phone1->set_type(PhoneType::HOME);
PhoneNumber *phone2 = person->add_phone();
phone2->set_number("234567");
phone2->set_type(PhoneType::MOBILE);
Address *address = person->mutable_address();
address->set_country("China");
address->set_detail("Jiangsu");
std::cout << "write to main.proto" << std::endl;
std::ofstream ofs;
ofs.open("main.proto", std::ofstream::binary);
person->SerializeToOstream(&ofs);
ofs.close();
std::cout << "read from main.proto" << std::endl;
Person *person_tmp = new Person();
std::ifstream ifs;
ifs.open("main.proto", std::ofstream::binary);
person_tmp->ParseFromIstream(&ifs);
std::cout << "id : " << person_tmp->id() << std::endl;
std::cout << "name : " << person_tmp->name() << std::endl;
std::cout << "age : " << person_tmp->age() << std::endl;
std::cout << "email : " << person_tmp->email(0) << " " << person_tmp->email(1) << std::endl;
std::cout << "phone : " << person_tmp->phone(0).number() << " " << person_tmp->phone(1).number() << std::endl;
std::cout << "address : " << person_tmp->address().country() << " " << person_tmp->address().detail() << std::endl;
return 0;
}
采用CMakeLists.txt进行编译并运行:
yngzmiao@yngzmiao-virtual-machine:~/test/c++$ ./main
write to main.proto
read from main.proto
id : 1
name : zhangsan
age : 18
email : 1.qq.com 2.qq.com
phone : 123456 234567
address : China Jiangsu
Python版本
与C++版本类似,具体的person_pb2.py就不对具体的数据访问类进行分析了,想要了解,可以自己解读下。
序列化、反序列化
对于序列化和反序列化的方法,也比较简单:
str = obj.SerializeToString()
obj.ParseFromString(str)
obj.ByteSize()
实例:
实例内容:将person的信息利用protobuf,序列化保存到文件main.proto(这里的.proto文件是二进制文件,和.proto结构声明文件仅仅后缀名相同而已)中;再读取该文件,反序列化成对象打印出来。
# -*- coding:UTF-8 -*-
import os,sys
import proto_pb2.person_pb2 as person_proto
if __name__ == "__main__":
person = person_proto.Person()
person.id = 1
person.name = "zhangsan"
person.age = 18
person.email.append("1.qq.com")
person.email.append("2.qq.com")
phone1 = person.phone.add()
phone2 = person.phone.add()
phone1.number = "123456"
phone1.type = person_proto.PhoneType.HOME
phone2.number = "234567"
phone2.type = person_proto.PhoneType.MOBILE
addr = person.address
addr.country = "China"
addr.detail = "Jiangsu"
print("write to main.proto")
fw = open("main.proto", "w")
fw.write(person.SerializeToString())
fw.close()
print("read from main.proto")
fr = open("main.proto", "r")
data = fr.read()
person_tmp = person_proto.Person()
person_tmp.ParseFromString(data)
print("id : " + str(person_tmp.id))
print("name : " + str(person_tmp.name))
print("age : " + str(person_tmp.age))
print("email : " + str(person_tmp.email))
print("phone : " + str(person_tmp.phone))
print("address : " + str(person_tmp.address))
fr.close()
运行该脚本:
yngzmiao@yngzmiao-virtual-machine:~/test/python$ python main.py
write to main.proto
read from main.proto
id : 1
name : zhangsan
age : 18
email : [u'1.qq.com', u'2.qq.com']
phone : [number: "123456"
type: HOME
, number: "234567"
type: MOBILE
]
address : country: "China"
detail: "Jiangsu"
二进制文件
对比一下C++版本和python版本各自生成的main.proto二进制文件,最终发现,两个文件一模一样。
打开生成的二进制文件main.proto,可以看到内容:
08 01 12 08 7A 68 61 6E 67 73 61 6E 18 12 22 08 31 2E
71 71 2E 63 6F 6D 22 08 32 2E 71 71 2E 63 6F 6D 2A 0A
0A 06 31 32 33 34 35 36 10 01 2A 0A 0A 06 32 33 34 35
36 37 10 00 32 10 0A 05 43 68 69 6E 61 12 07 4A 69 61
6E 67 73 75
这些二进制文件是什么含义呢,会在后面的博文中具体介绍。
本文详细介绍了如何使用Protobuf进行数据的序列化和反序列化,包括C++和Python版本的实现,展示了从创建数据对象、序列化到文件、再到反序列化并打印的过程。
657





