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