需求背景

在接收到 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. 使用AnkhSvn-2.5.12478.msi管理vs2013代码的工具安装步骤使用

    安装好AnkhSvn后,按照上面红色画出来的图,进行操作: 需要安装的文件有: AnkhSvn-2.5.12478.msi LanguagePack_1.8.5.25224-x64-zh_CN.msi ...

  2. wire与reg的区别?转载大神!

    本文转自:http://www.cnblogs.com/thymon/archive/2010/06/09/1754541.html //------------------------------- ...

  3. Crawling is going on - Alpha版本使用说明

    [Crawling is going on - Alpha版本] 使 用 说 明 北京航空航天大学计算机学院 远航1617 小组 产品版本:   Alpha版本 产品名称:Crawling   is ...

  4. 11.5Daily Scrum

    人员 任务分配完成情况 明天任务分配 王皓南 实现网页上视频浏览的功能.研究相关的代码和功能.811 数据库测试 申开亮 实现网页上视频浏览的功能.研究相关的代码和功能.812 实现视频浏览的功能 王 ...

  5. 四则运算2--设计思路--软件工程-c++

    1.题目避免重复. 2.可定制(数量 打印方式). 3.可以控制下列参数:是否有乘除法.是否有括号.数值范围.加减有无负数.除法有无余数.是否支持分数(真分数,假分数...).是否支持小数(精确到多少 ...

  6. 用hoverclock插件实现HTML5时钟

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http ...

  7. 自动回复消息-微信公众平台开发4(asp.net)

    接着上一节的processRequest 处理函数,代码如下: /// <summary>    /// 处理微信发来的请求     /// </summary>    /// ...

  8. Window.document对象(2)

    四.操作样式 首先利用元素的ID找到该元素,存于一个变量中: var a = document.getElementById("id"): 然后可以对该元素的属性进行操作: a.s ...

  9. 【BZOJ】【1449】【JSOI2009】球队收益

    网络流/费用流/二分图最小权匹配 题解:http://blog.csdn.net/huzecong/article/details/9119741 太神了!由于一赢一输不好建图,就先假设全部都输,再将 ...

  10. Hibernate O/R Mapping模拟

    作为SSH中的重要一环,有必要理解一下Hibernate对 O/R Mapping的实现. 主要利用java的反射机制来得到完整的SQL语句. 准备工作: 1. Object Student实体类: ...