Protobuf动态解析那些事儿
需求背景
在接收到 protobuf 数据之后,如何自动创建具体的 Protobuf Message 对象,再做反序列化。“自动”的意思主要有两个方面:(1)当程序中新增一个 protobuf Message 类型时,这部分代码不需要修改,不需要自己去注册消息类型,不需要重启进程,只需要提供protobuf文件;(2)当protobuf Message修改后,这部分代码不需要修改,不需要自己去注册消息类型,不需要重启进程只需要提供修改后protobuf文件。
技术介绍
Protobuf的入门可以参考Google Protocol Buffer 的在线帮助 网页或者IBM developerwor上的文章《Google Protocol Buffer 的使用和原理》。
protobuf的动态解析在google protobuf buffer官网并没有什么介绍。通过google出的一些参考文档可以知道,其实,Google Protobuf 本身具有很强的反射(reflection)功能,可以根据 type name 创建具体类型的 Message 对象,我们直接利用即可,应该就可以满足上面的需求。
实现可以参考淘宝的文章《玩转Protocol Buffers 》,里面对protobuf的动态解析的原理做了详细的介绍,在此我介绍一下Protobuf class diagram。
大家通常关心和使用的是图的左半部分:MessageLite、Message、Generated Message Types (Person, AddressBook) 等,而较少注意到图的右半部分:Descriptor, DescriptorPool, MessageFactory。
上图中,其关键作用的是 Descriptor class,每个具体 Message Type 对应一个 Descriptor 对象。尽管我们没有直接调用它的函数,但是Descriptor在“根据 type name 创建具体类型的 Message 对象”中扮演了重要的角色,起了桥梁作用。上图的红色箭头描述了根据 type name 创建具体 Message 对象的过程。
实现
先直接上代码,这个代码来自于《玩转Protocol Buffers 》:
|
#include <iostream> #include <google/protobuf/descriptor.h> #include <google/protobuf/descriptor.pb.h> #include <google/protobuf/dynamic_message.h> #include <google/protobuf/compiler/importer.h> using namespace std; using namespace google::protobuf; using namespace google::protobuf::compiler; int main(int argc,const char *argv[]) { DiskSourceTree sourceTree; //look up .proto file in current directory sourceTree.MapPath("","./"); Importer importer(&sourceTree, NULL); //runtime compile foo.proto importer.Import("foo.proto"); const Descriptor *descriptor = importer.pool()-> FindMessageTypeByName("Pair"); cout << descriptor->DebugString(); // build a dynamic message by "Pair" proto DynamicMessageFactory factory; const Message *message = factory.GetPrototype(descriptor); // create a real instance of "Pair" Message *pair = message->New(); // write the "Pair" instance by reflection const Reflection *reflection = pair->GetReflection(); const FieldDescriptor *field = NULL; field = descriptor->FindFieldByName("key"); reflection->SetString(pair, field,"my key"); field = descriptor->FindFieldByName("value"); reflection->SetUInt32(pair, field, 1111); cout << pair->DebugString(); delete pair; return0; } |
那我们就来看看上面的代码
1)把本地地址映射为虚拟地址
DiskSourceTree sourceTree;
//look up .proto file in current directory
sourceTree.MapPath("","./");
2)构造DescriptorPool
Importer importer(&sourceTree, NULL);
//runtime compile foo.proto
importer.Import("foo.proto");
3)获取Descriptor
const Descriptor *descriptor = importer.pool()->FindMessageTypeByName("Pair");
4)通过Descriptor获取Message
const Message *message = factory.GetPrototype(descriptor);
5)根据类型信息使用DynamicMessage new出这个类型的一个空对象
Message *pair = message->New();
6)通过Message的reflection操作message的各个字段
const Reflection *reflection = pair->GetReflection();
const FieldDescriptor *field = NULL;
field = descriptor->FindFieldByName("key");
reflection->SetString(pair, field,"my key");
field = descriptor->FindFieldByName("value");
reflection->SetUInt32(pair, field, 1111);
直接copy上面代码看起来我们上面的需求就满足了,只是唯一的缺点就是每次来个包加载一次配置文件,当时觉得性能应该和读取磁盘的性能差不多,但是经过测试性能极差,一个进程每秒尽可以处理1000多个包,经过分析性能瓶颈不在磁盘,而在频繁调用malloc和free上。
看来我们得重新考虑实现,初步的实现想法:只有protobuf描述文件更新时再重新加载,没有更新来包只需要使用加载好的解析就可以。这个方案看起来挺好的,性能应该不错,经过测试,性能确实可以,每秒可以处理3万左右的包,但是实现中遇到了困难。要更新原来的Message,必须更新Importer和Factory,那么要更新这些东西,就涉及到了资源的释放。经过研究这些资源的释放顺序特别重要,下面就介绍一下protobuf相关资源释放策略。
动态的Message是我们用DynamicMessageFactory构造出来的,因此销毁Message必须用同一个DynamicMessageFactory。 动态更新.proto文件时,我们销毁老的并使用新的DynamicMessageFactory,在销毁DynamicMessageFactory之前,必须先删除所有经过它构造的Message。
原理:DynamicMessageFactory里面包含DynamicMessage的共享信息,析构DynamicMessage时需要用到。生存期必须保持Descriptor>DynamicMessageFactory>DynamicMessage。
释放顺序必须是:释放所有DynamicMessage,释放DynamicMessageFactory,释放Importer。
总结
资源释放前,必须要了解资源的构造原理,通过构造原理反推释放顺序,这样就少走弯路、甚至不走。
参考文献
Google Protocol Buffer 的在线帮助 网页
Protobuf动态解析那些事儿的更多相关文章
- Protobuf动态解析在Java中的应用 包含例子程序
最近在做ProtoBuf相关的项目,其中用到了动态解析,网上看了下相关资料和博文都比较少,自己来写一个记录一下学习过程. Protocol Buffers是结构化数据格式标准,提供序列化和反序列方 ...
- 开源一个动态解析protobuf的工具
好久没写博客了,主要是这一年技术没啥长进都打杂了,还有就是生活琐事越来越多,人也越来越懒了…… 之前项目中用到了Protobuf,然后测试发现这玩意不好测,总不能每次定个协议或者改下都要编译Java代 ...
- 使用Newtonsoft.Json.dll(JSON.NET)动态解析JSON、.net 的json的序列化与反序列化(一)
在开发中,我非常喜欢动态语言和匿名对象带来的方便,JSON.NET具有动态序列化和反序列化任意JSON内容的能力,不必将它映射到具体的强类型对象,它可以处理不确定的类型(集合.字典.动态对象和匿名对象 ...
- 理解AngularJS生命周期:利用ng-repeat动态解析自定义directive
ng-repeat是AngularJS中一个非常重要和有意思的directive,常见的用法之一是将某种自定义directive和ng-repeat一起使用,循环地来渲染开发者所需要的组件.比如现在有 ...
- jsoncpp动态解析节点类型
在互联网无处不在的今天,JSON作为轻量级数据存储格式,被广泛应用到互联网数据传输中.众所周知,JSON由键/值对.对象.数组组成,其中键/值对的值包括以下几种类型: enum ValueType { ...
- 在C#中,Newtonsoft.Json + dynamic动态解析jsonString,jsonString转实体
记录一下 引用 using Newtonsoft.Json; using Newtonsoft.Json.Linq; var jsonString = "{\"ApiResourc ...
- C#匿名类型和动态解析减少定义传输类模板
C#作为强类型语言,在序列化和反序列化(json)场景中对字符串解析常常需要定义强类型模板,造成编码上的繁琐.其实可以使用匿名类型和动态解析减少json序列化时候的数据模板定义: string a = ...
- 域名动态解析到动态IP
一般宽带用户的IP都是动态IP,重连之后IP可能会发生变化. 如果想在其他地方连接家里的设备,或者在家中搭建服务器,就会受到影响. 现在提供一种动态解析域名的方式,只要检测到IP的变化,那么就调用阿里 ...
- 使用 DNSPOD API 实现域名动态解析
0. 简单概述在家里放一个 NAS 服务器,但是宽带的 IP 地址经常改变,一般路由器自带的花生壳域名解析可以解决,如果路由器没有类似功能或者想使用自己的域名,可以尝试使用 DNSPOD API 来实 ...
随机推荐
- iis7应用程序池自动关闭问题解决
解决: 应用程序池启动32位应用程序 设置托管管道为集成 (仍然有问题) 图片:123.png 访问网站之前 应用程序池是开启的 访问后 网页报503 service unavailable应用程序池 ...
- scjp考试准备 - 7 - Java构造器
题目——如下代码的执行结果: class Hello{ String title; int value; public Hello(){ title += " World!"; } ...
- Java 集合转换(数组、List、Set、Map相互转换)
转自:http://blog.csdn.net/top_code/article/details/10552827 package com.example.test; import java.util ...
- “我爱淘”冲刺阶段Scrum站立会议2
完成任务: 已经将菜单栏和首页精选页面整合在一起了,现在正在做第二个界面--分类. 计划任务: 将分类页面制作完毕,并可以整合到整体里,可以进行菜单点击的切换. 遇到问题: 1.分类页面布局的制作不好 ...
- Linux 下的类似Windows下Everything的搜索工具
Windows NTFS有个超级快的搜索工具Everything,非常好用,Linux下有几个类似的命令行工具,太难用了,推荐一个catfish,类似Everything,有GUI,可以自定义一个快捷 ...
- canvas实现跟随鼠标旋转的箭头
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta ht ...
- Unity3D中灵活绘制进度条
有时我们需要在Unity3D中绘制进度条,如: 或 如果使用4.6版本以下的unity绘制环形的进度条可能需要费点劲.我搜到的大多数方法都是用NGUI插件,但有时只是 ...
- Netsharp快速入门(之18) 平台常用功能(工作区相关)
作者:秋时 转载须说明出处 第6章 平台功能 6.1 部件二次开发设置 6.1.1 工具栏管理 1.从单据二次开发-工具栏管理进入 2.主要设置显示,对应的方法名或设置权限相关操作 ...
- 用Chrome devTools 调试Android手机app中的web页面。
(1) 手机要满足Android系统为4.4或更高版本,低版本不支持这种方式.(2) 确保App已经开启了webview的debug调试模式,由Android工程师协助.(2) 用usb数据线连接好手 ...
- Beta版软件说明书
软件使用说明书 一. 软件概述 本软件面向广大简易图片使用者,旨在为用户提供简单方便的不对其他软件产生依赖的截图软件,可以脱机使用. 二. 运行环境 个人电脑,Windows7操作系统,. ...