ProtoBuf 反射详解

Protocol Buffer 简称 ProtoBuf,是用于结构化数据串行化的灵活、高效、自动的方法,又如 XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。

本文主要介绍 protobuf 里的反射功能,使用的pb版本为2.6.1,同时为了简洁,对 repeated/extension 字段的处理方法没有说明。

最初是起源于这样一个问题:

给定一个pb对象,如何自动遍历该对象的所有字段?

即是否有一个通用的方法可以遍历任意pb对象的所有字段,而不用关心具体对象类型。

使用场景上有很多:

比如json格式字符串的相互转换,或者bigtable里根据pb对象的字段自动写列名和对应的value。

例如定义了pb messge类型Person如下:

能否将该对象自动转化为json字符串{"name":"yingshin","age":21},或者自动的写hbase里的多列:

key column-name column-value
uid name yingshin
uid age 21

如果设置了新的字段,比如person.set_email("zhy198606@gmail.com"),则自动添加新的一列:

key column-name column-value
uid email zhy198606@gmail.com

答案就是 pb的反射功能

我们的目标是提供这样两个接口:

接下来逐步介绍下如何实现。

1. 反射相关接口

要介绍pb的反射功能,先看一个相关的UML示例图:

pb-reflection

各个类以及接口说明:

1.1 Message

Person是自定义的pb类型,继承自Message. MessageLite作为Message基类,更加轻量级一些。

通过Message的两个接口GetDescriptor/GetReflection,可以获取该类型对应的Descriptor/Reflection。

1.2 Descriptor

Descriptor是对message类型定义的描述,包括message的名字、所有字段的描述、原始的proto文件内容等。

本文中我们主要关注跟字段描述相关的接口,例如:

  1. 获取所有字段的个数:int field_count() const
  2. 获取单个字段描述类型FieldDescriptor的接口有很多个,例如
1.3 FieldDescriptor

FieldDescriptor描述message中的单个字段,例如字段名,字段属性(optional/required/repeated)等。

对于proto定义里的每种类型,都有一种对应的C++类型,例如:

获取类型的label属性:

获取字段的名称:const string& name() const;

1.4 Reflection

Reflection主要提供了动态读写pb字段的接口,对pb对象的自动读写主要通过该类完成。

对每种类型,Reflection都提供了一个单独的接口用于读写字段对应的值。

例如对于读操作:

特殊的,对于枚举和嵌套的message:

对于写操作也是类似的接口,例如SetInt32/SetInt64/SetEnum等。

2. 反射示例

示例主要是接收任意类型的message对象,遍历解析其中的每个字段、以及对应的值,按照自定义的格式存储到一个string中。同时重新反序列化该string,读取字段以及value,填充到message对象中。例如:

其中Person是自定义的protobuf message类型,用于设置一些字段验证我们的程序。

单纯的序列化/反序列化功能可以通过pb自带的SerializeToString/ParseFromString接口完成。这里主要是为了同时展示自动从pb对象里提取field/value,自动根据field/value来还原pb对象这个功能。

其中Person定义是对example里的addressbook.proto做了少许修改(修改的原因是本文没有涉及pb里数组的处理)

3. 反射实例实现

3.1 serialize_message

serialize_message遍历提取message中各个字段以及对应的值,序列化到string中。主要思路就是通过Descriptor得到每个字段的描述符:字段名、字段的cpp类型。通过Reflection的GetX接口获取对应的value。

3.2 parse_message

parse_message通过读取field/value,还原message对象。
主要思路跟serialize_message很像,通过Descriptor得到每个字段的描述符FieldDescriptor,通过Reflection的SetX填充message。

2 3 收藏 2 评论

关于作者:yingshin

简介还没来得及写 :) 个人主页 · 我的文章 · 3 ·  

相关文章

可能感兴趣的话题



直接登录
最新评论
  • Aha__ha   2016/05/22

    这个是数据传输的利器啊。

  • 麦田听雨声   04/25

    您好,请问您知道如何遍历查询一个protobuf消息中所有的字段吗?
    我知道一种方法就是首先取到field_count,然后通过FindFieldByNumber(),来循环读取所有的字段,但是现在有一种情况,就是protobuf定义时,字段号是不连续的,比如下面这种,
    message MyPb
    {
    uint32 id =1;
    uint32 score =2;
    string name =5;
    uint32 high =6;
    }
    像上面这种定义,取到的field_count应该是4,那么我遍历时调用FindFieldByNumber(1)~FindFieldByNumber(4),能取到字段号为5的name以及为6的high吗?或者我的理解有问题,取得时候通过FindFieldByNumber(1)~FindFieldByNumber(4)便可以取到所有的字段。
    谢谢。

跳到底部
返回顶部