从零开始手写mmo游戏从框架到爆炸(十七)— 完善后端报错与客户端显示

 导航:从零开始手写mmo游戏从框架到爆炸(零)—— 导航-CSDN博客 

        我们在前后端交互的请求体的父类中再增加三个字段,分别是失败跳转topic,失败跳转tag,失败原因。

eternity-common - RequestBase.java

package com.loveprogrammer.dto.base;

import java.io.Serializable;

/**
 * @ClassName RequestBase
 * @Description 请求基类
 * @Author admin
 * @Date 2024/2/19 10:35
 * @Version 1.0
 */
public class RequestBase implements Serializable {

    /**
     * 请求成功的路由topic
     */
    private int successCallbackTopic;

    /**
     * 请求成功的路由tag
     */
    private int successCallbackTag;

    /**
     * 请求失败的路由topic
     */
    private int errorCallbackTopic;

    /**
     * 请求失败的路由tag
     */
    private int errorCallbackTag;

    /***
     * 失败的文案
     */
    private String errorMsg;

    public int getSuccessCallbackTopic() {
        return successCallbackTopic;
    }

    public void setSuccessCallbackTopic(int successCallbackTopic) {
        this.successCallbackTopic = successCallbackTopic;
    }

    public int getSuccessCallbackTag() {
        return successCallbackTag;
    }

    public void setSuccessCallbackTag(int successCallbackTag) {
        this.successCallbackTag = successCallbackTag;
    }

    public int getErrorCallbackTopic() {
        return errorCallbackTopic;
    }

    public void setErrorCallbackTopic(int errorCallbackTopic) {
        this.errorCallbackTopic = errorCallbackTopic;
    }

    public int getErrorCallbackTag() {
        return errorCallbackTag;
    }

    public void setErrorCallbackTag(int errorCallbackTag) {
        this.errorCallbackTag = errorCallbackTag;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }
}

在服务端,对成功与失败请求结果进行封装。从代码中可以看到,我们先是统一了返回的参数,同时对入参进行了解析,如果指定了转发的topic与tag就使用客户端指定的,如果没有指定就使用默认的。这里封装的还是有点粗糙但是味道是有了。

 NetworkListener.java。

package com.loveprogrammer.base.network.support;

import com.alibaba.fastjson2.JSON;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.loveprogrammer.base.factory.HandlerFactory;
import com.loveprogrammer.base.factory.SpringContextHelper;
import com.loveprogrammer.command.anotation.TagListener;
import com.loveprogrammer.command.client.ClientLoginTag;
import com.loveprogrammer.command.client.ClientTopic;
import com.loveprogrammer.dto.base.RequestBase;
import com.loveprogrammer.listener.INetworkEventListener;
import com.loveprogrammer.constants.CommonValue;
import com.loveprogrammer.pojo.StringMessage;
import com.loveprogrammer.utils.ChannelUtils;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Component
public class NetworkListener implements INetworkEventListener {

    protected static final Logger logger = LoggerFactory.getLogger(NetworkListener.class);

    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
            1,
            10,
            0L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(20480),
            new ThreadFactoryBuilder()
                    .setNameFormat("worker-pool-%d").build(),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    @Autowired
    private HandlerFactory handlerFactory;


    @Override
    public void onConnected(ChannelHandlerContext ctx) {
        logger.info("建立连接");
        SessionManager.getInstance().create(ctx.channel());
    }

    @Override
    public void onDisconnected(ChannelHandlerContext ctx) {
        logger.info("建立断开");
        SessionManager.getInstance().close(ctx.channel());
    }

    @Override
    public void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
        logger.warn("异常发生", throwable);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, StringMessage msg) {
        // 消息分发 对于消息的处理要增加多线程
        int topicId = msg.getTopicId();
        int tagId = msg.getTagId();

        Class handler = handlerFactory.handlerMap.get(topicId);
        if (handler == null) {
            logger.warn("未获取到指定的消息监听对象,topicId {}", topicId);
            return;
        }
        String bodyValue = msg.getBody();

        executor.execute(() -> {
            boolean isObject = false;
            Object bean = null;
            Object param = null;
            try {
                bean = SpringContextHelper.getBean(handler);
                // 找到tag 遍历methods
                Method[] methods = handler.getMethods();
                StringMessage result = null;
                for (Method method : methods) {
                    TagListener mqListener = method.getAnnotation(TagListener.class);
                    if (tagId == mqListener.tag()) {
                        Class<?> aClass = mqListener.messageClass();
                        String name = aClass.getName();
                        // 先处理基本类型
                        if ("java.lang.String".equals(name)) {
                            result = (StringMessage) method.invoke(bean, ctx, bodyValue);
                        } else if ("java.lang.Long".equals(name)) {
                            Long object = Long.parseLong(bodyValue);
                            result = (StringMessage)  method.invoke(bean, ctx, object);
                        } else if ("java.lang.Integer".equals(name)) {
                            Integer object = Integer.parseInt(bodyValue);
                            result = (StringMessage) method.invoke(bean, ctx, object);
                        } else if ("java.lang.Short".equals(name)) {
                            Short object = Short.parseShort(bodyValue);
                            result = (StringMessage) method.invoke(bean, ctx, object);
                        } else if ("java.lang.Byte".equals(name)) {
                            Byte object = Byte.parseByte(bodyValue);
                            result = (StringMessage)  method.invoke(bean, ctx, object);
                        } else if ("java.lang.Double".equals(name)) {
                            Double object = Double.parseDouble(bodyValue);
                            result = (StringMessage) method.invoke(bean, ctx, object);
                        } else if ("java.lang.Float".equals(name)) {
                            Float object = Float.parseFloat(bodyValue);
                            result = (StringMessage) method.invoke(bean, ctx, object);
                        }
                        // 转对象类型
                        else {
                            param = JSON.parseObject( bodyValue, aClass);
                            isObject = true;
                            result = (StringMessage) method.invoke(bean, ctx, param);
                        }
                        break;
                    }
                }
                if(result != null) {
                    ChannelUtils.pushToClient(ctx.channel(),result);
                }
                else{
                    logger.warn("未获取到指定的消息监听对象或者没有返回值,topicId {} tagId {}", topicId,tagId);
                }
            } catch (Exception e) {
                logger.error("发生异常", e);
                String result;
                if(e instanceof InvocationTargetException) {
                    InvocationTargetException ite = (InvocationTargetException) e;
                    Throwable exception = ite.getTargetException();
                    String message = exception.getMessage();
                    result = message;
                }else {
                    result = "异常:" + e.getMessage();
                }
                StringMessage message;
                if(isObject) {
                    RequestBase base = (RequestBase) param;
                    if(base.getErrorCallbackTopic() > 0 && base.getErrorCallbackTag() > 0) {
                        message = StringMessage.create(base.getErrorCallbackTopic(), base.getErrorCallbackTag());
                        base.setErrorMsg(result);
                        message.setBody(JSON.toJSONString(base));
                    }
                    else {
                        message = StringMessage.create(ClientTopic.TOPIC_LOGIN, ClientLoginTag.TAG_LOGIN_REGISTER);
                        message.setBody(result);
                    }
                }else{
                    message = StringMessage.create(ClientTopic.TOPIC_LOGIN, ClientLoginTag.TAG_LOGIN_REGISTER);
                    message.setBody(result);
                }
                message.setStatusCode(CommonValue.MSG_STATUS_CODE_FAIL);
                ChannelUtils.pushToClient(ctx.channel(), message);
            }
        });

    }
}

修改所有的handler返回值都是 StringMessage。这里大家去源码中查看即可。

客户端修改listener - NetworkClientListener.java。这里其实就是把回调的topic和tag写入进去。这里最好强制所有的请求体都是对象而不是基础类型。

package com.loveprogrammer.network;

import com.alibaba.fastjson2.JSON;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.loveprogrammer.command.anotation.TagListener;
import com.loveprogrammer.command.client.ClientMenuTag;
import com.loveprogrammer.command.client.ClientTopic;
import com.loveprogrammer.console.ConsolePrint;
import com.loveprogrammer.constants.CommonValue;
import com.loveprogrammer.dto.base.RequestBase;
import com.loveprogrammer.handler.HandlerFactory;
import com.loveprogrammer.listener.INetworkEventListener;
import com.loveprogrammer.pojo.StringMessage;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class NetworkClientListener implements INetworkEventListener {

    protected static final Logger logger = LoggerFactory.getLogger(NetworkClientListener.class);

    private NetworkClientListener(){

    }

    private static final NetworkClientListener instance = new NetworkClientListener();

    public static NetworkClientListener getInstance(){
        return instance;
    }


    private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2,
            2,
            0L,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(1024),
            new ThreadFactoryBuilder()
                    .setNameFormat("worker-pool-%d").build(),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    /***
     * 同客户端转发
     * @param ctx
     * @param topic
     * @param tag
     * @param msg
     */
    public void forward(ChannelHandlerContext ctx, int topic, int tag, String msg) {
        StringMessage data = new StringMessage();
        data.setTopicId(topic);
        data.setTagId(tag);
        data.setBody(msg);
        channelRead(ctx,data);
    }

    @Override
    public void onConnected(ChannelHandlerContext ctx) {

    }

    @Override
    public void onDisconnected(ChannelHandlerContext ctx) {

    }

    @Override
    public void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {

    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, StringMessage msg) {
        int topicId = msg.getTopicId();
        int tagId = msg.getTagId();
        int statusCode = msg.getStatusCode();

        Object handler = HandlerFactory.handlerMap.get(topicId);
        if (handler == null) {
            logger.warn("未获取到指定的消息监听对象,topicId {}", topicId);
            return;
        }
        String bodyValue = msg.getBody();
        executor.execute(() -> {
            try {
                Class<?> handlerClass = handler.getClass();
                // 找到tag 遍历methods
                Method[] methods = handlerClass.getMethods();
                for (Method method : methods) {
                    TagListener mqListener = method.getAnnotation(TagListener.class);
                    if (tagId == mqListener.tag()) {
                        Class<?> aClass = mqListener.messageClass();
                        String name = aClass.getName();
                        // 先处理基本类型
                        if ("java.lang.String".equals(name)) {
                            method.invoke(handler, ctx, bodyValue);
                        } else if ("java.lang.Long".equals(name)) {
                            Long object = Long.parseLong(bodyValue);
                            method.invoke(handler, ctx, object);
                        } else if ("java.lang.Integer".equals(name)) {
                            Integer object = Integer.parseInt(bodyValue);
                            method.invoke(handler, ctx, object);
                        } else if ("java.lang.Short".equals(name)) {
                            Short object = Short.parseShort(bodyValue);
                            method.invoke(handler, ctx, object);
                        } else if ("java.lang.Byte".equals(name)) {
                            Byte object = Byte.parseByte(bodyValue);
                            method.invoke(handler, ctx, object);
                        } else if ("java.lang.Double".equals(name)) {
                            Double object = Double.parseDouble(bodyValue);
                            method.invoke(handler, ctx, object);
                        } else if ("java.lang.Float".equals(name)) {
                            Float object = Float.parseFloat(bodyValue);
                            method.invoke(handler, ctx, object);
                        }
                        // 转对象类型
                        else {
                            Object object = JSON.parseObject(bodyValue, aClass);
                            if(statusCode != CommonValue.MSG_STATUS_CODE_SUCCESS) {
                                RequestBase base = (RequestBase) object;
                                ConsolePrint.publishMessage(base.getErrorMsg());
                            }
                            method.invoke(handler, ctx, object);
                        }
                        break;
                    }
                }
            } catch (Exception e) {
                logger.error("发生异常", e);
                // 转发到首页
                forward(ctx, ClientTopic.TOPIC_MENU, ClientMenuTag.TAG_MENU_PORTAL, msg.getBody());
            }
        });

    }
}

客户端修改handler - LoginHandler

package com.loveprogrammer.handler.support;

import com.alibaba.fastjson2.JSON;
import com.loveprogrammer.command.BaseHandler;
import com.loveprogrammer.command.anotation.TagListener;
import com.loveprogrammer.command.anotation.TopicListener;
import com.loveprogrammer.command.client.ClientLoginTag;
import com.loveprogrammer.command.client.ClientMenuTag;
import com.loveprogrammer.command.client.ClientTopic;
import com.loveprogrammer.command.server.LogAndRegisterTag;
import com.loveprogrammer.command.server.ServerTopic;
import com.loveprogrammer.console.ConsolePrint;
import com.loveprogrammer.constants.CommonValue;
import com.loveprogrammer.dto.base.StringRequest;
import com.loveprogrammer.dto.login.UserLogin;
import com.loveprogrammer.dto.login.UserRegister;
import com.loveprogrammer.network.NetworkClientListener;
import com.loveprogrammer.pojo.StringMessage;
import com.loveprogrammer.utils.ChannelUtils;
import com.loveprogrammer.utils.ScannerInput;
import io.netty.channel.ChannelHandlerContext;
import org.apache.logging.log4j.core.jmx.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Scanner;

/**
 * @ClassName MenuHandler
 * @Description TODO
 * @Author admin
 * @Date 2024/2/18 17:37
 * @Version 1.0
 */
@TopicListener(topic = ClientTopic.TOPIC_LOGIN)
public class LoginHandler extends BaseHandler {

    public static final Logger log = LoggerFactory.getLogger(LoginHandler.class);

    @TagListener(tag = ClientLoginTag.TAG_LOGIN_REGISTER, messageClass = StringRequest.class)
    public void LoginMenu(ChannelHandlerContext ctx, StringRequest msg) {
        // 展示首页数据
        ConsolePrint.publishMessage("【1.登录已有账户】  【2.注册新账户】  【3.退出】");
        ConsolePrint.publishMessage("请选择:");

        int choose = ScannerInput.inputInt(1, 3, 3);

        if(choose != 3) {
        switch (choose) {
            case 1:
                ConsolePrint.publishMessage("请输入账户名:");
                String name = ScannerInput.inputString();
                ConsolePrint.publishMessage("请输入密码:");
                String password = ScannerInput.inputString();
                UserLogin userLogin = new UserLogin(ClientTopic.TOPIC_LOGIN, ClientLoginTag.TAG_LOGIN_REGISTER);
                userLogin.setUserName(name);
                userLogin.setPassword(password);
                StringMessage message = new StringMessage();
                message.setTopicId(ServerTopic.TOPIC_LOGIN);
                message.setTagId(LogAndRegisterTag.TAG_LOGIN_LOGIN);
                message.setStatusCode(CommonValue.MSG_STATUS_CODE_SUCCESS);
                message.setBody(JSON.toJSONString(userLogin));
                ChannelUtils.pushToServer(ctx.channel(), message);
                return;
            case 2:
                // 注册
                ConsolePrint.publishMessage("请输入注册的用户名");
                String registerName = ScannerInput.inputString();
                UserRegister register = new UserRegister(ClientTopic.TOPIC_LOGIN, ClientLoginTag.TAG_LOGIN_REGISTER);
                register.setName(registerName);
                StringMessage data = StringMessage.create(ServerTopic.TOPIC_LOGIN, LogAndRegisterTag.TAG_LOGIN_REGISTER);
                data.setStatusCode(CommonValue.MSG_STATUS_CODE_SUCCESS);
                data.setBody(JSON.toJSONString(register));
                ChannelUtils.pushToServer(ctx.channel(), data);
                return;
            default:
                ConsolePrint.publishMessage("暂未开放,敬请期待", 1);
                return;
        }}
        System.exit(0);
    }

}

其他的代码就不贴出来了,大家可以自行去对应的tag中查看。

最终,客户端的显示结果如下:

17:30:57.358 [nioEventLoopGroup-2-1] [INFO ] com.loveprogrammer.handler.HandlerFactory:32 --- 初始化消息监听器成功 com.loveprogrammer.handler.support.MenuHandler
【1.登录已有账户】  【2.注册新账户】  【3.退出】
请选择:
1
请输入账户名:
eric
请输入密码:
111
用户名密码不匹配
【1.登录已有账户】  【2.注册新账户】  【3.退出】
请选择:
1
请输入账户名:
eric
请输入密码:
111111
请选择您要进行的操作
【1.打怪】  【2.装备】  【3.战兽】
【4.冒险者工会】   【5.副本】  【6.工会】 
【8.配置】  【9.退出】
请选择:

全部源码详见:

gitee : eternity-online: 多人在线mmo游戏 - Gitee.com

分支:step-10

请各位帅哥靓女帮忙去gitee上点个星星,谢谢!

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值