zoukankan      html  css  js  c++  java
  • Netty 黏包半包问题与解决方案

    Netty 黏包半包问题与解决方案

    一、黏包/半包现象分析

    黏包

    • 现象,发送 abc def , 接收abcdef
    • 原因
      • 应用层: 接收方ByteBuf设置太大(Netty默认1024)
      • 滑动窗口:假设发送发256 bytes 表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这256 bytes 字节就会缓冲在接收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会黏包
      • Nagle算法:会造成黏包

    半包

    • 现象,发送 abcdef , 接收abc def
    • 原因
      • 应用层:接收方ByteBuf小于实际发送数据量
      • 滑动窗口:假设接收方窗口只剩下了128bytes,发送方的报文大小是256 bytes,这时放不下了,只能先发送前 128 bytes,等待ack后才能发送剩余部分,这就造成了半包
      • MSS限制:当发送的数据超过MSS限制后,会将数据切分发送,就会造成半包

    本质是因为 TCP 是流式协议,消息无边界。

    黏包问题

    Server端

    @Slf4j
    public class NServer {
        public static void main(String[] args) {
            NioEventLoopGroup boss = new NioEventLoopGroup(1);
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap bootstrap = new ServerBootstrap();
                bootstrap.group(boss,worker)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel ch) throws Exception {
                                //添加处理器
                                ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                            }
                        });
    
                ChannelFuture channelFuture = bootstrap.bind(8080).sync();
    
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    }
    

    Client端

    @Slf4j
    public class NClient {
        public static void main(String[] args) {
            NioEventLoopGroup group = new NioEventLoopGroup();
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel ch) throws Exception {
    
                                // 添加处理器
                                ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
    
                                    // 会在 channel 连接成功后触发 channelActive()
                                    @Override
                                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
                                        // 连续向客户端 发送10次,每次长度为16个字节
                                        for (int i = 0; i < 10; i++) {
                                            ByteBuf buf = ctx.alloc().buffer(16);
                                            buf.writeBytes(new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15});
                                            ctx.writeAndFlush(buf);
                                        }
                                    }
                                });
                            }
                        });
    
                ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                group.shutdownGracefully();
            }
        }
    }
    

    Server端接收结果(黏包现象):

    6:39:32.811 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xecf2bdd3, L:/127.0.0.1:8080 - R:/127.0.0.1:63274] READ: 160B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000020| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000030| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000040| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000050| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000060| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000070| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000080| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000090| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    +--------+-------------------------------------------------+----------------+
    16:39:32.811 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 160, cap: 1024) that reached at the tail of the pipeline. Please check your pipeline configuration.
    16:39:32.812 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xecf2bdd3, L:/127.0.0.1:8080 - R:/127.0.0.1:63274] READ COMPLETE
    

    从该结果发现 服务端将这10次消息组合到一起接收了,也就产生了黏包现象

    半包问题

    Server端

    @Slf4j
    public class NServer {
        public static void main(String[] args) {
            NioEventLoopGroup boss = new NioEventLoopGroup(1);
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap bootstrap = new ServerBootstrap();
                bootstrap.group(boss,worker)
                        .channel(NioServerSocketChannel.class)
    
                        // 调整系统的接收缓冲区(滑动窗口) 10个字节
                        .option(ChannelOption.SO_RCVBUF,10)
                        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel ch) throws Exception {
                                //添加处理器
                                ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                            }
                        });
    
                ChannelFuture channelFuture = bootstrap.bind(8080).sync();
    
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    }
    

    Client端

    @Slf4j
    public class NClient {
        public static void main(String[] args) {
            NioEventLoopGroup group = new NioEventLoopGroup();
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel ch) throws Exception {
    
                                // 添加处理器
                                ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
    
                                    // 会在 channel 连接成功后触发 channelActive()
                                    @Override
                                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    
                                        // 连续向客户端 发送10次,每次长度为16个字节
                                        for (int i = 0; i < 10; i++) {
                                            ByteBuf buf = ctx.alloc().buffer(16);
                                            buf.writeBytes(new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15});
                                            ctx.writeAndFlush(buf);
                                        }
                                    }
                                });
                            }
                        });
    
                ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                group.shutdownGracefully();
            }
        }
    }
    

    Server端接收结果(半包现象):

    16:46:20.715 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ: 36B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000020| 00 01 02 03                                     |....            |
    +--------+-------------------------------------------------+----------------+
    16:46:20.715 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 36, cap: 1024) that reached at the tail of the pipeline. Please check your pipeline configuration.
    16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ COMPLETE
    16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ: 40B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
    |00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
    |00000020| 04 05 06 07 08 09 0a 0b                         |........        |
    +--------+-------------------------------------------------+----------------+
    16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 40, cap: 1024) that reached at the tail of the pipeline. Please check your pipeline configuration.
    16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ COMPLETE
    16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ: 40B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 0c 0d 0e 0f 00 01 02 03 04 05 06 07 08 09 0a 0b |................|
    |00000010| 0c 0d 0e 0f 00 01 02 03 04 05 06 07 08 09 0a 0b |................|
    |00000020| 0c 0d 0e 0f 00 01 02 03                         |........        |
    +--------+-------------------------------------------------+----------------+
    16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 40, cap: 512) that reached at the tail of the pipeline. Please check your pipeline configuration.
    16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ COMPLETE
    16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ: 40B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
    |00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
    |00000020| 04 05 06 07 08 09 0a 0b                         |........        |
    +--------+-------------------------------------------------+----------------+
    16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 40, cap: 512) that reached at the tail of the pipeline. Please check your pipeline configuration.
    16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ COMPLETE
    16:46:20.716 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x42cc76ba, L:/127.0.0.1:8080 - R:/127.0.0.1:50848] READ: 4B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 0c 0d 0e 0f                                     |....            |
    +--------+-------------------------------------------------+----------------+
    16:46:20.717 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 4, cap: 496) that reached at the tail of the pipeline. Please check your pipeline configuration.
    

    二、解决方案

    1. 短链接(方案一) 不推荐

    短链接方式,客户端 发送完消息后 立刻主动断开连接

    Client端

    @Slf4j
    public class NClient {
        public static void main(String[] args) {
    
            // 连续发送10次
            for (int i = 0; i < 10; i++) {
                send();
            }
            System.out.println("finish");
        }
    
        private static void send() {
            NioEventLoopGroup group = new NioEventLoopGroup();
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel ch) throws Exception {
    
                                // 添加处理器
                                ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
    
                                    // 会在 channel 连接成功后触发 channelActive()
                                    @Override
                                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                        // 连续向客户端 发送10次,每次长度为16个字节
                                        ByteBuf buf = ctx.alloc().buffer(16);
                                        buf.writeBytes(new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15});
    
                                        // 发送数据
                                        ctx.writeAndFlush(buf);
    
                                        // 断开连接
                                        ctx.channel().close();
                                    }
                                });
                            }
                        });
    
                ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                group.shutdownGracefully();
            }
        }
    }
    

    2.定长解码器(方案二)

    需要 客户端 和 服务端 双方 约定好消息的长度 (这也是弊端)

    服务端要 使用 FixedLengthFrameDecoder 定长解码器

    客户端要 固定好发送数据的长度

    Server端

    @Slf4j
    public class NServer {
        public static void main(String[] args) {
            NioEventLoopGroup boss = new NioEventLoopGroup(1);
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap bootstrap = new ServerBootstrap();
                bootstrap.group(boss,worker)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel ch) throws Exception {
                                // 添加处理器
    
                                // 添加定长解码器
                                ch.pipeline().addLast(new FixedLengthFrameDecoder(10));
    
                                ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                            }
                        });
    
                ChannelFuture channelFuture = bootstrap.bind(8080).sync();
    
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    }
    

    Client端

    @Slf4j
    public class NClient {
        
        public static void main(String[] args) {
            send();
            System.out.println("finish");
        }
    
    	// 填充为10个字节长度的数组
        public static byte[] fill0Bytes(char c,int len){
            byte[] b = new byte[10];
            for (int i = 0; i < 10; i++) {
                if (i < len){
                    b[i] = (byte) c;
                }else {
                    b[i] = '_';
                }
            }
            System.out.println(new String(b));
            return b;
        }
    
        private static void send() {
            NioEventLoopGroup group = new NioEventLoopGroup();
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel ch) throws Exception {
    
                                // 添加处理器
                                ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
    
                                    // 会在 channel 连接成功后触发 channelActive()
                                    @Override
                                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                        ByteBuf buf = ctx.alloc().buffer();
    
                                        // 发送 总长度为100 的消息
                                        char c = '0';
                                        Random r = new Random();
                                        for (int i = 0; i < 10; i++) {
                                            byte[] bytes = fill0Bytes(c, r.nextInt(10) + 1);
                                            c++;
                                            buf.writeBytes(bytes);
                                        }
                                        ctx.writeAndFlush(buf);
                                    }
                                });
                            }
                        });
    
                ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                group.shutdownGracefully();
            }
        }
    }
    

    结果

    01:16:48.461 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 30 30 30 30 30 30 30 5f 5f 5f                   |0000000___      |
    +--------+-------------------------------------------------+----------------+
    01:16:48.461 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 10, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
    01:16:48.461 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 31 31 31 31 5f 5f 5f 5f 5f 5f                   |1111______      |
    +--------+-------------------------------------------------+----------------+
    01:16:48.461 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 20, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 32 32 32 32 32 32 32 32 32 32                   |2222222222      |
    +--------+-------------------------------------------------+----------------+
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 30, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 33 33 33 33 33 33 5f 5f 5f 5f                   |333333____      |
    +--------+-------------------------------------------------+----------------+
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 40, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 34 34 34 34 34 34 5f 5f 5f 5f                   |444444____      |
    +--------+-------------------------------------------------+----------------+
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 50, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 35 35 35 35 35 35 35 5f 5f 5f                   |5555555___      |
    +--------+-------------------------------------------------+----------------+
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 60, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 36 36 36 36 36 5f 5f 5f 5f 5f                   |66666_____      |
    +--------+-------------------------------------------------+----------------+
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 70, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 37 37 37 37 37 37 37 5f 5f 5f                   |7777777___      |
    +--------+-------------------------------------------------+----------------+
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 80, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 38 38 38 38 38 38 38 38 38 38                   |8888888888      |
    +--------+-------------------------------------------------+----------------+
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 90, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x98236ef6, L:/127.0.0.1:8080 - R:/127.0.0.1:61077] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 39 39 39 39 39 39 39 39 39 5f                   |999999999_      |
    +--------+-------------------------------------------------+----------------+
    01:16:48.462 [nioEventLoopGroup-3-1] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 100, widx: 100, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
    
    

    缺点

    • 需要 服务端 与客户端 约定好固定消息的长度。
    • 若不够长度的消息,也会造成空间的浪费。

    3.分隔符解码器(方案三)

    约定 :消息以分隔符作为界限 (分隔符 需要 客户端与服务端协商一致)

    服务端使用 DelimiterBasedFrameDecoder (可以传入事先约定好的分隔符作为消息分界线)

    这里我们使用 LineBasedFrameDecoder ( 以换行符作为消息分界线)演示

    Server端

    @Slf4j
    public class NServer {
        public static void main(String[] args) {
            NioEventLoopGroup boss = new NioEventLoopGroup(1);
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap bootstrap = new ServerBootstrap();
                bootstrap.group(boss,worker)
                        .channel(NioServerSocketChannel.class)
                        .childHandler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel ch) throws Exception {
                                // 添加处理器
    
                                // 添加 行分隔符解码器   参数: 超过该长度还没有遇到分隔符 则抛异常
                                ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
    
                                ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                            }
                        });
    
                ChannelFuture channelFuture = bootstrap.bind(8080).sync();
    
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    }
    

    Client端

    @Slf4j
    public class NClient {
        public static void main(String[] args) {
            send();
            System.out.println("finish");
        }
    
    
        // 根据字符生成字符串 以换行符结尾
        public static  StringBuilder makeString(char c,int len){
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < len; i++) {
                sb.append(c);
            }
            sb.append("
    ");
            return sb;
        }
        
        private static void send() {
            NioEventLoopGroup group = new NioEventLoopGroup();
            Bootstrap bootstrap = new Bootstrap();
            try {
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<NioSocketChannel>() {
                            @Override
                            protected void initChannel(NioSocketChannel ch) throws Exception {
    
                                ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                                ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                                    @Override
                                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                        ByteBuf buf = ctx.alloc().buffer();
    
                                        // 发送 消息的组合 每个消息以换行符结尾
                                        char c = '0';
                                        Random r = new Random();
                                        for (int i = 0; i < 10; i++) {
                                            StringBuilder sb = makeString(c, r.nextInt(256) + 1);
                                            c++;
                                            buf.writeBytes(sb.toString().getBytes());
                                        }
                                        ctx.writeAndFlush(buf);
                                    }
                                });
                            }
                        });
    
                ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                group.shutdownGracefully();
            }
        }
    }
    

    因为结果太长 这里就不贴出来了。

    4.LTC解码器(方案四)

    该方案类似于 TCP/IP 中消息传输的方式, 根据报头 来解析该报文的消息体。

    服务端使用 LengthFieldBasedFrameDecoder 解码器

    LTC 参数介绍

    • maxFrameLength - 若消息超过该最大长度 仍没有发现分割标准 则失败抛异常

    • lengthFieldOffset - 长度字段偏移量

    • lengthFieldLength - 长度字段长度

    • lengthAdjustment - 长度字段为基准,还有几个字节是内容

    • initialBytesToStrip - 最后解析的结果需要剥离几个字节

    代码演示:

    这里使用 EmbeddedChannel 方便做测试

    public class TestLengthFieldDecoder {
    
        public static void main(String[] args) {
    		
             /**
             * 参数1: maxFrameLength 最大接收多少长度的限制
             * 参数2: lengthFieldOffset  距离 内容长度字段 的偏移量
             * 参数3: lengthFieldLength  内容长度字段多少个字节
             * 参数4: lengthAdjustment   从内容长度字段结尾算起  还需要多少个字节到 主内容
             * 参数5: initialBytesToStrip  最终将数据从头剥离几个字节
             */
            EmbeddedChannel channel = new EmbeddedChannel(
                    new LengthFieldBasedFrameDecoder(1024,0,4,0,0),
                    new LoggingHandler(LogLevel.DEBUG)
            );
    
            // 4个字节的内容长度, 实际内容
            ByteBuf  buffer = ByteBufAllocator.DEFAULT.buffer();
            send(buffer, "Hello,World");
            send(buffer, "hi!");
            channel.writeInbound(buffer);
    
        }
    
        private static void send(ByteBuf buffer, String content) {
            // 实际内容
            byte[] bytes = content.getBytes();
            // 实际内容长度
            int length = bytes.length;
            // 先写入长度  再写入内容
            buffer.writeInt(length);
            buffer.writeBytes(bytes);
        }
    }
    

    结果:

    12:11:51.671 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 15B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 00 00 00 0b 48 65 6c 6c 6f 2c 57 6f 72 6c 64    |....Hello,World |
    +--------+-------------------------------------------------+----------------+
    12:11:51.671 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 7B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 00 00 00 03 68 69 21                            |....hi!         |
    +--------+-------------------------------------------------+----------------+
    12:11:51.671 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE
    
    

    initialBytesToStrip 参数改为4 ,就意味着 要将数据去除前4个字节,后面的为最终结果

            
    		EmbeddedChannel channel = new EmbeddedChannel(
                    new LengthFieldBasedFrameDecoder(1024,0,4,0,4),
                    new LoggingHandler(LogLevel.DEBUG)
            );
    

    结果为:

    12:20:12.070 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 11B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 48 65 6c 6c 6f 2c 57 6f 72 6c 64                |Hello,World     |
    +--------+-------------------------------------------------+----------------+
    12:20:12.070 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 3B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 68 69 21                                        |hi!             |
    +--------+-------------------------------------------------+----------------+
    12:20:12.070 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE
    
    
    万般皆下品,唯有读书高!
  • 相关阅读:
    Spring Boot缓存实战 Redis 设置有效时间和自动刷新缓存
    快速排序
    JDK,JRE,JVM区别与联系
    RocketMQ
    IO、NIO、AIO 内部原理分析
    java设计模式-回调、事件监听器、观察者模式
    Spring源码相关
    java单例模式几种实现方式
    RabbitMQ学习笔记二:Java使用RabbitMQ
    RabbitMQ学习笔记一:本地Windows环境安装RabbitMQ Server
  • 原文地址:https://www.cnblogs.com/s686zhou/p/15257812.html
Copyright © 2011-2022 走看看