手游服务端框架之模仿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
相关文章推荐
- springMVC请求处理流程及框架结构
- 手游服务端框架之配置与玩家数据库设计
- 游戏服务端线程池设计技巧——无锁处理玩家请求
- 手游服务端框架之配置与玩家数据库设计
- 【框架学习】SpringMVC请求处理
- 自己封装的Socket组件,实现服务端多进程共享Socket对象,协同处理客户端请求
- 自己封装的Socket组件,实现服务端多进程共享Socket对象,协同处理客户端请求
- 用.Net打造一个移动客户端(Android/IOS)的服务端框架NHM(二)——“请求”“交互”与传输数据(服务器端)
- 用.Net打造一个移动客户端(Android/IOS)的服务端框架NHM(五)——Android端消息处理机制
- HTTP协议处理框架 - 最简单的GET请求拼装和Response解析
- 当 jquery 发送 ajax 请求的时候遇到服务端session过期超时返回 302 跳转登陆页面的时候怎么办的处理方法
- ASP.NET MVC 框架处理请求生命周期
- SpringMVC Spring3 MVC 注解,注释 用@RequestMapping处理请求,多个请求,提交,.do,带参数,url重写
- SpringMVC对异常进行全局处理,并区分对待ajax和普通请求
- 【转】Ajax响应中文乱码 [SpringMVC使用@ResponseBody处理Ajax请求]
- javaweb开发中异步ajax请求之DWR框架详解(通过直接访问java类实现异步请求处理)
- 自己封装的Socket组件,实现服务端多进程共享Socket对象,协同处理客户端请求
- springMVC web请求处理流程
- (2010-08-24)Asp.net请求处理框架
- Ajax响应中文乱码 [SpringMVC使用@ResponseBody处理Ajax请求]