您的位置:首页 > 编程语言 > Java开发

手游服务端框架之模仿SpringMvc处理玩家请求

2017-07-02 14:53 477 查看

经典web项目的三层架构

经典web开发项目通常采用三层架构来组织代码。典型的,第一层为表现层,通常使用MVC模式;第二层为业务逻辑层,该层主要是各种service业务操作类;第三层则为数据访问层,通过dao层对数据表进行增删查改操作。

游戏项目的三层架构

类似的,我们的游戏项目也可以采用上面的三层架构。在命名方面,我们部分借鉴了SpringMvc的命名,使用Controller注解对应MVC模式的控制器,使用RequestMapper注解对应的消息处理者(类似于web的http url地址)。网关层收到玩家请求后,将消息分发到对应控制器的指定方法处理者。控制器只用于控制业务流程,具体的业务逻辑将交由业务逻辑层service(游戏项目习惯用Manager来命名)。

使用控制器处理对应业务模块的请求消息

从前面的Message抽象消息的定义可以看出,每一个请求消息包含有一个模块id,一个模块(一个相对独立的游戏功能模块)映射到唯一的控制器;每一个消息包含一个cmd类型,一个cmd类型就代表该功能模块一个子操作。
模块控制器与cmd业务处理method的一对多关系模型如下:



从上面的模型图可以看出,对于给定的模块号结合给定的cmd类型,可以找到唯一的Method与之对应。
好了,开始我们的coding之路吧。

消息控制器与业务流程映射

1. 申明Controller注解,带有该注解的类被标记为消息控制器
package com.kingston.net.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 控制器Controller
* 负责处理由MessageDispatcher分发的请求
*/
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {

}
2.申明RequestMapping注解,带有该注解的方法被标记为处理消息的业务映射package com.kingston.net.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 处理请求地址映射的注解
* @author kingston
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {

}
3. 定义消息执行单元(CmdExecutor.java),该类负责封装消息的执行者所需要的全部信息
package com.kingston.net.dispatch;

import java.lang.reflect.Method;

/**
* 消息执行单元封装
* @author kingston
*
*/
public class CmdExecutor {

/** 业务处理的工作方法 */
private Method method;
/** 传递给工作方法的相关参数 */
private Class<?>[] params;
/** 控制器实例 */
private Object handler;

public static CmdExecutor valueOf(Method method, Class<?>[] params, Object handler) {
CmdExecutor executor = new CmdExecutor();
executor.method = method;
executor.params = params;
executor.handler = handler;

return executor;
}

public Method getMethod() {
return method;
}

public Class<?>[] getParams() {
return params;
}

public Object getHandler() {
return handler;
}

}
4. 定义消息分发器,该分发器负责Controller的初始化,RequestMapper的绑定,消息的接收处理。
package com.kingston.net.dispatch;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.mina.core.session.IoSession;
import org.slf4j.Logger;

import com.kingston.logs.LoggerSystem;
import com.kingston.net.Message;
import com.kingston.net.SessionManager;
import com.kingston.net.annotation.Controller;
import com.kingston.net.annotation.Protocol;
import com.kingston.net.annotation.RequestMapping;
import com.kingston.utils.ClassFilter;
import com.kingston.utils.ClassScanner;

/**
* 消息分发器
*/
public class MessageDispatcher {

private final Logger logger = LoggerSystem.EXCEPTION.getLogger();

private volatile static MessageDispatcher instance;

/** [module_cmd, CmdExecutor] */
private static final Map<String, CmdExecutor> MODULE_CMD_HANDLERS = new HashMap<>();

public static MessageDispatcher getInstance() {
//双重检查锁单例
if (instance == null) {
synchronized (MessageDispatcher.class) {
if (instance == null) {
instance = new MessageDispatcher();
}
}
}
return instance;
}

private MessageDispatcher() {
initalize();
}

public void initalize() {
Set<Class<?>> controllers = ClassScanner.getClasses("com.kingston.game", new ClassFilter() {
@Override
public boolean accept(Class<?> clazz) {
return clazz.getAnnotation(Controller.class) != null;
}
});

for (Class<?> controller: controllers) {
try {
Object handler = controller.newInstance();
Method[] methods = controller.getDeclaredMethods();
for (Method method:methods) {
RequestMapping mapperAnnotation = method.getAnnotation(RequestMapping.class);
if (mapperAnnotation != null) {
MessageMeta meta = getMessageMeta(method);
if (meta == null) {
throw new RuntimeException(String.format("controller[%s]方法[%s]缺少RequestMapping注解",
controller.getName(), method.getName()));
}
short module = meta.module;
short cmd    = meta.cmd;
String key = buildKey(meta.module, meta.cmd);
CmdExecutor cmdExecutor = MODULE_CMD_HANDLERS.get(key);
if (cmdExecutor != null) {
throw new RuntimeException(String.format("module[%d] cmd[%d]重复", module, cmd));
}

cmdExecutor = CmdExecutor.valueOf(method, method.getParameterTypes(), handler);
MODULE_CMD_HANDLERS.put(key, cmdExecutor);
}
}
}catch(Exception e) {
logger.error("", e);
}
}
}

private MessageMeta getMessageMeta(Method method) {
for (Class<?> paramClazz: method.getParameterTypes()) {
if (Message.class.isAssignableFrom(paramClazz)) {
Protocol protocol = paramClazz.getAnnotation(Protocol.class);
if (protocol != null) {
return MessageMeta.valueOf(protocol.module(), protocol.cmd());
}
}
}
return null;
}

/**
* 向线程池分发消息
* @param session
* @param message
*/
public void dispatch(IoSession session, Message message) {
short module = message.getModule();
short cmd    = message.getCmd();

CmdExecutor cmdExecutor = MODULE_CMD_HANDLERS.get(buildKey(module, cmd));
if (cmdExecutor == null) {
logger.error("请求协议不存在,module=[%d],cmd=[%d]", module, cmd);
return;
}

Object[] params = convertToMethodParams(session, cmdExecutor.getParams(), message);
Object controller = cmdExecutor.getHandler();
try {
//通过反射,
cmdExecutor.getMethod().invoke(controller, params);
}catch(Exception e) {
logger.error("dispatch message failed", e);
}

}

/**
* 将各种参数转为被RequestMapper注解的方法的实参
* @param session
* @param methodParams
* @param message
* @return
*/
private Object[] convertToMethodParams(IoSession session, Class<?>[] methodParams, Message message) {
Object[] result = new Object[methodParams==null?0:methodParams.length];

for (int i=0;i<result.length;i++) {
Class<?> param = methodParams[i];
if (IoSession.class.isAssignableFrom(param)) {
result[i] = session;
}else if (Long.class.isAssignableFrom(param)) {
result[i] = SessionManager.INSTANCE.getPlayerId(session);
}else if (long.class.isAssignableFrom(param)) {
result[i] = SessionManager.INSTANCE.getPlayerId(session);
}else if (Message.class.isAssignableFrom(param)) {
result[i] = message;
}
}

return result;
}

private String buildKey(short module, short cmd) {
return module + "_" + cmd;
}

}
对上文的IoHandler的messageReceived()进行修改,让消息分发器处理消息
@Override
public void messageReceived(IoSession session, Object data ) throws Exception
{
Message message = (Message)data;
System.err.println("收到消息-->" + message);
//交由消息分发器处理
MessageDispatcher.getInstance().dispatch(session, message);

}
至此,整个消息控制器的接收与逻辑映射就完成了。

服务端与客户端程序入口

1.服务端入口,服务端仅需要完成各自模块的初始化,启动mina nioSocket进行监听
package com.kingston;

import com.kingston.logs.LoggerSystem;
import com.kingston.net.MessageFactory;
import com.kingston.net.SocketServer;

public class ServerStarter {

public static void main(String args[]) {
//初始化协议池
MessageFactory.INSTANCE.initMeesagePool();
//启动socket服务
try{
new SocketServer().start();
}catch(Exception e) {
LoggerSystem.EXCEPTION.getLogger().error("ServerStarter failed ", e);
}
}

}
2.客户端程序入口,由于编写客户端界面比较麻烦,这里就用一些机器人模拟登录就好了
package com.kingston;

import com.kingston.net.MessageFactory;
import com.kingston.robot.SocketRobot;

public class ClientStarter {

public static void main(String[] args) {
//初始化协议池
MessageFactory.INSTANCE.initMeesagePool();

SocketRobot robot = new SocketRobot("hello");
robot.buildConnection();
robot.sendMessage();
}

}
SocketRobot类完成客户端链路的建立及收发消息
package com.kingston.robot;

import java.net.InetSocketAddress;

import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

import com.kingston.game.login.message.ReqLoginMessage;
import com.kingston.net.codec.MessageCodecFactory;

public class SocketRobot {

private String name;

private IoSession session;

public SocketRobot(String name) {
this.name = name;
}

public void buildConnection() {
NioSocketConnector connector = new NioSocketConnector();
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(MessageCodecFactory.getInstance()));
connector.setHandler(new ClientHandler());

System.out.println("开始连接socket服务端");
ConnectFuture future = connector.connect(new InetSocketAddress(9527));

future.awaitUninterruptibly();

IoSession session = future.getSession();
this.session = session;

}

public void sendMessage() {
ReqLoginMessage message = new ReqLoginMessage();
message.setPassword("kingston");
message.setAccountId(123L);
this.session.write(message);
}

private class ClientHandler extends IoHandlerAdapter {
public void messageReceived(IoSession session, Object message) {
System.out.println("收到响应-->" + message);
}
}

}
3.程序运行结果(先启动服务端,再启动客户端)
a.服务端运行截图


b.客户端运行截图



本文主要讲述Mina socket服务端消息在业务上的流向,从中我们也可以看到,消息是在mina的io线程上进行处理的(服务io线程接收消息后直接处理)。如果业务执行非常耗时,就会影响消息的吞吐量。文章预告:下一篇主要介绍利用独立线程池来异步处理玩家请求。手游服务端开源框架系列完整的代码请移步github ->>game_server
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐