从零开始手写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());@Autowiredprivate HandlerFactory handlerFactory;@Overridepublic void onConnected(ChannelHandlerContext ctx) {logger.info("建立连接");SessionManager.getInstance().create(ctx.channel());}@Overridepublic void onDisconnected(ChannelHandlerContext ctx) {logger.info("建立断开");SessionManager.getInstance().close(ctx.channel());}@Overridepublic void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {logger.warn("异常发生", throwable);}@Overridepublic 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 遍历methodsMethod[] 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);}@Overridepublic void onConnected(ChannelHandlerContext ctx) {}@Overridepublic void onDisconnected(ChannelHandlerContext ctx) {}@Overridepublic void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {}@Overridepublic 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 遍历methodsMethod[] 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上点个星星,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/691207.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

hive load data未正确读取到日期

1.源数据CSV文件日期字段值&#xff1a; 2.hive DDL语句&#xff1a; CREATE EXTERNAL TABLE test.textfile_table1(id int COMMENT ????, name string COMMENT ??, gender string COMMENT ??, birthday date COMMENT ????,.......) ROW FORMAT SERDE org.apache.…

wordpress企业网站模板免费

绿色风格的wordpress免费模板&#xff0c;经测试可以免费下载的WP模板。 https://www.wpniu.com/themes/300.html 简洁大气的文化艺术类wordpress模板&#xff0c;可以免费下载&#xff0c;实用易上手&#xff0c;新手也适合。 https://www.wpniu.com/themes/304.html 高端大…

Spring Security对接OIDC(OAuth2)外部认证

前后端分离项目对接OIDC(OAuth2)外部认证&#xff0c;认证服务器可以使用Keycloak。 后端已有用户管理和权限管理&#xff0c;需要外部认证服务器的用户名和业务系统的用户名一致才可以登录。 后台基于Spring Boot 2.7 Spring Security 流程&#xff1a; 前台浏览器跳转到…

C++面试宝典第30题:分发饼干

题目 假设你是一位非常棒的家长,想要给你的孩子们分发一些小饼干。但是,每个孩子最多只能给一块饼干。对每一个孩子i,都有一个胃口值gi,这是能让孩子们满足胃口的饼干的最小尺寸。对每一块饼干j,都有一个尺寸sj。如果sj >= gi,我们就可以将这个饼干j分配给孩子i,这个…

golangci-lint如何关闭typecheck

https://github.com/golangci/golangci-lint/issues/2912 typecheck是go源码的校验&#xff0c;无法通过.golangci.yml配置关闭 可以直接在golangci-lint源码层面关闭typecheck

Springboot AOP开发

Springboot AOP开发 一 AOP概述 AOP&#xff0c;即面向切面编程&#xff0c;简言之&#xff0c;面向方法编程。 针对方法&#xff0c;在方法的执行前或执行后使用&#xff0c;用于增强方法&#xff0c;或拓展。 二 AOP开发 1.引入 spring-boot-starter-aop 在SpringBoot项…

AWS Elastic Beanstalk通过应用负载均衡配置https

接上一篇&#xff0c;今天说说怎么通过AWS Elastic Beanstalk提供的应用负载均衡配置https。 首先创建应用和环境&#xff0c;这里应用可以使用上一篇文章中使用的demo应用&#xff08;只需要package.json和app.js文件&#xff09; 创建环境的时候&#xff0c;确认下面两个参…

【高效开发工具系列】PyCharm使用

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

自养号测评低成本高效率推广,安全可控

测评的作用在于让用户更真实、清晰、快捷地了解产品以及产品的使用方法和体验。通过买家对产品的测评&#xff0c;也可以帮助厂商和卖家优化产品缺陷&#xff0c;提高用户的使用体验。这进而帮助他们获得更好的销量&#xff0c;并更深入地了解市场需求。因此&#xff0c;测评在…

基于单片机的智能宠物喂食器设计

摘要:阐述智能宠物喂食器的实现方式,以STC89C52单片机为核心芯片,控制LCD的显示、语音芯片的启动和步进电机的运行。通过按键设置预设时间,当时间到达预设时间时,语音电路发出提示,步进电机工作,提供食物。此系统解决了主人由于各种原因不在家,使得宠物不能按时吃饭的问…

ACL 2024系统

文章目录 ACL官方网站信息通知ACL2024 官方模版 ACL官方网站信息通知 https://2024.aclweb.org/ ACL2024 官方模版 https://github.com/acl-org/acl-style-files

【Java中23种设计模式-单例模式2--懒汉式线程不安全】

加油&#xff0c;新时代打工人&#xff01; 今天&#xff0c;重新回顾一下设计模式&#xff0c;我们一起变强&#xff0c;变秃。哈哈。 23种设计模式定义介绍 Java中23种设计模式-单例模式 package mode;/*** author wenhao* date 2024/02/19 09:16* description 单例模式--懒…

.NET高级面试指南专题九【 泛型概念,常用泛型类和方法,泛型约束,协变与逆变】

C#中的泛型&#xff08;Generics&#xff09;是一种强大的编程特性&#xff0c;它允许你在编写代码时使用不特定数据类型&#xff0c;而在编译时确定这些数据类型。泛型使得代码更加灵活、可重用&#xff0c;并提高了类型安全性。 功能和原理 泛型允许你编写能够与不同数据类型…

数字孪生核心技术揭秘(五):BIM究竟是解药还是毒药?

一、关于BIM的那些幻想 1.1 BIM是“数字孪生城市”最后一块拼图&#xff1f; 近几年&#xff0c;CIM概念越来越流行&#xff0c;已经成为数字孪生城市的主流数据模型。CIM原始概念指的是“城市信息模型”&#xff1b;2015年同济大学吴志强院士基于CIM概念提出“城市智慧模型”&…

如何用ChatGPT绘图?

详情点查看公众号&#xff1a;技术科研吧 链接&#xff1a;如何用ChatGPT绘图&#xff1f; 一&#xff1a;AI领域最新技术 1.OpenAI新模型-GPT-5 2.谷歌新模型-Gemini Ultra 3.Meta新模型-LLama3 4.科大讯飞-星火认知 5.百度-文心一言 6.MoonshotAI-Kimi 7.智谱AI-GLM-…

23种设计模式-Golang(完整版)

23种设计模式-Golang 完整代码链接&#xff1a;https://github.com/ziyifast/easy_design_mode &#x1f680;&#xff1a;欢迎star哦&#xff5e; 1 创建型模式 1.1 简单工厂模式&#xff08;simple factory&#xff09;&#xff1a;不同协议有生成不同downloader ①解析 go…

ChatGPT在数据分析学习阶段的应用

ChatGPT在数据分析学习阶段的应用 ​ 这个阶段&#xff0c;核心是三件事&#xff1a;制定学习计划、确定学习资料以及学习策略。我们可以自己完成这几件事&#xff0c;当然也可以借助ChatGPT来高效地达到目的。 1.1 制定学习计划 ​ 学习阶段的第一件事是制定学习计划&#…

USACO 2024年1月铜组 MAJORITY OPINION

第一题&#xff1a;MAJORITY OPINION 标签&#xff1a;思维、模拟 题意&#xff1a;给定一个长度为 n n n的序列 a a a&#xff0c;操作&#xff1a;若区间 [ i , j ] [i,j] [i,j]内某个数字 k k k出现的次数 大于区间长度的一半&#xff0c;可以将区间内的所有数都换成这个数…

python之元类

最近刚好在准备面试&#xff0c;然后复习知识点。看了好多元类相关的文章&#xff0c;但还是心存疑惑&#x1f914;。 还是写点东西吧&#xff01; 元类&#xff0c;官方的定义是&#xff0c;类的类型。而类型的顶点&#xff0c;便是type metaclass - The class of a class.…

Java流程控制

1. 流程控制 在一个程序中&#xff0c;有很多的代码&#xff0c;代码的执行顺序&#xff0c;就是流程。 用于控制代码流程的方式&#xff0c;就是流程控制 流程控制的分类&#xff1a; 顺序结构&#xff1a;代码从上到下&#xff0c;从右到左依次执行。 分支结构&#xff1a…