从零开始手写mmo游戏从框架到爆炸(八)— byte数组传输

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

Netty帧解码器

        Netty中,提供了几个重要的可以直接使用的帧解码器。

LineBasedFrameDecoder

       行分割帧解码器。适用场景:每个上层数据包,使用换行符或者回车换行符做为边界分割符。发送端发送的时候,每个数据包之间以换行符/回车换行符作为分隔。在这种场景下,只需要把这个解码器加到 pipeline 中,Netty 会使用换行分隔符,把底层帧分割成一个一个完整的应用层数据包,发送到下一站。

FixedLengthFrameDecoder

        固定长度帧解码器。适用场景:每个上层数据包的长度,都是固定的,比如 100。在这种场景下,只需要把这个解码器加到 pipeline 中,Netty 会把底层帧,拆分成一个个长度为 100 的数据包 (ByteBuf),发送到下一个 channelHandler入站处理器。

DelimiterBasedFrameDecoder

        自定义分隔符帧解码器 。DelimiterBasedFrameDecoder 是LineBasedFrameDecoder的通用版本。不同之处在于,这个解码器,可以自定义分隔符,而不是局限于换行符。如果使用这个解码器,在发送的时候,末尾必须带上对应的分隔符。

LengthFieldBasedFrameDecoder

        自定义长度帧解码器。这是一种基于灵活长度的解码器。在数据包中,加了一个长度字段(长度域),保存上层包的长度。解码的时候,会按照这个长度,进行上层ByteBuf应用包的提取。

        我们之前使用的是自定义分隔符帧解码器,现在我们改用自定义长度帧解码器。LengthFieldBasedFrameDecoder有几个参数,我们看一起看一下:

  • lengthFieldOffset 长度域的偏移量,简单而言就是偏移几个字节是长度域
  • lengthFieldLength : 长度域的所占的字节数
  • lengthAdjustment : 长度值的调整值
  • initialBytesToStrip : 需要跳过的字节数

参考:LengthFieldBasedFrameDecoder 详解-CSDN博客  

现在我们就来修改编解码器,同时修改相关的类。

byte数组传输

        前两章我们已经完成了对消息体的封装和编解码器,然后我们来修改相关的代码,其实就是把原来string类型改为StringMessage类型

TcpMessageStringHandler.java


import com.loveprogrammer.base.network.listener.INetworkEventListener;
import com.loveprogrammer.base.network.support.SessionManager;
import com.loveprogrammer.pojo.StringMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

/**
 * @ClassName TcpMessageStringHandler
 * @Description tcp消息处理类
 * @Author admin
 * @Date 2024/2/4 15:16
 * @Version 1.0
 */
@Component
@Scope("prototype")
public class TcpMessageStringHandler extends SimpleChannelInboundHandler {
    private static final Logger logger = LoggerFactory.getLogger(TcpMessageStringHandler.class);

    @Autowired
    private INetworkEventListener listener;

//    public TcpMessageStringHandler(INetworkEventListener listener) {
//        this.listener = listener;
//    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
        listener.onExceptionCaught(ctx,throwable);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        super.channelRead(ctx, msg);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, StringMessage msg) {

        listener.channelRead(ctx,msg);
        // ctx.writeAndFlush(result);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        listener.onConnected(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        listener.onDisconnected(ctx);
    }
}

TcpServerStringInitializer.java

import com.loveprogrammer.base.factory.SpringContextHelper;
import com.loveprogrammer.base.network.listener.INetworkEventListener;
import com.loveprogrammer.base.network.support.NetworkListener;
import com.loveprogrammer.codec.MessageDecoder;
import com.loveprogrammer.codec.MessageEncoder;
import com.loveprogrammer.constants.ConstantValue;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;


public class TcpServerStringInitializer extends ChannelInitializer {

    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();
        // pipeline.addLast("framer",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast("decoder", new MessageEncoder());
        pipeline.addLast("encoder", new MessageDecoder(ConstantValue.MESSAGE_CODEC_MAX_FRAME_LENGTH,
                ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_OFFSET, ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_LENGTH,
                ConstantValue.MESSAGE_CODEC_LENGTH_ADJUSTMENT, ConstantValue.MESSAGE_CODEC_INITIAL_BYTES_TO_STRIP, false));
        TcpMessageStringHandler handler = (TcpMessageStringHandler) SpringContextHelper.getBean(TcpMessageStringHandler.class);
        pipeline.addLast(handler);
    }

}

INetworkEventListener.java

public interface INetworkEventListener {

    /**
     * 连接建立
     *
     * @param ctx ChannelHandlerContext
     */
    void onConnected(ChannelHandlerContext ctx);

    /**
     * 连接断开
     * * @param ctx ChannelHandlerContext
     */
    void onDisconnected(ChannelHandlerContext ctx);

    /**
     * 异常发生
     * * @param ctx ChannelHandlerContext
     * * @param throwable 异常
     */
    void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable);


    void channelRead(ChannelHandlerContext ctx, StringMessage msg);

}

NetworkListener.java

package com.loveprogrammer.base.network.support;

import com.loveprogrammer.base.bean.session.Session;
import com.loveprogrammer.base.network.listener.INetworkEventListener;
import com.loveprogrammer.constants.CommonValue;
import com.loveprogrammer.pojo.StringMessage;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class NetworkListener implements INetworkEventListener {

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

    @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) {
        logger.info("数据内容:data=" + msg.getBody());
        String result = "我是服务器,我收到了你的信息:" + msg.getBody();

        Session session = SessionManager.getInstance().getSessionByChannel(ctx.channel());
        result += ",sessionId = " + session.getId();

        StringMessage message = StringMessage.create(1,1);
        message.setStatusCode(CommonValue.MSG_STATUS_CODE_SUCCESS);
        message.setBody(result);

        SessionManager.getInstance().sendMessage(ctx.channel(),message);
    }
}

同样的client端也要跟着修改一下

SocketClient

public class SocketClient {

    private static final Logger logger = LoggerFactory.getLogger(SocketClient.class);
    private static final String IP = "127.0.0.1";
    private static final int PORT = 8088;

    private static EventLoopGroup group = new NioEventLoopGroup();

    protected static void run() throws InterruptedException {
//        Bootstrap bootstrap = new Bootstrap();
//        bootstrap.group(group);
//        bootstrap.channel(NioSocketChannel.class);
//        bootstrap.handler(new ChannelInitializer() {
//            protected void initChannel(Channel ch) throws Exception {
//                ChannelPipeline pipeline = ch.pipeline();
//                pipeline.addLast("framer",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
//                pipeline.addLast("decoder",new StringDecoder());
//                pipeline.addLast("encoder",new StringEncoder());
//                pipeline.addLast(new SocketClientHandler());
//            }
//        });
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group);
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.handler(new ChannelInitializer() {
            @Override
            protected void initChannel(Channel ch) {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast("encoder", new MessageEncoder());
                pipeline.addLast("decoder", new MessageDecoder(ConstantValue.MESSAGE_CODEC_MAX_FRAME_LENGTH,
                        ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_LENGTH, ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_OFFSET,
                        ConstantValue.MESSAGE_CODEC_LENGTH_ADJUSTMENT, ConstantValue.MESSAGE_CODEC_INITIAL_BYTES_TO_STRIP,
                        false));

                pipeline.addLast(new SocketClientHandler());
            }
        });

        // 连接服务器
        ChannelFuture channelFuture = bootstrap.connect(IP, PORT).sync();
        StringMessage msg = StringMessage.create(1,1);
        msg.setStatusCode(200);
        msg.setBody("你好,我是eric");
        // msg += "\r\n";
        channelFuture.channel().writeAndFlush(msg);
        logger.info("向服务器发送消息 {}",msg);
        channelFuture.channel().closeFuture().sync();

    }

    public static void main(String[] args) throws InterruptedException {
        logger.info("开始连接Socket服务器...");
        try {
            run();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }


}

SocketClientHandler.java


import com.loveprogrammer.pojo.StringMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @ClassName SocketClientHandler
 * @Description TODO
 * @Author admin
 * @Date 2024/1/29 17:41
 * @Version 1.0
 */
public class SocketClientHandler extends SimpleChannelInboundHandler {

    private static final Logger logger = LoggerFactory.getLogger(SocketClientHandler.class);
    @Override
    public void exceptionCaught(ChannelHandlerContext arg0, Throwable arg1) {
        logger.info("异常发生", arg1);
    }

    @Override
    public void channelRead(ChannelHandlerContext arg0, Object msg) throws Exception {
        super.channelRead(arg0, msg);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext context, StringMessage data) {
        logger.info("数据内容:data=" + data);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        StringMessage msg = StringMessage.create(1,1);
        msg.setStatusCode(202);
        msg.setBody("你好,我是eric");
        // msg += "\r\n";
        context.channel().writeAndFlush(msg);
        logger.info("向服务器发送消息 {}",msg);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.info("客户端连接建立");
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("客户端连接断开");
        super.channelInactive(ctx);
    }

}

全部源码详见:

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

分支:step-06

你可能感兴趣的:(从零开始MMO游戏,游戏,java,mmo,spring,boot,后端)