手游服务端框架之使用控制器来处理玩家消息
经典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注解,带有该注解的类被标记为消息控制器
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注解,带有该注解的方法被标记为处理消息的业务映射
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),该类负责封装消息的执行者所需要的全部信息
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的绑定,消息的接收处理。
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); }
至此,整个消息控制器的接收与逻辑映射就完成了。
服务端与客户端程序入口
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.客户端程序入口,由于编写客户端界面比较麻烦,这里就用一些机器人模拟登录就好了
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类完成客户端链路的建立及收发消息
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); } } }
浙公网安备 33010602011771号