需求背景

在接收到 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 的在线帮助 网页

一种自动反射消息类型的 Google Protobuf 网络传输方案

《玩转Protocol Buffers 》

《Google Protocol Buffer 的使用和原理》

Protobuf动态解析那些事儿的更多相关文章

  1. Protobuf动态解析在Java中的应用 包含例子程序

    最近在做ProtoBuf相关的项目,其中用到了动态解析,网上看了下相关资料和博文都比较少,自己来写一个记录一下学习过程.   Protocol Buffers是结构化数据格式标准,提供序列化和反序列方 ...

  2. 开源一个动态解析protobuf的工具

    好久没写博客了,主要是这一年技术没啥长进都打杂了,还有就是生活琐事越来越多,人也越来越懒了…… 之前项目中用到了Protobuf,然后测试发现这玩意不好测,总不能每次定个协议或者改下都要编译Java代 ...

  3. 使用Newtonsoft.Json.dll(JSON.NET)动态解析JSON、.net 的json的序列化与反序列化(一)

    在开发中,我非常喜欢动态语言和匿名对象带来的方便,JSON.NET具有动态序列化和反序列化任意JSON内容的能力,不必将它映射到具体的强类型对象,它可以处理不确定的类型(集合.字典.动态对象和匿名对象 ...

  4. 理解AngularJS生命周期:利用ng-repeat动态解析自定义directive

    ng-repeat是AngularJS中一个非常重要和有意思的directive,常见的用法之一是将某种自定义directive和ng-repeat一起使用,循环地来渲染开发者所需要的组件.比如现在有 ...

  5. jsoncpp动态解析节点类型

    在互联网无处不在的今天,JSON作为轻量级数据存储格式,被广泛应用到互联网数据传输中.众所周知,JSON由键/值对.对象.数组组成,其中键/值对的值包括以下几种类型: enum ValueType { ...

  6. 在C#中,Newtonsoft.Json + dynamic动态解析jsonString,jsonString转实体

    记录一下 引用 using Newtonsoft.Json; using Newtonsoft.Json.Linq; var jsonString = "{\"ApiResourc ...

  7. C#匿名类型和动态解析减少定义传输类模板

    C#作为强类型语言,在序列化和反序列化(json)场景中对字符串解析常常需要定义强类型模板,造成编码上的繁琐.其实可以使用匿名类型和动态解析减少json序列化时候的数据模板定义: string a = ...

  8. 域名动态解析到动态IP

    一般宽带用户的IP都是动态IP,重连之后IP可能会发生变化. 如果想在其他地方连接家里的设备,或者在家中搭建服务器,就会受到影响. 现在提供一种动态解析域名的方式,只要检测到IP的变化,那么就调用阿里 ...

  9. 使用 DNSPOD API 实现域名动态解析

    0. 简单概述在家里放一个 NAS 服务器,但是宽带的 IP 地址经常改变,一般路由器自带的花生壳域名解析可以解决,如果路由器没有类似功能或者想使用自己的域名,可以尝试使用 DNSPOD API 来实 ...

随机推荐

  1. extjs 表格可复制

    在GridPanel的配置项中,加入这个配置就可以了: viewConfig:{ enableTextSelection:true }

  2. linux下操作

    一.没有正确安装GNOME电源管理器的默认配置 二.oracle启停 1. linux下启动oraclesu - oraclesqlplus /nologconn /as sysdbastartupe ...

  3. 慎把“DataContext”静态化 或则单例

    之前在项目里由于把DataContext静态化,最后在测试阶段发现了很多奇怪的问题,后来经过同事的指点 然后上网搜了一翻终于发现 MSDN上说:   "请不要试图重用 DataContext ...

  4. homework-02 二维的,好喝的(二维数组的各种子数组)

    1)输入部分 对于输入部分,我定义的输入格式是这样的 前两行为列数和行数 如果文件无法打开,或者输入文件格式不对,均会提示出错并退出 2)二维数组的最大矩形子数组 首先,我使用最最简单的暴力算法,直接 ...

  5. 推荐一款系统软件:Unity tweak tool

    功能很多慢慢体会 在软件中心搜索unity tweak tool安装

  6. week 9 scenario testing

    1:How do you expect different personas to use your software?  What’s their need and their goals,  ho ...

  7. 安装配置OPENCMS的Replication cluster(从)详细过程

    1.  把opencms.war拷贝到tomcat下的webapps目录,启动tomcat服务. 2.  在安装之前,打开解压缩后的war包目录(tomcat启动后会自动把war包解开),删除目录 $ ...

  8. MyEclipse运行很慢的原因

    myEclipse以其丰富的功能博得程序员的热爱,但是其速度确实有问题,jsp文 件打开会不停的校验,甚至出现卡死,分析原因,原来是 validation在做怪. 好,既然找到了原因,那就把问题解决, ...

  9. 使用SecureRandom类替代Random类

    java.util.Random 产生确定的值可能被恶意的程序预测到. java.security.SecureRandom 产生不确定的随机数不能被预测到. 所以优先使用java.security. ...

  10. 初用Spring Test

    粗糙的研究了下Spring test,分享以下blog: 1. http://blog.csdn.net/shan9liang/article/details/40452469 2. http://w ...