当前位置:   article > 正文

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

从零开始手写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

  1. import com.loveprogrammer.base.network.listener.INetworkEventListener;
  2. import com.loveprogrammer.base.network.support.SessionManager;
  3. import com.loveprogrammer.pojo.StringMessage;
  4. import io.netty.channel.ChannelHandlerContext;
  5. import io.netty.channel.SimpleChannelInboundHandler;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.context.annotation.Scope;
  10. import org.springframework.stereotype.Component;
  11. /**
  12. * @ClassName TcpMessageStringHandler
  13. * @Description tcp消息处理类
  14. * @Author admin
  15. * @Date 2024/2/4 15:16
  16. * @Version 1.0
  17. */
  18. @Component
  19. @Scope("prototype")
  20. public class TcpMessageStringHandler extends SimpleChannelInboundHandler<StringMessage> {
  21. private static final Logger logger = LoggerFactory.getLogger(TcpMessageStringHandler.class);
  22. @Autowired
  23. private INetworkEventListener listener;
  24. // public TcpMessageStringHandler(INetworkEventListener listener) {
  25. // this.listener = listener;
  26. // }
  27. @Override
  28. public void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
  29. listener.onExceptionCaught(ctx,throwable);
  30. }
  31. @Override
  32. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  33. super.channelRead(ctx, msg);
  34. }
  35. @Override
  36. protected void channelRead0(ChannelHandlerContext ctx, StringMessage msg) {
  37. listener.channelRead(ctx,msg);
  38. // ctx.writeAndFlush(result);
  39. }
  40. @Override
  41. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  42. listener.onConnected(ctx);
  43. }
  44. @Override
  45. public void channelInactive(ChannelHandlerContext ctx) throws Exception {
  46. listener.onDisconnected(ctx);
  47. }
  48. }

TcpServerStringInitializer.java

  1. import com.loveprogrammer.base.factory.SpringContextHelper;
  2. import com.loveprogrammer.base.network.listener.INetworkEventListener;
  3. import com.loveprogrammer.base.network.support.NetworkListener;
  4. import com.loveprogrammer.codec.MessageDecoder;
  5. import com.loveprogrammer.codec.MessageEncoder;
  6. import com.loveprogrammer.constants.ConstantValue;
  7. import io.netty.channel.ChannelInitializer;
  8. import io.netty.channel.ChannelPipeline;
  9. import io.netty.channel.socket.SocketChannel;
  10. import io.netty.handler.codec.DelimiterBasedFrameDecoder;
  11. import io.netty.handler.codec.Delimiters;
  12. import io.netty.handler.codec.string.StringDecoder;
  13. import io.netty.handler.codec.string.StringEncoder;
  14. public class TcpServerStringInitializer extends ChannelInitializer<SocketChannel> {
  15. @Override
  16. protected void initChannel(SocketChannel ch) {
  17. ChannelPipeline pipeline = ch.pipeline();
  18. // pipeline.addLast("framer",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
  19. pipeline.addLast("decoder", new MessageEncoder());
  20. pipeline.addLast("encoder", new MessageDecoder(ConstantValue.MESSAGE_CODEC_MAX_FRAME_LENGTH,
  21. ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_OFFSET, ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_LENGTH,
  22. ConstantValue.MESSAGE_CODEC_LENGTH_ADJUSTMENT, ConstantValue.MESSAGE_CODEC_INITIAL_BYTES_TO_STRIP, false));
  23. TcpMessageStringHandler handler = (TcpMessageStringHandler) SpringContextHelper.getBean(TcpMessageStringHandler.class);
  24. pipeline.addLast(handler);
  25. }
  26. }

INetworkEventListener.java

  1. public interface INetworkEventListener {
  2. /**
  3. * 连接建立
  4. *
  5. * @param ctx ChannelHandlerContext
  6. */
  7. void onConnected(ChannelHandlerContext ctx);
  8. /**
  9. * 连接断开
  10. * * @param ctx ChannelHandlerContext
  11. */
  12. void onDisconnected(ChannelHandlerContext ctx);
  13. /**
  14. * 异常发生
  15. * * @param ctx ChannelHandlerContext
  16. * * @param throwable 异常
  17. */
  18. void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable);
  19. void channelRead(ChannelHandlerContext ctx, StringMessage msg);
  20. }

NetworkListener.java

  1. package com.loveprogrammer.base.network.support;
  2. import com.loveprogrammer.base.bean.session.Session;
  3. import com.loveprogrammer.base.network.listener.INetworkEventListener;
  4. import com.loveprogrammer.constants.CommonValue;
  5. import com.loveprogrammer.pojo.StringMessage;
  6. import io.netty.channel.ChannelHandlerContext;
  7. import org.slf4j.Logger;
  8. import org.slf4j.LoggerFactory;
  9. import org.springframework.stereotype.Component;
  10. @Component
  11. public class NetworkListener implements INetworkEventListener {
  12. protected static final Logger logger = LoggerFactory.getLogger(NetworkListener.class);
  13. @Override
  14. public void onConnected(ChannelHandlerContext ctx) {
  15. logger.info("建立连接");
  16. SessionManager.getInstance().create(ctx.channel());
  17. }
  18. @Override
  19. public void onDisconnected(ChannelHandlerContext ctx) {
  20. logger.info("建立断开");
  21. SessionManager.getInstance().close(ctx.channel());
  22. }
  23. @Override
  24. public void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {
  25. logger.warn("异常发生", throwable);
  26. }
  27. @Override
  28. public void channelRead(ChannelHandlerContext ctx, StringMessage msg) {
  29. logger.info("数据内容:data=" + msg.getBody());
  30. String result = "我是服务器,我收到了你的信息:" + msg.getBody();
  31. Session session = SessionManager.getInstance().getSessionByChannel(ctx.channel());
  32. result += ",sessionId = " + session.getId();
  33. StringMessage message = StringMessage.create(1,1);
  34. message.setStatusCode(CommonValue.MSG_STATUS_CODE_SUCCESS);
  35. message.setBody(result);
  36. SessionManager.getInstance().sendMessage(ctx.channel(),message);
  37. }
  38. }

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

SocketClient

  1. public class SocketClient {
  2. private static final Logger logger = LoggerFactory.getLogger(SocketClient.class);
  3. private static final String IP = "127.0.0.1";
  4. private static final int PORT = 8088;
  5. private static EventLoopGroup group = new NioEventLoopGroup();
  6. protected static void run() throws InterruptedException {
  7. // Bootstrap bootstrap = new Bootstrap();
  8. // bootstrap.group(group);
  9. // bootstrap.channel(NioSocketChannel.class);
  10. // bootstrap.handler(new ChannelInitializer() {
  11. // protected void initChannel(Channel ch) throws Exception {
  12. // ChannelPipeline pipeline = ch.pipeline();
  13. // pipeline.addLast("framer",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
  14. // pipeline.addLast("decoder",new StringDecoder());
  15. // pipeline.addLast("encoder",new StringEncoder());
  16. // pipeline.addLast(new SocketClientHandler());
  17. // }
  18. // });
  19. Bootstrap bootstrap = new Bootstrap();
  20. bootstrap.group(group);
  21. bootstrap.channel(NioSocketChannel.class);
  22. bootstrap.handler(new ChannelInitializer() {
  23. @Override
  24. protected void initChannel(Channel ch) {
  25. ChannelPipeline pipeline = ch.pipeline();
  26. pipeline.addLast("encoder", new MessageEncoder());
  27. pipeline.addLast("decoder", new MessageDecoder(ConstantValue.MESSAGE_CODEC_MAX_FRAME_LENGTH,
  28. ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_LENGTH, ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_OFFSET,
  29. ConstantValue.MESSAGE_CODEC_LENGTH_ADJUSTMENT, ConstantValue.MESSAGE_CODEC_INITIAL_BYTES_TO_STRIP,
  30. false));
  31. pipeline.addLast(new SocketClientHandler());
  32. }
  33. });
  34. // 连接服务器
  35. ChannelFuture channelFuture = bootstrap.connect(IP, PORT).sync();
  36. StringMessage msg = StringMessage.create(1,1);
  37. msg.setStatusCode(200);
  38. msg.setBody("你好,我是eric");
  39. // msg += "\r\n";
  40. channelFuture.channel().writeAndFlush(msg);
  41. logger.info("向服务器发送消息 {}",msg);
  42. channelFuture.channel().closeFuture().sync();
  43. }
  44. public static void main(String[] args) throws InterruptedException {
  45. logger.info("开始连接Socket服务器...");
  46. try {
  47. run();
  48. } catch (Exception e) {
  49. e.printStackTrace();
  50. } finally {
  51. group.shutdownGracefully();
  52. }
  53. }
  54. }

SocketClientHandler.java

  1. import com.loveprogrammer.pojo.StringMessage;
  2. import io.netty.channel.ChannelHandlerContext;
  3. import io.netty.channel.SimpleChannelInboundHandler;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. /**
  7. * @ClassName SocketClientHandler
  8. * @Description TODO
  9. * @Author admin
  10. * @Date 2024/1/29 17:41
  11. * @Version 1.0
  12. */
  13. public class SocketClientHandler extends SimpleChannelInboundHandler<StringMessage> {
  14. private static final Logger logger = LoggerFactory.getLogger(SocketClientHandler.class);
  15. @Override
  16. public void exceptionCaught(ChannelHandlerContext arg0, Throwable arg1) {
  17. logger.info("异常发生", arg1);
  18. }
  19. @Override
  20. public void channelRead(ChannelHandlerContext arg0, Object msg) throws Exception {
  21. super.channelRead(arg0, msg);
  22. }
  23. @Override
  24. protected void channelRead0(ChannelHandlerContext context, StringMessage data) {
  25. logger.info("数据内容:data=" + data);
  26. try {
  27. Thread.sleep(1000);
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. StringMessage msg = StringMessage.create(1,1);
  32. msg.setStatusCode(202);
  33. msg.setBody("你好,我是eric");
  34. // msg += "\r\n";
  35. context.channel().writeAndFlush(msg);
  36. logger.info("向服务器发送消息 {}",msg);
  37. }
  38. @Override
  39. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  40. logger.info("客户端连接建立");
  41. super.channelActive(ctx);
  42. }
  43. @Override
  44. public void channelInactive(ChannelHandlerContext ctx) throws Exception {
  45. logger.info("客户端连接断开");
  46. super.channelInactive(ctx);
  47. }
  48. }

全部源码详见:

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

分支:step-06

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/98453
推荐阅读
相关标签
  

闽ICP备14008679号