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

最近编写一个游戏用到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,以上是经过试验成功的,由于时间原因就不把源码贴上来了。有什么问题希望大家指正。

protobuf在java应用中通过反射动态创建对象(DynamicMessage)的更多相关文章

  1. Java学习:注解,反射,动态编译

    狂神声明 : 文章均为自己的学习笔记 , 转载一定注明出处 ; 编辑不易 , 防君子不防小人~共勉 ! Java学习:注解,反射,动态编译 Annotation 注解  什么是注解 ? Annotat ...

  2. C# 利用反射动态创建对象——带参数的构造函数和String类型

    C# 利用反射动态创建对象——带参数的构造函数和String类型 最近笔者有一个想法需要利用反射动态创建对象(如string,int,float,bool,以及自定义类等)来实现,一直感觉反射用不好, ...

  3. C#利用反射动态创建对象 带参数的构造函数和String类型 (转载)

    最近笔者有一个想法需要利用反射动态创建对象(如string,int,float,bool,以及自定义类等)来实现,一直感觉反射用不好,特别是当构造函数带参数的时候.MSDN上给出的例子十分复杂,网上的 ...

  4. C# 利用反射动态创建对象[摘录]

    摘自:http://hi.baidu.com/yangyuhang/blog/item/f12ea90e13f214e336d12250.html 在VS.Net中,有很多种方法动态调用对象的构造函数 ...

  5. 【转】C# 利用反射动态创建对象

    http://www.cnblogs.com/Jan_Dai/archive/2010/11/09/1872812.html Activator.CreateInstance(Type.GetType ...

  6. .Net 中的反射(动态创建类型实例) - Part.4

    动态创建对象 在前面节中,我们先了解了反射,然后利用反射查看了类型信息,并学习了如何创建自定义特性,并利用反射来遍历它.可以说,前面三节,我们学习的都是反射是什么,在接下来的章节中,我们将学习反射可以 ...

  7. .Net 中的反射(动态创建类型实例)

    动态创建对象 在前面节中,我们先了解了反射,然后利用反射查看了类型信息,并学习了如何创建自定义特性,并利用反射来遍历它.可以说,前面三节,我们学习的都是反射是什么,在接下来的章节中,我们将学习反射可以 ...

  8. C#回顾 - 8.利用反射动态创建对象

    拿微信消息返回的示例数据实验 var data = "<xml><ToUserName><![CDATA[toUser]]></ToUserName ...

  9. JAVA基础篇—接口实现动态创建对象

    Scanner在控制台输入内容 package com.Fruit; public interface Fruit {//提供接口 } package com.Fruit; public class ...

随机推荐

  1. Atitit.spring体系结构大总结

    Atitit.spring体系结构大总结 1. Srping mvc 1 2. Ioc 4 3. ApplicationContext在BeanFactory的基础上构建,区别 4 4. Aop 5 ...

  2. EF操作VS中

    1.安装mysql server 2.安装MySql的VS插件(版本请下载最新版,百度自己搜索,这个不用多说)mysql-for-visualstudio-1.2.3.msi 3.安装用于.net连接 ...

  3. [POJ 1236][IOI 1996]Network of Schools

    Description A number of schools are connected to a computer network. Agreements have been developed ...

  4. ENGINE_API CXSroll

    #ifndef __XSROLL_H__ #define __XSROLL_H__ #include "CocoHead.h" #include "XWindow.h&q ...

  5. windows phone 切换多语言时,商店标题显示错误的问题

    前段时间,用业余时间写了一款 wp8 app(“超级滤镜”商店,中文地址:英文地址),在多语言的时候,给 app title 和 app tile title 进行多语言时(参考 MSDN),中文商店 ...

  6. Eclipse中复制android项目后要改动的地方

    1.清单文件中,改package=2.修改包名3.清单文件中app_name F3点进去修改名字

  7. 搭建自己的GitHub Pages

    本文记录博主使用Win 10操作系统和Jekyll 3.1.2搭建GitHub Pages的过程.希望能帮助到相同有需要的朋友. 基本需求 GitHub账号及一个命名为{GitHub昵称}.githu ...

  8. Okra框架(二) 搭建Socket服务器

    本文将介绍使用Okra框架帮助开发者快速搭建高性能应用程序Socket服务端. 博主接触的网络游戏(包含但不限于网页, 手机)的服务端通信使用的协议基本上就Socket,Http或是WebSocket ...

  9. linphone 在am335x的编译过程

    环境变量: export PREFIX=/usr export HOSTTPL=arm-linux-gnueabihf export INSTALLDIR=/home/elinux/linphone/ ...

  10. shiro配置unauthorizedUrl,无权限抛出无权限异常,但是不跳转

    在使用shiro配置无授权信息的url的时候,发现这样的一个scenario,配置好unauthorizedUrl后仍然无法跳转,然后就在网上开始找,找了原因以及解决方案 原因,先post一个源码: ...