导航:从零开始手写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上点个星星,谢谢!