序列化之protobuf

7 分钟阅读

一、基本说明

  • 源码:protobuf
  • 指南:Protocol Buffer Basics: C++
  • 结构定义文件为.proto,可以使用import包含另一个.proto文件,注释使用//
  • 配置文件为.prototxt, 根据.proto的结构配置数据信息

二、语法

字段限制

  • required: 必须赋值的字符
  • optional: 可有可无的字段,可以使用[default = xxx]配置默认值
  • repeated: 可重复变长字段,类似数组

Tags

  • 每个字段都有独一无二的tag
  • tag 1-15是字节编码,16-2047使用2字节编码,所以1-15给频繁使用的字段

类型

proto Note C++
float   float
double   double
int32 变长编码. 编码负数效率底下– 打算使用负数的话请使用 sint32. int32
int64 变长编码. 编码负数效率底下– 打算使用负数的话请使用 sint64. int64
uint32 变长编码. uint32
uint64 变长编码. uint64
sint32 U变长编码. 数值有符号,负数编码效率高于int32 int32
sint64 U变长编码. 数值有符号,负数编码效率高于int64 int64
fixed32 固定4byte, 如果数值经常大于2的28次方的话效率高于uint32. uint32
fixed64 固定8byte, 如果数值经常大于2的56次方的话效率高于uint64. uint64
sfixed32 固定4byte. int32
sfixed64 固定8byte. int64
bool   bool
string 字符串内容应该是 UTF-8 编码或者7-bit ASCII 文本. string
bytes 任意二进制数据. string

系统默认值:string默认为空字符串,bool默认为false,数值默认为0,enum默认为第一个元素

解析与序列化

每个message都包含如下方法,用于解析和序列化,注意目标是字节形式,非文本。

  • bool SerializeToString(string* output) const: 将message序列化成二进制保存在output中,注意保存的是二进制,不是文本;仅仅是string作为容器。
  • bool ParseFromString(const string& data): 从给定的二进制数值中解析成message
  • bool SerializeToOstream(ostream* output) const: 序列化到ostream中
  • bool ParseFromIstream(istream* input): 从istream中解析出message

三、举例介绍

1. 建立proto文件

建立.proto文件定义message,如下addressbook.proto

syntax = "proto2"; // 定义语法类型,通常proto3好于proto2,proto2好于proto1

package tutorial; // 定义作用域

message Person {  // 生成类class Person : public ::google::protobuf::Message
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType { // 定义枚举类型,生成Person_PhoneType类型
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber { // 生成Person_PhoneNumber类
    required string number = 1;
    optional PhoneType type = 2 [default = HOME]; // 值必须是枚举类型中的一个
  }

  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}

2. 生成源码文件

protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

其中protoc工具地址:protoc

1) message Person{}

会生成源码如下:

class Person  : public ::google::protobuf::Message {
Person(); // 默认构造函数
~Person(); // 默认西沟函数
Person(const Person& other); // 拷贝构造函数
Person& operator=(const Person& other); // 赋值构造函数
void Swap(Person* other); // 内容交换
const UnknownFieldSet& unknown_fields() const;
UnknownFieldSet* mutable_unknown_fields();
static const Descriptor* descriptor();
static const Foo& default_instance();
};
2) required string name

生成源码如下:

bool has_name() const; // 返回true,如果name被设置
void clear_name(); // 清除name,has_name会返回false
const ::std::string& name() const; // 返回name字符串
void set_name(const ::std::string& value); // 设置name字符串
::std::string* mutable_name(); // 可以修改的字符串,has_name会返回true
3) required int32 id

生成源码如下:

bool has_id() const; // 返回true,如果id被设置
int32 id() const; // 返回id数值
void set_id(int32 value); // 设置id数值
void clear_id(); // 清除id,has_id会返回false
4) message Person { message PhoneNumber }

生成类Person_PhoneNumber,并在Person类内定义:

typedef Person_PhoneNumber PhoneNumber;

其他消息如果要使用PhoneNumber,使用方法如下:

message OtherPerson {
    optional Person.PhoneNumber number = 1;
}

3. 定义prototxt文件

定义如下:

people{
   name : "Lucy"
   id : 120
   email : "lucy@163.com"
   phones{
       number : "13540737210"
       type : HOME
   }
}

4. 代码中解析prototxt文件

Person one;
string filename = "one.prototxt";
ReadProtoFromTextFileOrDie(filename, &one);