手游服务端框架之使用控制器来处理玩家消息

经典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);  
          
    }   

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

 

服务端与客户端程序入口

1.服务端入口,服务端仅需要完成各自模块的初始化,启动mina nioSocket进行监听
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);   
        }  
    }  
  
}  
3.程序运行结果(先启动服务端,再启动客户端)
 
a.服务端运行截图

b.客户端运行截图


本文主要讲述Mina socket服务端消息在业务上的流向,从中我们也可以看到,消息是在mina的io线程上进行处理的(服务io线程接收消息后直接处理)。如果业务执行非常耗时,就会影响消息的吞吐量。
文章预告:下一篇主要介绍利用独立线程池来异步处理玩家请求。
 
手游服务端开源框架系列完整的代码请移步github ->> jforgame

 

posted @ 2017-09-17 21:50  手游业务狗  阅读(92)  评论(0)    收藏  举报