当前位置: 代码迷 >> 综合 >> 【Protobuf】Protobuf的C++/python版本的使用
  详细解决方案

【Protobuf】Protobuf的C++/python版本的使用

热度:65   发布时间:2023-12-18 03:18:29.0

上一篇博文讲解了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_protoif __name__ == "__main__":person = person_proto.Person()person.id = 1person.name = "zhangsan"person.age = 18person.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.HOMEphone2.number = "234567"phone2.type = person_proto.PhoneType.MOBILEaddr = person.addressaddr.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

这些二进制文件是什么含义呢,会在后面的博文中具体介绍。


相关阅读

  • Developer Guide
  • protobuf相关的操作函数