上一篇文章,我们重构了游戏的消息处理器,现在我们为了保持代码的整洁,重构编码器和解码器,大概思路与消息处理器的重构思路类似
消息编解码器的重构
1.增加消息识别器class:GameMsgRecognizer,用于可动态的获取消息对象和消息编号
package com.tk.tinygame.herostory;
import com.google.protobuf.GeneratedMessageV3;
import com.google.protobuf.Message;
import com.tk.tinygame.herostory.msg.GameMsgProtocol;
import java.util.HashMap;
import java.util.Map;
/**
* 消息识别器
*/
public final class GameMsgRecognizer {
/**
* 消息编号 -> 消息对象字典
*/
static private final Map<Integer, GeneratedMessageV3> _msgCodeAndMsgObjMap = new HashMap<>();
/**
* 消息类 -> 消息编号字典
*/
static private final Map<Class<?>, Integer> _clazzAndMsgCodeMap = new HashMap<>();
/**
* 私有化类默认构造器
*/
private GameMsgRecognizer() { }
/**
* 初始化
*/
static public void init() {
_msgCodeAndMsgObjMap.put(GameMsgProtocol.MsgCode.USER_ENTRY_CMD_VALUE, GameMsgProtocol.UserEntryCmd.getDefaultInstance());
_msgCodeAndMsgObjMap.put(GameMsgProtocol.MsgCode.WHO_ELSE_IS_HERE_CMD_VALUE, GameMsgProtocol.WhoElseIsHereCmd.getDefaultInstance());
_clazzAndMsgCodeMap.put(GameMsgProtocol.UserEntryResult.class, GameMsgProtocol.MsgCode.USER_ENTRY_RESULT_VALUE);
_clazzAndMsgCodeMap.put(GameMsgProtocol.WhoElseIsHereResult.class, GameMsgProtocol.MsgCode.WHO_ELSE_IS_HERE_RESULT_VALUE);
_clazzAndMsgCodeMap.put(GameMsgProtocol.UserQuitResult.class, GameMsgProtocol.MsgCode.USER_QUIT_RESULT_VALUE);
}
/**
* 根据消息编号获取消息构建器
*
* @param msgCode
* @return
*/
static public Message.Builder getBuilderByMsgCode(int msgCode) {
if (msgCode < 0) {
return null;
}
GeneratedMessageV3 defaultMsg = _msgCodeAndMsgObjMap.get(msgCode);
if (null == defaultMsg) {
return null;
} else {
return defaultMsg.newBuilderForType();
}
}
/**
* 根据消息类获取消息编号
*
* @param msgClazz
* @return
*/
static public int getMsgCodeByClazz(Class<?> msgClazz) {
if (null == msgClazz) {
return -1;
}
Integer msgCode = _clazzAndMsgCodeMap.get(msgClazz);
if (null == msgCode) {
return -1;
} else {
return msgCode.intValue();
}
}
}
2.在程序运行前初始化消息识别器
//初始化消息识别器
GameMsgRecognizer.init();
ServerMain完整代码:
package com.tk.tinygame.herostory;
import com.tk.tinygame.herostory.cmdhandler.CmdHandlerFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import org.apache.log4j.PropertyConfigurator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 服务器入口类
*/
public class ServerMain {
/**
* 日志对象
*/
static private final Logger LOGGER = LoggerFactory.getLogger(ServerMain.class);
/**
* 应用主函数
* @param args 参数数组
*/
public static void main(String[] args) {
// 设置 log4j 属性文件
PropertyConfigurator.configure(ServerMain.class.getClassLoader().getResourceAsStream("log4j.properties"));
// 初始化命令处理器工厂
CmdHandlerFactory.init();
//初始化消息识别器
GameMsgRecognizer.init();
EventLoopGroup bossGroup = new NioEventLoopGroup(); //拉客的group
EventLoopGroup workGroup = new NioEventLoopGroup(); //干活的group
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workGroup);
b.channel(NioServerSocketChannel.class); //服务器信道的处理方式
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
new HttpServerCodec(), // Http 服务器编解码器
new HttpObjectAggregator(65535), // 内容长度限制
new WebSocketServerProtocolHandler("/websocket"), // WebSocket 协议处理器, 在这里处理握手、ping、pong 等消息
new GameMsgDecoder(),
new GameMsgEncoder(),
new GameMsgHandler() // 自定义的消息处理器
);
}
});
b.option(ChannelOption.SO_BACKLOG,128);
b.childOption(ChannelOption.SO_KEEPALIVE,true);
try {
// 绑定 12345 端口,
// 注意: 实际项目中会使用 argArray 中的参数来指定端口号
ChannelFuture f = b.bind(12345).sync();
if (f.isSuccess()) {
LOGGER.info("服务器启动成功!");
}
// 等待服务器信道关闭,
// 也就是不要立即退出应用程序, 让应用程序可以一直提供服务
f.channel().closeFuture().sync();
} catch (Exception ex) {
//关闭服务器
workGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
LOGGER.error(ex.getMessage(),ex);
}
}
}
3.修改消息解码器GameMsgDecoder:
package com.tk.tinygame.herostory;
import com.google.protobuf.Message;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 自定义的消息解码器
*/
public class GameMsgDecoder extends ChannelInboundHandlerAdapter {
/**
* 日志对象
*/
static private final Logger LOGGER = LoggerFactory.getLogger(GameMsgDecoder.class);
/**
* @param ctx
* @param msg
* @throws Exception
* @deprecated 用于解码消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (null == ctx || null == msg) {
return;
}
if (!(msg instanceof BinaryWebSocketFrame)) {
return;
}
try {
/**
* 读取消息,获取消息长度、编号和消息体
*/
BinaryWebSocketFrame inputFrame = (BinaryWebSocketFrame)msg;
ByteBuf byteBuf = inputFrame.content();
byteBuf.readShort(); // 读取消息的长度
int msgCode = byteBuf.readShort(); // 读取消息编号
byte[] msgBody = new byte[byteBuf.readableBytes()]; //拿到消息体
byteBuf.readBytes(msgBody);
// 获取消息构建器
Message.Builder msgBuilder = GameMsgRecognizer.getBuilderByMsgCode(msgCode);
msgBuilder.clear();
msgBuilder.mergeFrom(msgBody);
//构建消息体
Message cmd = msgBuilder.build();
if (null != cmd) {
ctx.fireChannelRead(cmd);
}
}catch (Exception ex){
// 记录错误日志
LOGGER.error(ex.getMessage(),ex);
}
}
}
4.修改消息编码器GameMsgEncoder:
package com.tk.tinygame.herostory;
import com.google.protobuf.GeneratedMessageV3;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 游戏消息编码器
*/
public class GameMsgEncoder extends ChannelOutboundHandlerAdapter {
/**
* 日志对象
*/
static private final Logger LOGGER = LoggerFactory.getLogger(GameMsgEncoder.class);
/**
* @param ctx
* @param msg
* @param promise
* @throws Exception
* @deprecated 编码数据
*/
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (null == ctx || null == msg) {
return;
}
if (!(msg instanceof GeneratedMessageV3)) {
super.write(ctx, msg, promise);
return;
}
try{
// 消息编码
int msgCode = GameMsgRecognizer.getMsgCodeByClazz(msg.getClass());
if (-1 == msgCode) {
LOGGER.error(
"无法识别的消息类型, msgClazz = {}",
msg.getClass().getSimpleName()
);
super.write(ctx, msg, promise);
return;
}
// 消息体
byte[] msgBody = ((GeneratedMessageV3) msg).toByteArray();
ByteBuf byteBuf = ctx.alloc().buffer();
byteBuf.writeShort((short) msgBody.length); // 消息的长度
byteBuf.writeShort((short) msgCode); // 消息编号
byteBuf.writeBytes(msgBody); // 消息体
// 写出 ByteBuf
BinaryWebSocketFrame outputFrame = new BinaryWebSocketFrame(byteBuf);
super.write(ctx, outputFrame, promise);
}catch (Exception ex){
LOGGER.error(ex.getMessage(),ex);
}
}
}
5:测试的代码结构
测试代码:
测试地址:http://cdn0001.afrxvk.cn/hero_story/demo/step010/index.html?serverAddr=127.0.0.1:12345&userId=1
经测试后会发现,用户的入场消息,谁在场消息都会同之前一样的处理,说明我们重构的代码是可行的。