protobuf在java应用中通过反射动态创建对象

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

---恢复内容开始---

最近编写一个游戏用到protobuf数据格式进行前后台传输,苦于protobuf接受客户端的数据时是需要数据类型的如xxx.parseForm(...),这样就要求服务器在接受客户端请求时必须知道客户端传递的数据类型。由于客户端的请求数据是多种多样的,服务器端又不知道客户端的请求到底是哪个类型,这样就使得服务器端编程带来很多麻烦,甚至寸步难行。难道就没有解决办法了吗,答案当然是有的。下面就说一下常用的方法。(在看本文之前建议先了解protobuf的一些基本语法,和基本用法)

1.第一种方法也是最简单的方法,就是在整个应用程序中只定义一个proto文件,那么所有的请求都是一种类型,那么服务器端就不用苦恼怎么解析请求数据了,因为不管哪个请求数据都用同一个对象解析。如下面的列子:

首先贴一个PBMessage.proto文件

//客户端请求以及服务端响应数据协议 option java_outer_classname = "PBMessageProto";

package com.ppsea.message; import "main/resources/message/DataMsg.proto";

message PBMessage{ optional int32 playerId = 1; //玩家id required int32 actionCode = 2; //操作码id optional bytes data = 5; //提交或响应的数据 optional DataMsg dataMsg = 6; //服务器端推送数据 optional string sessionKey = 7; //请求的校验码 optional int32 sessionId = 8;//当前请求的标示 } 如上述代码,整个应用都基于PBMessage.proto传输,注意到protobuf 语法中 optional修饰符,他表示这个字段是非必须的,也就是说对于客户端的不同请求,只需要为它填充其请求时用到的字段的值即可,其他的字段的值就不用管了,这样就可以模拟出各种请求来,那么接下来我们就用: PBMessage.parseForm(byte_PBMesage) // byte_PBMesage表示客户端请求数据 ,这样请求的解析就完成了。同时我们注意到: (required int32 actionCode = 2; // 操作码id) ,required表示该字段是必须的,前面请求已经解析好了,在这里我们拿到 actionCode 就可以知道我们该用哪个Action事件来处理该请求了(前提是必须维护一张actionCode到Action的映射关系表:Map<int,Action>),至此整个请求的解析和处理都完成了。

接下来说一下第一种方式的优缺点,优点:整个应用消息格式一致统一,操作简单。缺点:太统一,就不灵活,对于请求很少,消息格式很少的小型应用倒还勉强能用,消息格式多的话再用这种方式就显得臃肿,不便于管理,失去了程序设计的意义。

2.第二种方法,也是我本次用到的方法。苦于提议中方式的局限性,本人通过在网上收集资料以及查看protobuf java版的源码,发现了一个折中的方式。首先我们看下protobuf源码中提供的, DynamicMessage 类(顾名思义 动态消息类,眼前一亮有木有),它继承了AbstractMessage类,比较一下和第一种方式创建的PBMessage类的区别 我们发现PBMessage类继承了GeneratedMessage类,而GeneratedMessage类继承了AbstractMessage类,至此我们发现了共同类AbstractMessage,再次证明了DynamicMessage 管用,同时我们再看看AbstractParser<MessageType>类(在PBMessage类中持有AbstractParser类的对象,并用其来解析请求数据),它继承了Parser<MessageType>接口,看看其部分方法:

public abstract MessageType parseFrom(byte[] paramArrayOfByte) throws InvalidProtocolBufferException;

public abstract MessageType parseFrom(InputStream paramInputStream) throws InvalidProtocolBufferException;

public abstract MessageType parseFrom(InputStream paramInputStream,ExtensionRegistryLite paramExtensionRegistryLite) throws InvalidProtocolBufferException; ,再看看DynamicMessage 里面提供的方法:

public static DynamicMessage parseFrom(Descriptors.Descriptor type,byte[] data) throws InvalidProtocolBufferException {

  return ((Builder) newBuilder(type).mergeFrom(data)).buildParsed(); 

}

public static DynamicMessage parseFrom(Descriptors.Descriptor type,byte[] data, ExtensionRegistry extensionRegistry)throws InvalidProtocolBufferException { return ((Builder) newBuilder(type).mergeFrom(data, extensionRegistry)).buildParsed(); }

public static DynamicMessage parseFrom(Descriptors.Descriptor type,InputStream input) throws IOException {

return ((Builder) newBuilder(type).mergeFrom(input)).buildParsed(); 

}

public static DynamicMessage parseFrom(Descriptors.Descriptor type,InputStream input, ExtensionRegistry extensionRegistry)throws IOException { return ((Builder) newBuilder(type).mergeFrom(input, extensionRegistry)).buildParsed(); }

发现了他们方法的相似点,在这里我们可以用一个等量关系比喻:DynamicMessage=AbstractMessage+ AbstractParser=PBMessage,也就是说DynamicMessage继承AbstractMessage(请求消息对象)的同时又间接实现了AbstractParser(请求消息数据解析)对数据解析的功能。现在我们唯一缺少的就是 Descriptors.Descriptor(对消息的描述)对象,这个对象该怎么拿到呢,在这里肯定的说,对于不同的.proto请求这里的Descriptors.Descriptor是不一样的。在这里我们又回到PBMessage对象中,我们发现了其中有这样一个方法:

public final class PBMessageProto {

..................//此处省略若干行public static final class PBMessage extendscom.google.protobuf.GeneratedMessage implements PBMessageOrBuilder {

...........//此处省略若干行 public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { return com.ppsea.message.PBMessageProto.internal_static_com_ppsea_message_PBMessage_descriptor; } } } 这不就是我们苦苦寻找的东西吗,通过这个方法就可以拿到Descriptor了,不是吗。在这里重点来了,再来理解一下,首先有了 PBMessage对象(这里用其来做代表,可以使其他的.proto对象)就可以获得 Descriptors.Descriptor 对象,有了Descriptors.Descriptor对象就可以创建DynamicMessage对象了,有了DynamicMessage就可以解析对应请求了。下面看代码:

//存放消息操作码和消息对象 Map<Integer,Descriptor> descriptorMap=new Map<Integer,Descriptor>; //把消息描述对象添加进来 descriptorMap.add(100,PBMessage.getDescriptor()); descriptorMap.add(xxx,xxx); 这样Descriptor有了,其实还可以做得更好一点,通过反射机制,Map里面只存放操作码和对应的proto对象类名,再通过反射方式创建proto对象在获得其getDescriptor()方法。这样就可以在配置文件中配置操作码和proto对象的关系了。

好接下来我们来接受客户端的请求试一下,

//客户端伪代码 client.send(byte_PBMessage); 然后服务器接受请求并解析,

//服务器伪代码

byte[] date=server.accept();

//客户端操作码 int actionCode;

Descriptor descriptor=descriptorMap.get(actionCode);

//解析请求 DynamicMessage req=DynamicMessage.parseFrom(descriptor, date); 在这里我们发现似乎还少了点什么,好像 actionCode还不知道,怎么办呢,好吧,我们在客户端发送的请求消息头上再加上个actionCode,即把操作码和proto消息合并为一个新的请求发送给客户端,请求2位为操作码,那么现在客户端应该这么发送消息了:

//客户端伪代码 short actionCode=100;

//两个字节来存放actionCode byte [] actionCodeByte=new byte [2];

// 转换成字节流 actionCodeByte.set(actionCode.toByteArray());//伪代码,请勿当真

//带请求头的消息的总长度 int length=actionCodeByte.length+byte_PBMEssage.length;

byte [] messageByte=new byte[length];

//把操作码和proto消息合并 messageByte=actionCodeByte+byte_PBMEssage;

client.send(messageByte); 下来是服务器了:

//服务器伪代码 byte[] data=server.accept(); //把前两位取出来 byte[] actionCodeByte=data.read(0,2);

// actionCode有了 int actionCode=actionCodeByte.readShort();

// 取出proto消息 byte[] byte_PBMessage=data.read(2,data.length); .....接下来就和前面的服务器伪代码一样了 DynamicMessage req=DynamicMessage.parseFrom(descriptorMap.get(actionCode, byte_PBMessage); .... 至此动态创建对象完成了,接下来就是按照第一种方式维护的ActionMap通过actionCode取到action来处理DynamicMessage 解析好的请求了

,当然actionCode也可以换成actionName,类似的。到这里似乎差不多了,当时始终不完美,因为我们还没有把DynamicMessage 转换成PBMessage对象,在后续的action里处理DynamicMessage总是不舒服,解决办法是通过DynamicMessage对象获得Descriptor对象,在获得其所有字段名和值, 然后看一下这个地址的这篇文章(通过字段反射对象部分):http://liufei-fir.iteye.com/blog/1160700,通过反射来还原PBMessage,以上是经过试验成功的,由于时间原因就不把源码贴上来了。有什么问题希望大家指正。

转载于:https://my.oschina.net/u/257088/blog/277461

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/299635.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

黑科技轮胎:有能发电的,脑洞简直不要太大...

全世界只有3.14 % 的人关注了青少年数学之旅人类历史上的很多伟大发明&#xff0c;都是由脑洞产生的&#xff0c;这样那样&#xff0c;然后问题就解决了&#xff0c;过程很复杂&#xff0c;却又很简单&#xff0c;甚至有时候&#xff0c;是一种很奇葩的方式。在漫长的历史进程中…

VS 2019 16.11正式发布 | 新功能(Hot Reload 热重载)试用

VS 2019 16.11VS 2019 16.11已于2021.8.10正式发布。&#xff08;https://devblogs.microsoft.com/visualstudio/visual-studio-16-11/&#xff09;这个版本主要包括以下内容&#xff1a;Visual Studio中的Git工具体验改进支持.NET应用程序的Hot Reload&#xff08;热重载&…

测试String, StringBuilder,StringBuffer 的数据,我居然发现这些了~~

懒的搞什么开头了&#xff0c;直接撸代码吧 想了想还是给大家看看学姐照片吧&#xff0c;保持眼睛的明亮&#xff0c;代码敲多了&#xff0c;伤眼 1&#xff1a; 作StringBuilder与String的拼接比较 Test public void testString () {String s"";long begin S…

厉害了!这里藏着通关学霸的秘籍

全世界只有3.14 % 的人关注了青少年数学之旅在这个资讯丰富且易获取的时代&#xff0c;越来越多的人不愿意花时间阅读书籍&#xff0c;碎片化阅读成了主流。人们获取的东西多而杂&#xff0c;很难系统、全面。海量信息对人是冲击&#xff0c;更是诱惑。谁不想了解天下奇闻&…

『 编程思维』之我见

编程思维&#xff0c;对于一个开发人员来说是必备的&#xff0c;但凡能让应用跑起来&#xff0c;不管应用的大小&#xff0c;优劣&#xff0c;说明这个开发人员都具有编程思维&#xff0c;毕竟程序认可了这个开发人员逻辑&#xff0c;能启动起来。小到几行代码&#xff0c;一个…

史上最全图详解Jvm—诊断工具和JVM监控

3.1. 工具概述 使用上一章命令行工具或组合能帮您获取目标Java应用性能相关的基础信息&#xff0c;但它们存在下列局限&#xff1a; 1&#xff0e;无法获取方法级别的分析数据&#xff0c;如方法间的调用关系、各方法的调用次数和调用时间等&#xff08;这对定位应用性能瓶颈至…

去医院看病如何开开心心出来? | 今日最佳

全世界只有3.14 % 的人关注了青少年数学之旅&#xff08;图源百度弱智吧&#xff0c;侵权删&#xff09;

玩机器学习,能不知道它?

推荐一个端对端的开源机器学习平台编程导航 致力于推荐优质编程资源 ????项目开源仓库&#xff1a;https://github.com/liyupi/code-nav跪求一个 star ⭐️大家好&#xff0c;我是编程导航的小编 Made 。今天安利一个强大的开源机器学习平台—TensorFlow。TensorFlow 是一个…

那些读了硕士博士的人,最不想让你知道的是什么?

全世界只有3.14 % 的人关注了青少年数学之旅在这个资讯丰富且易获取的时代&#xff0c;越来越多的人不愿意花时间阅读书籍&#xff0c;碎片化阅读成了主流。人们获取的东西多而杂&#xff0c;很难系统、全面。海量信息对人是冲击&#xff0c;更是诱惑。谁不想了解天下奇闻&…

超详细图解!【MySQL进阶篇】MySQL事务和锁

ACID 特性 在关系型数据库管理系统中&#xff0c;一个逻辑工作单元要成为事务&#xff0c;必须满足这 4 个特性&#xff0c;即所谓的 ACID&#xff1a; 原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔离性&#xff08;Isolation&am…

这道题号称无人能解!300多年来无一人答对,却让这群人这么简单就解出来了?...

全世界只有3.14 % 的人关注了青少年数学之旅最近&#xff0c;一条新闻引起了超模君的注意&#xff1a;“三体问题”或有解了&#xff01;这个蔑视了人类300多年的老顽固&#xff0c;真的要被彻底解决了吗&#xff1f;三体问题到底是什么&#xff1f;三体问题是说&#xff1a;三…

超详细图解!【MySQL进阶篇】MySQL架构原理

MySQL体系架构 MySQL Server架构自顶向下大致可以分网络连接层、服务层、存储引擎层和系统文件层。 一、网络连接层 客户端连接器&#xff08;Client Connectors&#xff09;&#xff1a;提供与MySQL服务器建立的支持。目前几乎支持所有主流 的服务端编程技术&#xff0c;例如…

掉入黑洞会怎样?被拉成面条,还是前往另一个宇宙?

全世界只有3.14 % 的人关注了青少年数学之旅○ 黑洞通往何处&#xff1f;现在&#xff0c;你准备好要跳入一个黑洞。如果你能想办法活下来&#xff08;尽管这困难重重&#xff09;&#xff0c;等待着你的是什么呢&#xff1f;如果你想方设法地要回头&#xff0c;最终你会去到哪…

反射学习系列3-反射实例应用

反射学习系列目录 反射学习系列1-反射入门 反射学习系列2-特性&#xff08;Attribute&#xff09; 反射学习系列3-反射实例应用 作者 例子这个东西其实挺难弄得,弄个简单的,虽然能说明问题但却容易让人觉得没实用价值,弄个有实用价值却又往往牵扯很多别的技术甚至牵扯很多业务…

怎么向女朋友解释什么叫区块链?

全世界只有3.14 % 的人关注了青少年数学之旅现在最火热的科技和风口&#xff0c;无疑就是“区块链”了。很多投行面试中也总是会被问到 于是&#xff0c;发生了下面的故事……有一对恩爱的男女朋友开始了这样的对话&#xff0c;我们暂且叫他们小明和小花吧。&#xff08;将就点…

遭遇价格欺诈

周末和朋友逛街时&#xff0c;买了副皮手套&#xff0c;店家说帮朋友代卖的&#xff0c;标价318元&#xff0c;打五折&#xff0c;又跟店家讲了下价&#xff0c;虽然店家表现的老大不情愿&#xff0c;但最终还是以130买进。 回家后顺手从网上查了下&#xff0c;淘宝网上才卖75&…

超详细图解!【MySQL进阶篇】MySQL索引原理

索引类型 索引可以提升查询速度&#xff0c;会影响where查询&#xff0c;以及order by排序。MySQL索引类型如下&#xff1a; 从索引存储结构划分&#xff1a;B Tree索引、Hash索引、FULLTEXT全文索引、R Tree索引 从应用层次划分&#xff1a;普通索引、唯一索引、主键索引、复…

ExtJs学习笔记(21)-使用XTemplate结合WCF显示数据

个人认为&#xff0c;XTemplate是ExtJs中最灵活的用来显示数据的组件&#xff0c;有点类似aspx中的Repeater控件&#xff0c;显示数据的模板完全可以由用户以html方式来定制. 先给一个官方的静态示例(稍微改了下)&#xff0c;代码并不复杂&#xff0c;关键的地方&#xff0c;我…

Blazor 路由及导航开发指南

翻译自 Waqas Anwar 2021年4月2日的文章 《A Developer’s Guide To Blazor Routing and Navigation》 [1]检查传入的请求 URL 并将它们导航到对应的视图或页面是每个单页应用程序 (SPA) 框架的基本功能。Blazor Server 和 WebAssembly 应用程序也同样支持使用一些内置组件和服…

超详细图解!【MySQL进阶篇】SQL优化-索引-存储引擎

1. Mysql的体系结构概览 整个MySQL Server由以下组成 Connection Pool : 连接池组件Management Services & Utilities : 管理服务和工具组件SQL Interface : SQL接口组件Parser : 查询分析器组件Optimizer : 优化器组件Caches & Buffers : 缓冲池组件Pluggable Storag…