CS-Framework框架action的处理
首先简单介绍一个概念
分发器:在服务端的通道栈之上,则是一个分发器(Dispatcher,或者说是调度器),它会首先对消息进行检查,然后选择一个客户端要调用的操作。
@在CS-Framework框架中,通过客户端Client向服务器端Server发送请求,服务器端响应请求并处理,然后将处理结果返回给发出请求的客户端。所以二者之间必然涉及到客户端以及服务端的消息通信及事件处理。
这里呢,我将消息构造成一个网络消息NetMessage类(成员包含ENetCommand类(枚举类型–消息类别)、action(请求动作)、para(参数),并且让客户端以及服务端之间的消息通信遵守一定的规范。
NetMessage类具体设计如下
package com.mec.cs_framework.core;
public class NetMessage {
private ENetCommand command;
private String action;
private String para;
NetMessage() {
}
NetMessage(String message) {
int dotIndex;
dotIndex = message.indexOf('.');
if (dotIndex < 0) {
return;
}
String str = message.substring(0, dotIndex);
this.command = ENetCommand.valueOf(str);
message = message.substring(dotIndex + 1);
dotIndex = message.indexOf('.');
if (dotIndex < 0) {
this.command = null;
return;
}
str = message.substring(0, dotIndex);
this.action = str.equals(" ") ? null : str;
message = message.substring(dotIndex + 1);
this.para = message;
}
ENetCommand getCommand() {
return command;
}
NetMessage setCommand(ENetCommand command) {
this.command = command;
return this;
}
String getAction() {
return action;
}
NetMessage setAction(String action) {
this.action = action;
return this;
}
String getPara() {
return para;
}
NetMessage setPara(String para) {
this.para = para;
return this;
}
@Override
public String toString() {
StringBuffer result = new StringBuffer(command.name());
result.append('.');
result.append(action == null ? " " : action).append('.');
result.append(para);
return result.toString();
}
}
ENetCommand类具体如下
package com.mec.cs_framework.core;
public enum ENetCommand {
OUT_OF_ROOM,
ID,
OFFLINE,
FORCE_DOWN,
TO_ONE,
TO_OTHER,
GONE,
REQUEST,
RESPONSE,
}
消息通信已经设计好,那么接下来考虑事件处理。对于不同的场景,客户端会调用不同的方法,如以下例子所示。
public void sendRequest(String action, String para) {
conversation.sendRequest(action + ":" + action, para);
}
public void sendRequest(String request, String response, String para) {
conversation.sendRequest(request + ":" + response, para);
}
public void toOne(String targetId, String message) {
// 通过调用conversation层所提供的相关功能实现
conversation.toOne(targetId, message);
}
public void toOther(String message) {
// 通过调用conversation层所提供的相关功能实现
conversation.toOther(message);
}
实际上客户端调用的方法里面,消息的发送都是由客户端会话层来实现的,因此会发送不同的NetMessage,如下图所示。
void sendRequest(String action, String para) {// 向服务器发送“请求”
send(new NetMessage()
.setCommand(ENetCommand.REQUEST)
.setAction(action)
.setPara(para));
}
void toOne(String targetId, String message) { //私聊
send(new NetMessage()
.setCommand(ENetCommand.TO_ONE)
.setAction(targetId)
.setPara(message));
}
void toOther(String message) {//群聊
send(new NetMessage()
.setCommand(ENetCommand.TO_OTHER)
.setPara(message));
}
服务器会话层ServerConversation接收到客户端会话层发送过来的NetMessage实例以后,就会处理NetMessage实例,一般的做法是通过switch语句来处理。如下图所示。
@Override
protected void dealNetMessage(NetMessage message) {
String para = message.getPara();
String action = message.getAction();
String resourceId = this.id;
String targetId = action;
switch (message.getCommand()) {
case OFFLINE:
server.removeConversation(id);
server.speakOut("客户端[" + id + "]下线!");
close();
break;
case TO_OTHER:
server.toOther(resourceId, para);
break;
case TO_ONE:
server.toOne(resourceId, targetId, para);
break;
case REQUEST:
break;
default:
break;
}
}
正如你所见的,通过switch的一般用法使功能及代码上有些混杂,而且我们每次新增处理方式都会造成函数的改动,很有可能造成不必要的麻烦;我们更希望,对NetMessage处理用对应的函数来代替,使得功能独立并且代码简洁。
比如当服务器会话层ServerConversation层接收到的NetMessage中的ENetCommand为OFFLINE时,服务器会话层ServerConversation层就会调用`dealOffline方法处理。
public void dealOffline(NetMessage message) {
server.removeConversation(id);
server.speakOut("客户端[" + id + "]下线!");
close();
}
这种方法的效果是显而易见的,所以我们这里对调用的方法有个规范,对应着不同的NetMessage中的ENetCommand,命名相应的处理方法。
这里我构造了一个DealNetMessage类来实现,该类中包含了根据NetMessage中的ENetCommand,命名相应的处理方法的方法,也包含对该方法的反射执行。该类具体设计如下。
package com.mec.cs_framework.core;
import java.lang.reflect.Method;
public class DealNetMessage {
public DealNetMessage() {
}
private static String getMethodName(String command) {
StringBuffer result = new StringBuffer("deal");//StringBuffer处理字符串连接比String高效很多,并且线程安全。
String[] words = command.split("_");//以下划线为分隔符将字符串分为几个字符串数组
int wordCount = words.length;
for (int i = 0; i < wordCount; i++) {
result.append(words[i].substring(0, 0+1).toUpperCase());//deal后面首字母大写
result.append(words[i].substring(1).toLowerCase());
}
return result.toString();
}
public static void dealCommand(Object object, NetMessage message) {
Class<?> klass = object.getClass();
String methodName = getMethodName(message.getCommand().name());
Method method;
try {
method = klass.getMethod(methodName, NetMessage.class);//获取构造的相应方法名
method.invoke(object, message);//反射机制调用执行
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端会话层还是服务器端会话层都是继承通信层Communication而来的,在通信层中对NetMessage的处理是一个抽象方法,因此,无论是客户端会话层还是服务器端会话层在接收到NetMessage后都会实现该方法,具体怎样实现对应不同的需求具体设计。这里我对二者的实现方式都是一样的。
具体实现方法如以下dealNetMessage(NetMessage message),里面调用了DealNetMessage.dealCommand(this, message),反射执行服务端会话层或者客户端会话层的方法。
public void dealToOther(NetMessage message) {
server.toOther(this.id, message.getPara());
}
public void dealToOne(NetMessage message) {
server.toOne(this.id, message.getAction(), message.getPara());
}
public void dealRequest(NetMessage message) {
String action = message.getAction();
int index = action.indexOf(":");
String requestAction = action.substring(0, index);
String responseAction = action.substring(index + 1);
String para = message.getPara();
String result = null;
try {
result = actionExecuter.doRequest(requestAction, para);
} catch (Exception e) {
e.printStackTrace();
}
send(new NetMessage()
.setCommand(ENetCommand.RESPONSE)
.setAction(responseAction)
.setPara(result));
}
@Override
protected void dealNetMessage(NetMessage message) {
DealNetMessage.dealCommand(this, message);
}
自从通过消息类别来命名方法,再通过反射机制来执行方法后,再也不用写switch…case…了。
也许有人会问,通过这样的方式来执行NetMessage的处理方法会不会在时间和性能上造成损失?的确,由于反射机制自身的原因,会造成必要的性能损失,但这种方法带来的好处远比损失大的多。
总结
1.建立客户端与服务端的消息通信规范
2.建立通过消息类别来命名方法的规范,再通过反射机制来执行方法。
尚有不足之处,请多多指教!