protobuf 学习02 序列化 && 反射

我们重新复习一下, ProtoBuf 的序列化使用过程:

  • 定义 .proto 文件
  • protoc 编译器编译 .proto 文件生成一系列接口代码
  • 调用生成的接口实现对 .proto 定义的字段的读取以及 message 对象的序列化、反序列化方法

注意:并非编码成字符串数据,string 只是作为编码结果的容器

而我们经常调用的序列化函数 SerializeToString 并定义在基类 MessageLite 中。

编码

当某个 Message 调用 SerializeToString 时,经过一层层调用最终会调用底层的关键编码函数 WriteVarint32ToArray 或 WriteVarint64ToArray,整个过程如下图所示:

WriteVarint32ToArray 函数可在源码目录下的 google.protobuf.io 包下的 coded_stream.h 中找到。在上一篇 深入 ProtoBuf - 编码 中我们解析了 Varint 编码原理和详细过程,WriteVarint32ToArray(以及 WriteVarint64ToArray)便是 Varint 编码的核心。

可以对照上一篇指出的 Varints 编码的几个关键点来阅读以下代码,可以看出编码实现确实优雅,代码如下:

Length delimited 字段序列化

而对于 Length delimited 类型的字段,Tag-Length-Value 中的 Tag 和 Length 依然采用 Varint 编码,Value 若为 String 等类型,则直接进行 memcpy。

另外对于 embedded message 或 packed repeated ,则套用上述规则。底层编码实现实际便是遍历字段下所有内嵌字段,然后递归调用编码函数即可。

深入 ProtoBuf - 反射原理解析

计算机程序在运行时可以访问、检测和修改它本身状态或行为  —— 反射

如果用一句话来总结反射实现的关键,笔者会概括为:获取系统元信息

元信息:即系统自描述信息,用于描述系统本身。举例来讲,即系统有哪些类?类中有哪些字段、哪些方法?字段属于什么类型、方法又有怎样的参数和返回值?

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

而由于 C++ 在编译时并不会将类的元信息写进结果中,最终编译结果中只会包含变量、函数地址偏移、函数关系等,所以 C++ 自身无法获取元信息,那么自然无法提供反射能力。但这并不意味着使用 C++ 就无法应用反射技术,程序可以自己设计与实现程序元信息以及元信息与地址之间的映射。例如本文所要介绍的 ProtoBuf 反射实现

正如上一节提到的,反射的核心要点是:获取程序元信息。

ProtoBuf 自然也不会例外,那么 ProtoBuf 反射所需的元信息在哪?答案便是使用 ProtoBuf 的第一步就会接触到的:.proto 文件

ProtoBuf 反射原理概述

我们在 深入 ProtoBuf - 简介 一文中介绍过使用 ProtoBuf 的第一步便是创建 .proto 文件,定义我们所需的数据结构。但很多人没有意识到,这个过程同时也是为 ProtoBuf 提供我们数据元信息的过程,这些元信息包括数据由哪些字段构成,字段又属于什么类型以及字段之间的组合关系等。

当然元信息也并非一定由 .proto 文件提供,它也可来自于网络或其它可能的输入,只要它满足 ProtoBuf Message 的定义语法即可。那么元信息的可能来源和处理就有:

  • .proto 文件
    • 使用 ProtoBuf 内置的工具 protoc 编译器编译,protoc 将 .proto 文件内容编码并写入生成的代码中(.pb.cc 文件)
    • 使用 ProtoBuf 提供的编译 API 在运行时手动(指编码)解析 .proto 文件内容。实际上 protoc 底层调用的也正是这个编译 API。
  • 非 .proto 文件
    • 从远程读取,如将数据与数据元信息一同进行 protobuf 编码并传输:

其中 descriptor 数组存储的便是 .proto 内容。这里当然不是简单的存储原始文本字符串,而是经过了 SerializeToString 序列化处理,而后将结果以硬编码的形式保存在 xxx.pb.cc 中,真是充分利用了自己的高效编码能力。

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

二、
代码 4-1 例子中的第二步是根据 MessageFactory 获得了一个实例。

MessageFactory 是实例工厂,对外提供了根据元信息 descriptor 获取相应实例的能力

猜你喜欢

转载自blog.csdn.net/kuaipao19950507/article/details/107287272