protobuf在java应⽤中通过反射动态创建对象
(DynamicMessage)
---恢复内容开始---
最近编写⼀个游戏⽤到protobuf数据格式进⾏前后台传输,苦于protobuf接受客户端的数据时是需要数据类型的如xxx.parseForm(...),这样就要求服务器在接受客户端请求时必须知道客户端传递的数据类型。由于客户端的请求数据是多种多样的,服务器端⼜不知道客户端的请求到底是哪个类型,这样就使得服务器端编程带来很多⿇烦,甚⾄⼨步难⾏。难道就没有解决办法了吗,答案当然是有的。下⾯就说⼀下常⽤的⽅法。(在看本⽂之前建议先了解protobuf的⼀些基本语法,和基本⽤法)
1.第⼀种⽅法也是最简单的⽅法,就是在整个应⽤程序中只定义⼀个proto⽂件,那么所有的请求都是⼀种类型,那么服务器端就不⽤苦恼怎么解析请求数据了,因为不管哪个请求数据都⽤同⼀个对象解析。如下⾯的列⼦:
⾸先贴⼀个PBMessage.proto⽂件
//客户端请求以及服务端响应数据协议
option java_outer_classname = "PBMessageProto";
package ssage;
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>类(在PBMessa
ge类中持有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();
}
java接口可以创建对象吗
发现了他们⽅法的相似点,在这⾥我们可以⽤⼀个等量关系⽐喻:DynamicMessage=AbstractMessage+ AbstractParser=PBMessage,也就是说DynamicMessage继承AbstractMessage(请求消息对象)的同时⼜间接实现了AbstractParser(请求消息数据解析)对数据解析的功能。现在我们唯⼀缺少的就是Descriptors.Descriptor(对消息的描述)对象,这个对象该怎么拿到呢,在这⾥肯定的说,对于不同的.proto 请求这⾥的Descriptors.Descriptor是不⼀样的。在这⾥我们⼜回到PBMessage对象中,我们发现了其中有这样⼀个⽅法:
public final class PBMessageProto {
..................//此处省略若⼲⾏
public static final class le.protobuf.GeneratedMessage implements PBMessageOrBuilder {
...........//此处省略若⼲⾏
public static le.protobuf.Descriptors.Descriptor getDescriptor() {
return ssage.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(Descriptor());
descriptorMap.add(xxx,xxx);
这样Descriptor有了,其实还可以做得更好⼀点,通过反射机制,Map⾥⾯只存放操作码和对应的proto对象类名,再通过反射⽅式创建proto 对象在获得其getDescriptor()⽅法。这样就可以在配置⽂件中配置操作码和proto对象的关系了。
好接下来我们来接受客户端的请求试⼀下,
//客户端伪代码
client.send(byte_PBMessage);
然后服务器接受请求并解析,
//服务器伪代码
byte[] date=server.accept();
//客户端操作码
int actionCode;
Descriptor (actionCode);
//解析请求
DynamicMessage req=DynamicMessage.parseFrom(descriptor, date);
在这⾥我们发现似乎还少了点什么,好像actionCode还不知道,怎么办呢,好吧,我们在客户端发送的请求消息头上再加上个actionCode,即把操作码和proto消息合并为⼀个新的请求发送给客户端,请求2位为操作码,那么现在客户端应该这么发送消息了:
//客户端伪代码
short actionCode=100;
//两个字节来存放actionCode
byte [] actionCodeByte=new byte [2];
// 转换成字节流
actionCodeByte.ByteArray());//伪代码,请勿当真
//带请求头的消息的总长度
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[] ad(0,2);
// actionCode有了
int adShort();
// 取出proto消息
byte[] byte_ad(2,data.length);
.....接下来就和前⾯的服务器伪代码⼀样了
DynamicMessage req=DynamicMessage.(actionCode, byte_PBMessage);
....
⾄此动态创建对象完成了,接下来就是按照第⼀种⽅式维护的ActionMap通过actionCode取到action来处理DynamicMessage 解析好的请求了
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论