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("\n");
        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

上一篇:十分钟带你看懂Netty如何实现C-S,【MySQL


下一篇:Netty 协议设计与解析 (自定义协议)