1.C-S服务器中网络指令的用途?
要讲清楚什么是分发器并且要明白为什么要在这个场合使用分发器,我们先要明白什么是C-S框架中的网络指令。
上图是C-S工作的基本模式,我们首先要明确服务器和客户端之间发送的信息仅仅是单纯的信息吗?答案是否定的。
他们发送的信息至少包括三个信息?
1.Command // 网络指令,这可以是一个枚举类
2.action //这个后面讲解分发器时会重点讲到
3.para //这个才是服务器与客户端之间发送的真正的信息
大概就是下图的这个样子:
switch(netConmand) {
case REQUEST_ONLINE:
if(!server.addConmunication(para, this)) {
send(new NetMessage().setNetMessage(ENetCommand.REFUSE_ONLINE).setPara("登陆过于频繁"));
close();
return;
}
this.id = para;
server.sendMessageToListener("客户端" + "[" + id + "]" + "成功上线");
send(new NetMessage().setNetMessage(ENetCommand.CONFIRM_ONLINE));
break;
case REQUEST_OFFLINE:
send(new NetMessage().setNetMessage(ENetCommand.CONFIRM_OFFLINE));
break;
case CONFIRM_OFFLINE:
server.removeCommunication(this.id);
server.sendMessageToListener("客户端" + "[" + id + "]" + "正常下线");
close();
break;
总体来说,不论是服务器还是客户端,会根据网络指令的不同自动执行不同的操作。比如上图中的REQUEST_OFFLINE(请求上线)下面就会执行关于这个网络指令相关的代码。
这就是网络指令
2.分发器在C-S框架中的作用:
我们客户端在完成登录等一系列操作时,需要向服务器请求资源,这里我们可以增加新的网络指令,并且使用switch case语句。但是这么做有一个大问题,如果你要进行的操作,没有相对应的网络指令,难道去改框架吗?就算有相对应的网络指令,switch case将写的又臭又长,因为一个客户端会多次向服务器发送不同的资源请求。
这时候就要提到上面没有说明的action,它既不是网络指令,也不是网络消息,它是一个专门为资源请求和完成分发器所必须的变量。程序会通过action的值找到与其方法注解内容相同的方法,并通过反射机制来执行它。这就完成了所谓的分发。
关于注解的基本使用请参见我的博客:注解的创建与使用
https://blog.youkuaiyun.com/Demon_T/article/details/100527526
3.完成分发器必须先要完成3个注解的定义与配置:
3.1三个注解的定义
3.1.1 Action注解用于标明具有ActionClass注解的类
package com.mec.csFarme.annotation;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(ElementType.METHOD)
public @interface Action {
String value();
}
3.1.2 ActionClass注解表示分发器需要执行的方法
package com.mec.csFarme.annotation;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(TYPE)
public @interface ActionClass {
}
3.1.3 Para注解是给参数进行注解,用于记载参数名
package com.mec.csFarme.annotation;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface Para {
String value();
}
3.2 注解的配置
package com.mec.demo.action;
import com.mec.annotation.action;
import com.mec.annotation.actionClass;
import com.mec.annotation.para;
@ActionClass
public class RequestAction {
@Action("login")
public void login(
@Para("id") String id,
@Para("password") String password) {
System.out.println("id " + id);
System.out.println("password " + password);
}
}
4.1分发器的运行机制
4.1.1解析注解,读取注解内容
ActionDefinition类:这个类中用来存储关于分发器要执行的一个方法的所有信息,有了它就能通过反射机制执行该方法
package com.mec.action;
import java.lang.reflect.Method;
public class ActionDefinition {
Class<?> klass; //该方法所在的类
Object object;// 该类所对应的对象
Method method;// 该方法对应的方法类
ActionDefinition() {
}
public Class<?> getKlass() {
return klass;
}
public void setKlass(Class<?> klass) {
this.klass = klass;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
return "ActionDefinition [klass=" + klass + ", object=" + object + ", method=" + method + "]";
}
}
ActionBeenFactory 类:这个类中通过包扫描到Action注解的类,并且找到拥有ActionClass注解的方法,最终生成一个ActionDefinition类型的对象,以ActionClass注解的内容为键,以ActionDefinition类型的对象为值,加入到一个HashMap中。
这个类中有用到包扫描:包扫描请见我的另一篇博客。
https://blog.youkuaiyun.com/Demon_T/article/details/102077250
package com.mec.action;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import com.mec.annotation.action;
import com.mec.annotation.actionClass;
import com.mec.util.PackageScanner;
public class ActionBeenFactory {
private static final Map<String, ActionDefinition> Actionmap;
static {
Actionmap = new HashMap<String, ActionDefinition>();
}
public void getActionDefinition(String packageName) {
new PackageScanner() {
@Override
public void dealClass(Class<?> klass) {
if (klass.isAnnotation()
|| klass.isEnum()
|| klass.isPrimitive()
|| klass.isInterface()
|| !klass.isAnnotationPresent(actionClass.class)) {
return;
}
try {
Object object = klass.newInstance();
Method[] methods = klass.getMethods();
for (Method method : methods) {
if (!method.isAnnotationPresent(Action.class)) {
continue;
}
ActionDefinition actionDefinition = new ActionDefinition();
actionDefinition.setKlass(klass);
actionDefinition.setMethod(method);
actionDefinition.setObject(object);
Action ac = method.getAnnotation(Action.class);
String methodName = ac.value();
Actionmap.put(methodName, actionDefinition);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}.packageScanner(packageName);
}
//从map中得到对应的ActionDefinition对象
public static ActionDefinition value(String action) {
return Actionmap.get(action);
}
}
ServerAction:这个类写的不完全,仅供参考
因为对于参数的处理要用Gson,这里目的就是要使用doAction()方法调用我们用配置过的方法,对于参数的处理比较麻烦,这里仅仅是说明了一下分发器的调用机制。对于参数的处理,如果有机会我会另写一篇去专门介绍。
总结一下 分发器的的处理机制
1.先用三个注解去配置你要分发器调用的方法,方法的参数,以及方法所在的类。
2.ActionBeenFactory类通过包扫描的手段得到拥有ActionClass注解的方法,以及它所在的类,最后生成一个ActionDefinition的对象,并把它加入到HashMap中。
3.最后调用doAction()方法把action 和 para这个两个参数传进来,通过action在HashMap中得到我们需要的ActionDefinition类的对象,取出里面的内容,这样我们就可以调用该方法了。
package com.mec.action;
mport java.lang.reflect.Method;
public class ServerAction {
public String doAction(String action, String para) {
//通过action得到ActionDefinition的对象
ActionDefinition actionDefinition = ActionBeenFactory.value(action);
//得到actionDefinition里面的Method对象
Method method = actionDefinition.getMethod();
Object object = actionDefinition.getObject();
Object result = null;
try {
//反射机制执行被ActionClass标记的方法
result = method.invoke(object);
} catch (Exception e) {
e.printStackTrace();
}
//
return result;
}
}