关于protobuf的使用、编码原理、编码原理应用 可以分别参见以下文章。

Python 操作 protobuf 常见用法

linux环境下protobuf的安装与使用

Protobuf编码规则详解

protobuf编码原理及其在schema格式转换的应用

另外,陈硕大神的 这篇 非常值得参考。

这篇文章主要是介绍下protobuf的反射机制、pb反射机制涉及到的几个类、pb反射实现步骤、反射在pb↔json互转的应用。

首先解决一个问题,反射是什么?可以干什么用?

1、反射是什么。给定一个pb对象,如何自动遍历该对象的所有字段?换句话说 是否有一个通用的方法可以遍历任意pb对象的所有字段,而不用关心具体对象类型。为了加深理解这里引用下陈硕文章的一段话。“这里要解决的问题是:在接收到protobuf数据之后,如何自动创建具体的Protobuf Message对象,再做反序列化。‘自动’的意思是:当程序中新增一个protobuf message类型时,这部分代码不需要修改,不需要自己注册消息类型。”

这里我在明确一下:其实就是如何用type name 自动创建Message对象;然后再对序列化后的二进制流反序列化即可还原出原数据。

举个例子:对于如下pb message类型Person实例,我们能否将对象自动转换为json字符串{"name":"waitingzhuo","age":26}。对于这个问题很多同学会说这不简单,把Person解析出来然后取出各个字段再创建json就好了呀。 注意:这里的“自动”二字。比如说如果我们又在pb中添加了新的字段 person.set_email("izualzhy@163.com") ,你得到json能否自动的新增这个字段?亦或是你需要通过手动修改的pb2json的代码才能实现。

#有如下proto文件
syntax = "proto2";
package tencent;message Person
{required string name = 1;required uint32 age  = 2;optional uint64 email = 3;
}
Person person;
person.set_name("yingshin");
person.set_age(21);

答案就是protobuf的反射功能。实际上protobuf自身的反序列化过程就是利用反射实现的。

2、使用场景。前面已经说了pb与json格式的相互转换,其实很多pb2json的底层库就是利用protobuf的反射能力来实现的。另外pb到xml的转换、pb直接写数据库(如不同字段写到hbase的不同列)、基于pb的自动化测试工具等都是pb反射机制的应用场景。一句话:反射只是一种机制,有着什么样的应用场景要看你的想象力了。

3、反射实现的关键点。

反射实现的关键点是获取系统的元(meta)信息。

原信息:即系统字描述信息,用于描述系统本身。举例来说,即系统有哪些类?每个类中有哪些字段、哪些方法?字段属于什么类型、方法又又着怎样的参数及返回值?………

对于java而言,其能够提供反射能力的关键是在编译阶段将程序的meta信息编译进了.class文件,在程序运行事JVM将会把.class文件加载到JVM内存模型中的方法区。此后程序运行时将有能力获取关于自身的元信息。除了java外,JS、python、GO、PHP等语言也在语言层面实现了程序的反射。

那么protobuf反射所需的元信息在哪?——其实就在.proto文件中。用户在.proto文件中定义我们所需的数据结构,这个过程同事也是为protobuf提供数据元信息的过程。即 这些元信息包括数据由哪些字段构成,字段又属于什么类型一级字段之间的组合关系等。

反射的两种主要用途:

(1)通过proto对象的名字来创建一个对象(json→pb)

#include <iostream>
#include <string>
#include "person.pb.h"using namespace std;/*Descriptor/FieldDescriptor位于descriptor.h文件;Message/Reflection 位于message.h文件以上四个类都位于 namespace google::protobuf下.
*/
using namespace google::protobuf;typedef tencent::Person T;Message* createMessage(const std::string& typeName);int main()
{//通过Descriptor类的full_name函数获取相应结构的type_namestd::string type_name = T::descriptor()->full_name();cout << "type_name:" << type_name << endl;//根据type name创建相应的message对象 new_personMessage* new_person = createMessage(type_name);assert(new_person != NULL);//指针为null向stderr打印一条信息assert(typeid(*new_person) == typeid(tencent::Person::default_instance()));cout << "new_person:" << new_person->DebugString() << endl;//接下来使用DescriptorPool类的FindMessageTypeByName方法通过type_name查到元信息Descriptor*const Descriptor* descriptor = google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(type_name);cout << "FindMessageTypeByName() = " << descriptor << endl;cout << "T::descriptor()         = " << T::descriptor() << endl;cout << endl;// 再用MessageFactory::generated_factory() 找到 MessageFactory 对象const Message* prototype = MessageFactory::generated_factory()->GetPrototype(descriptor);cout << "GetPrototype()        = " << prototype << endl;cout << "T::default_instance() = " << &T::default_instance() << endl;cout << endl;//再然后我们实例化出一个实例//dynamic_cast:将基类的指针或引用安全第一转换成派生类的指针或引用,并用派生类的指针或引用调用非虚函数。T* new_obj = dynamic_cast<T*>(prototype->New());cout << "prototype->New() = " << new_obj << endl;cout << endl;/*--------接下来看看反射接口怎么用--------*///获取这个message的反射接口指针const Reflection* reflecter = new_obj->GetReflection();//通过name查找filedconst FieldDescriptor* field = descriptor->FindFieldByName("name");//设置这个field的字段值std::string str1 = "shuozhuo";reflecter->SetString(new_obj, field, str1);//取出这个field的值std::cout << "\"name\" field is:" << reflecter->GetString(*new_obj,field)<< std::endl;
}/*本函数的作用就是根据type name 自动创建具体的protobuf message对象;
*/
Message* createMessage(const std::string& typeName)
{Message* message = NULL;const Descriptor* descriptor = DescriptorPool::generated_pool()->FindMessageTypeByName(typeName);if (descriptor){const Message* prototype = MessageFactory::generated_factory()->GetPrototype(descriptor);if (prototype){message = prototype->New();}}return message;
}

1)获取元信息DescriptorPool。通过DescriptorPool的FindMessageTypeByName获得元信息Descriptor(即Descriptor类)。

其中DescriptorPool为元信息池,对外提供FindMessageTypeByName、FindMessageTypeByName等各类接口以便外部查询所需的原信息。

DescriptorDatabase 可从硬编码或磁盘中查询对应名称的 .proto 文件内容,解析后返回查询需要的元信息。DescriptorPool 相当于缓存了文件的 Descriptor(底层使用 Map),查询时将先到缓存中查询,如果未能找到再进一步到 DB 中(即 DescriptorDatabase)查询,此时可能需要从磁盘中读取文件内容,然后再解析成 Descriptor 返回,这里需要消耗一定的时间。从上面的描述不难看出,DescriptorPool 和 DescriptorDatabase 通过缓存机制提高了反射运行效率,但这只是反射工程实现上的一种优化,我们更感兴趣的应该是 Descriptor 的来源。

DescriptorDatabase 从磁盘中读取 .proto 内容并解析成 Descriptor 这一来源很容易理解,但我们大多数时候并不会采用这种方式,反射时也不会去读取 .proto 文件。那么我们的 .proto 内容在哪?实际上我们在使用 protoc 生成 xxx.pb.cc 和 xxx.pb.h 文件时,其中不仅仅包含了读写数据的接口,还包含了 .proto 文件内容。阅读任意一个 xxx.pb.cc 的内容,你可以看到如下类似一坨东西。

这个数组存的就是.proto内容。当然这里并不是简单的存原始文本字符串,而是经过SerializeToString序列化处理的结果,这个结果以硬编码的方式板寸在xxx.pb.cc中。

硬编码的 .proto 元信息内容将以懒加载的方式(被调用时才触发)被 DescriptorDatabase 加载、解析,并缓存到 DescriptorPool 中。

2)其他语句就不做详解了。

(2)通过Message初始化和获取成员变量的值(pb→json)

看最后的示例即可。

反射相关接口(相关类介绍):

首先看下UML图

注:下面说的type name 指的是完整的结构名称,例如上面的“tencent.Person”。 

1、MessageLite类

所有message的接口类,从名字看就是lite的message,普通的message也是他的子类。

MessageLite是和“轻量级”的message(仅仅提供encoding+序列化,没有reflection和descriptors)。在确定可以使用“轻量级”message的场景下,可以在.proto文件中增加配置(option optimize_for = LITE_RUNTIME;),来让protocol compiler产出MessageLite类型的类,这样可以节省runtime资源。

注:lite 低热量的、淡的、轻量级的。

2、Message类

接口类,在MessageLite类的基础上增加了descriptor和reflection。

3、MessageFactory类

接口类,来找到MessageFactory对象,他能创建程序编译的时候所链接的全部protobuf Message types。其提供的GetPrototype方法可以找到具体的Message Type的default instance。  底层封装了GeneratedMessageFactory类。

4、DescriptorPool类

用 DescriptorPool::generated_pool() 找到一个 DescriptorPool 对象,它包含了程序编译的时候所链接的全部 protobuf Message types。然后通过其提供的 FindMessageTypeByName 方法即可根据type name 查找到Descriptor。

5、GeneratedMessageFactory类

继承自MessageFactory,singleton模式。

6、Descriptor类

描述一种message的meta信息(注意:不是单独的message对象)。构造函数是private类型,必须通过DescriptorPool(friend类)来构造。

const成员如下:

const FileDescriptor* file_: 描述message所在的.proto文件信息
const Descriptor* containing_type_:如果在proto定义中,这个message是被其它message所包含,那么这个字段是上一级message的descriptor*;如果没有被包含,那么是NULL
const MessageOptions* options_: 定义在descriptor.proto,从注释看是用来和老版本proto1中MessageSet做拓展,可以先不去关注涉及extension的部分。

非const成员如下:

int field_count_:当前field包含的field的个数
FieldDescriptor* fields_: 以连续数组方式保存的所有的fieds
int nested_type_count_: 嵌套类型数量
Descriptor* nested_types_: message中嵌套message
int enum_type_count_: 内部enum的个数
EnumDescriptor* enum_types_: enum类型的连续内存起始地址

7、FileDescriptor类

描述整个.proto文件信息,其中包含:

1、依赖.proto文件信息:
int dependency_count_;
const FileDescriptor** dependencies_;2、当前.proto文件包含的message信息:
int message_type_count_;
Descriptor* message_types_;3、当前.proto文件包含的所有symbol(各种discriprot)的tables:
const FileDescriptorTables* tables_;

8、FileDescriptor类

描述一个单独的field,构造函数为private,也必须由DescriptorPool(friend类)构造。通过包含这个field的message的descriptor的函数(Descriptor::FindFieldByName())获得。

9、EnumDescriptor类

描述在.proto文件中定义的enum类型。

应用实例:pb↔json相互转换

文件命令为 pb2json.cpp ,代码在个人云服务器 /mystudy/protobuf2json

实例程序可以从 这里 获得!!!!

执行效果如下:

[root@VM_50_94_centos /mystudy/protobuf2/pb2json]# ./pb2json
-------------------------------pbbuf二进制流反序列化---------------------------
body1 is:riXXXXXt {exxxs {text {str: "\346\226\260\345\271\264\345\260\206\350\207\263"}}exxxs {face {index: 69old: "\024h"}}exxxs {text {str: "\357\274\214\347\272\242\345\214\205\345\257\271\350\201\224\345\244\247\347\244\274\345\214\205\347\201\253\347\203\255\345\256\232\345\210\266\344\270\255"}}exxxs {face {index: 144old: "\024\321"}}exxxs {text {str: "\357\274\201\357\274\201\357\274\201"}}elems {crm_elem {crm_buf: "\010\010\202\001.\010"}}
}-------------------------------将pb对象 → json-------------------------------
my_json{"rixxxxxt" : {"elxxxs" : [{"texxt" : {"str" : "新年将至"}},{"face" : {"index" : 69,"old" : "\u0014h"}},{"text" : {"str" : ",红包对联大礼包火热定制中"}},{"face" : {"index" : 144,"old" : "\u0014�"}},{"text" : {"str" : "!!!"}},{"crm_elem" : {"crm_buf" : "\b\b�\u0001.\b"}}]}
}----------------通过Descriptor类的full_name函数获取相应结构的type_name-----------
type_name:tencent.im.msg.MsgBody
---------------------根据type_name创建一个default instance---------------------
------------------------根据json填充这个default instance------------------------
my_msgbody is:rxxxxt {exxxxs {text {str: "\346\226\260\345\271\264\345\260\206\350\207\263"}}exxxxs {face {index: 69old: "\024h"}}exxxxs {txxxt {stxxxr: "\357\274\214\347\272\242\345\214\205\345\257\271\350\201\224\345\244\247\347\244\274\345\214\205\347\201\253\347\203\255\345\256\232\345\210\266\344\270\255"}}elems {face {index: 144old: "\024\321"}}exxxs {text {str: "\357\274\201\357\274\201\357\274\201"}}exxxxs {crxxxxm {cxxxxxuf: "\010\010\202\001.\010"}}
}

参见: GitHub - HaustWang/pb2json: protobuf message与json互转,使用C++11特性

深入 ProtoBuf - 反射原理解析 - 简书

protobuf反射详解及应用(pb/json相互转换)相关推荐

  1. protobuf反射详解

    本文主要介绍protobuf里的反射功能,使用的pb版本为2.6.1,同时为了简洁,对repeated/extension字段的处理方法没有说明. 最初是起源于这样一个问题: 给定一个pb对象,如何自 ...

  2. C#高级--反射详解

    C#高级–反射详解 零.文章目录 一.反射是什么 1.C#编译运行过程 高级语言->编译->dll/exe文件->CLR/JIT->机器码 2.原理解析 metadata:元数 ...

  3. mysql的json函数与实例_Mysql实例详解Mysql中的JSON系列操作函数

    <Mysql实例详解Mysql中的JSON系列操作函数>要点: 本文介绍了Mysql实例详解Mysql中的JSON系列操作函数,希望对您有用.如果有疑问,可以联系我们. MYSQL必读前言 ...

  4. qpython能使用json吗l_[python] 详解Python在使用JSON时需要注意的编码问题

    Python 中的字符编码 在 Python3 中, 字符 在内存中是使用 Unicode 存储的, 常规的字符使用 两个字节 表示, 一些很生僻的字符就需要 四个字节. 默认使用 Unicode 存 ...

  5. Java :反射详解

    Java 反射详解 目录 Java 反射详解 1.什么是反射? 2.反射能做什么? 3.反射的具体实现 4.根据反射获取父类属性 4.反射总结 1.什么是反射? Java反射就是在运行状态中,对于任意 ...

  6. java 7 反射_【7】java 反射详解

    [7]java 反射详解 获取Class对象的方式: 1. Class.forName("全类名"); 将字节码加载进内存,返回Class对象,多用于配置文件,将类名定义在配置文件 ...

  7. python json方法详解_详解python中的json的基本使用方法

    在Python中使用json的时候,主要也就是使用json模块,json是以一种良好的格式来进行数据的交互,从而在很多时候,可以使用json数据格式作为程序之间的接口. #!/usr/bin/env ...

  8. 【C++】Google Protocol Buffer(protobuf)详解(二)

    代码走读:caffe中protobuf的详细使用过程 [一]proto文件,以caffe.proto中BlobShape为例 syntax = "proto2"; //指明prot ...

  9. ProtoBuf格式详解

    "介绍protobuf编码格式." protobuf是一种数据交换格式,又称PB编码,由Google开源,类似于Json.XML,但其内部是纯二进制格式,比Json,XML等格式要 ...

  10. 12_Go语言 反射详解

    1. 反射定义: 可以在运行时动态获取变量的相关信息. Import ("reflect") 官方对此有个非常简明的介绍,两句话耐人寻味: 反射提供一种让程序检查自身结构的能力.再 ...

最新文章

  1. unix时间戳(unix timestamp)与北京时间的互转方法
  2. 在LINUX上配置oracle ASMLib的多路径磁盘
  3. HSDIS工具在macbook M1电脑的安装
  4. 在WPF程序中使用多线程技术
  5. IDEA显示Run Dashboard窗口,Multiple Spring Boot run configurations were detected. Run Dashboard allows to
  6. Vue采用input实现文件上传与删除
  7. 修改vim中的tab为4个空格
  8. 线性代数三之状压DP的矩阵加速——Quad Tiling,Bus公交线路
  9. Flash网站流量统计的方法
  10. python实训报告50000_Python程序设计 实验报告五
  11. golang | 使用goroutine和channel实现一个计算int64随机数各位数和的程序。
  12. clion环境搭建c++_mingw64_clion中使用python,ros-qt
  13. ffmpeg系列-解复用流程解析
  14. 使用Editplus查看空格
  15. java生成pdf文档
  16. MFC API——》ModifyStyle
  17. 唯物论、辩证法和认识论
  18. Robot Rapping Results Report CodeForces - 645D
  19. C++ strcpy、strcat、strcmp和strlen的实现
  20. 单工通信、半双工通信和全双工通信之间有什么区别。

热门文章

  1. java 全角半角符号转换_java 字符串全角半角转换
  2. MySQL — 利用命令:获取系统当前时间、打开系统服务、Mysql命令界面快速回到之前的语句
  3. 1018 锤子剪刀布 (20 分)—PAT (Basic Level) Practice (中文)
  4. Android AbsoluteLayout 绝对布局
  5. Linux使用CLASS_ATTR创建节点
  6. P1541 乌龟棋 线性dp
  7. D - Send a Table (UVA - 10820)
  8. Boundary Representations
  9. 《上古天真论》第六讲文字版
  10. 第二个网站成长经历,http://www.chaomagou.com/ 潮妈购