Netty进阶-黏包半包解决方案

4、解决方案

4.1、短连接

以解决黏包为例。服务端同上,客户端如下

//短连接处理黏包,发送后关闭
public class TestSloveNianbaoClient {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            send();
        }
    }

    private static void send() {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            ChannelFuture channelFuture = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                //连接建立成功,active事件
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                    ByteBuf buf = ctx.alloc().buffer();
                                    buf.writeBytes("0123456789".getBytes());
                                    ctx.writeAndFlush(buf);
                                    ctx.channel().close();//发送后关闭,就是短连接
                                }
                            });
                        }
                    })
                    .connect(new InetSocketAddress(8080)).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            group.shutdownGracefully();
        }
    }
}

不能解决半包,服务端Netyy接受缓冲区大小默认是1024,调小一点就可以看到半包问题

.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16))
4.2、固定长度-定长解码器

客户端和服务端约定数据包长度

服务端

.addLast(new FixedLengthFrameDecoder(10))

//固定长度服务端
@Slf4j
public class TestFixLengthServer {
    public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup();
        NioEventLoopGroup worker = new NioEventLoopGroup();

        try {
            ChannelFuture channelFuture = new ServerBootstrap()
                    .group(boss, worker)
                    .channel(NioServerSocketChannel.class)

                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline()
                                    //核心代码,固定长度
                                    .addLast(new FixedLengthFrameDecoder(10))
                                    .addLast(new LoggingHandler());
                        }
                    })
                    .bind(8080).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("server error");
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}

客户端

//固定长度客户端
public class TestFixLengthClient {
    public static void main(String[] args) {
        send();
    }

    private static void send() {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            ChannelFuture channelFuture = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline()
                                    .addLast(new LoggingHandler())
                                    .addLast(new ChannelInboundHandlerAdapter() {
                                //连接建立成功,active事件
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                    ByteBuf buf = ctx.alloc().buffer();
                                    Random random = new Random();
                                    char c = '0';
                                    for (int i = 0; i < 10; i++) {
                                        byte[] bytes = fill(c, random.nextInt(10) + 1);
                                        c++;
                                        buf.writeBytes(bytes);
                                    }
                                    ctx.writeAndFlush(buf);
                                }
                            });
                        }
                    })
                    .connect(new InetSocketAddress(8080)).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            group.shutdownGracefully();
        }
    }

    private static byte[] fill(char c, int len) {
        StringBuilder builder = new StringBuilder();
        int i = 10 - len;
        for (int i1 = 0; i1 < len; i1++) {
            builder.append(c);
        }
        for (int i1 = 0; i1 < i; i1++) {
            builder.append("_");
        }
        return builder.toString().getBytes();
    }
}

结果

  • 客户端

    14:31:07.271 [nioEventLoopGroup-2-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x651d9086, L:/192.168.1.91:3834 - R:0.0.0.0/0.0.0.0:8080] WRITE: 100B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 30 30 5f 5f 5f 5f 5f 5f 5f 5f 31 5f 5f 5f 5f 5f |00________1_____|
    |00000010| 5f 5f 5f 5f 32 5f 5f 5f 5f 5f 5f 5f 5f 5f 33 5f |____2_________3_|
    |00000020| 5f 5f 5f 5f 5f 5f 5f 5f 34 34 34 34 34 34 34 34 |________44444444|
    |00000030| 5f 5f 35 35 35 35 35 5f 5f 5f 5f 5f 36 36 36 36 |__55555_____6666|
    |00000040| 5f 5f 5f 5f 5f 5f 37 5f 5f 5f 5f 5f 5f 5f 5f 5f |______7_________|
    |00000050| 38 38 38 38 38 38 38 38 38 38 39 39 5f 5f 5f 5f |888888888899____|
    |00000060| 5f 5f 5f 5f                                     |____            |
    +--------+-------------------------------------------------+----------------+
    
  • 服务端

    14:31:07.272 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x753a8804, L:/192.168.1.91:8080 - R:/192.168.1.91:3834] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 30 30 5f 5f 5f 5f 5f 5f 5f 5f                   |00________      |
    +--------+-------------------------------------------------+----------------+
    
    ..........
    
    14:31:07.273 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x753a8804, L:/192.168.1.91:8080 - R:/192.168.1.91:3834] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 31 5f 5f 5f 5f 5f 5f 5f 5f 5f                   |1_________      |
    +--------+-------------------------------------------------+----------------+
    
    
    一直到9
    ........
    

缺点是,数据包的大小不好把握

  • 长度定的太大,浪费
  • 长度定的太小,对某些数据包又显得不够
4.3、固定分隔符-行解码器

服务端加入,默认以 \n 或 \r\n 作为分隔符,如果超出指定长度仍未出现分隔符,则抛出异常

ch.pipeline().addLast(new LineBasedFrameDecoder(1024));

服务端

//分隔符解决黏包半包,服务端
@Slf4j
public class TestLineBasedServer {
    public static void main(String[] args) {
        NioEventLoopGroup boss = new NioEventLoopGroup();
        NioEventLoopGroup worker = new NioEventLoopGroup();

        try {
            ChannelFuture channelFuture = new ServerBootstrap()
                    .group(boss, worker)
                    .channel(NioServerSocketChannel.class)

                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline()
                                    //核心代码,分隔符,1024个字节内没找到分隔符,抛出异常 TooLongFrameException
                                    .addLast(new LineBasedFrameDecoder(1024))
                                    .addLast(new LoggingHandler());
                        }
                    })
                    .bind(8080).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("server error");
        } finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}

客户端

//分隔符解决黏包半包,客户端
public class TestLineBasedClient {
    public static void main(String[] args) {
        send();
    }

    private static void send() {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            ChannelFuture channelFuture = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel channel) throws Exception {
                            channel.pipeline()
                                    .addLast(new LoggingHandler())
                                    .addLast(new ChannelInboundHandlerAdapter() {
                                        //连接建立成功,active事件
                                        @Override
                                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                            ByteBuf buf = ctx.alloc().buffer();
                                            Random random = new Random();
                                            char c = '0';
                                            for (int i = 0; i < 10; i++) {
                                                StringBuilder builder = makeStr(c, random.nextInt(10) + 1);
                                                c++;
                                                buf.writeBytes(builder.toString().getBytes());
                                            }
                                            ctx.writeAndFlush(buf);
                                        }
                                    });
                        }
                    })
                    .connect(new InetSocketAddress(8080)).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            group.shutdownGracefully();
        }
    }

    private static StringBuilder makeStr(char c, int len) {
        StringBuilder builder = new StringBuilder(len + 2);
        for (int i = 0; i < len; i++) {
            builder.append(c);
        }
        builder.append("\n");
        return builder;
    }
}

结果

  • 客户端

    14:54:06.694 [nioEventLoopGroup-2-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x9208b1d6, L:/192.168.1.91:4584 - R:0.0.0.0/0.0.0.0:8080] WRITE: 58B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 30 30 30 30 30 0a 31 31 0a 32 32 32 32 32 32 32 |00000.11.2222222|
    |00000010| 0a 33 33 33 33 33 33 33 33 0a 34 34 34 34 34 0a |.33333333.44444.|
    |00000020| 35 35 0a 36 36 36 0a 37 37 37 0a 38 38 38 38 0a |55.666.777.8888.|
    |00000030| 39 39 39 39 39 39 39 39 39 0a                   |999999999.      |
    +--------+-------------------------------------------------+----------------+
    
  • 服务端

    14:54:06.696 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xe9699553, L:/192.168.1.91:8080 - R:/192.168.1.91:4584] READ: 5B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 30 30 30 30 30                                  |00000           |
    +--------+-------------------------------------------------+----------------+
    
    14:54:06.696 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xe9699553, L:/192.168.1.91:8080 - R:/192.168.1.91:4584] READ: 2B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 31 31                                           |11              |
    +--------+-------------------------------------------------+----------------+
    
    直到9
    ...........
    

缺点:如果数据本身中包含了分隔符,就解析错错误了

4.4、预设长度-LTC解码器
  • lengthFieldOffset - 长度字段偏移量
  • lengthFieldLength - 长度字段长度
  • lengthAdjustment - 长度字段为基础,还有几个字节是内容
  • initialBytesToStrip - 从头剥离几个字节

源码例子说明

  lengthFieldOffset   = 0
  lengthFieldLength   = 2
  lengthAdjustment    = 0
  initialBytesToStrip = 0 (= do not strip header)
 
  BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
  +--------+----------------+      +--------+----------------+
  | Length | Actual Content |----->| Length | Actual Content |
  | 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |
  +--------+----------------+      +--------+----------------+
  lengthFieldOffset   = 0
  lengthFieldLength   = 2
  lengthAdjustment    = 0
  initialBytesToStrip = 2 (= the length of the Length field)
 
  BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
  +--------+----------------+      +----------------+
  | Length | Actual Content |----->| Actual Content |
  | 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
  +--------+----------------+      +----------------+
  lengthFieldOffset   = 0
  lengthFieldLength   = 2
  lengthAdjustment    = 0
  initialBytesToStrip = 2 (= the length of the Length field)
 
  BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
  +--------+----------------+      +----------------+
  | Length | Actual Content |----->| Actual Content |
  | 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
  +--------+----------------+      +----------------+
  lengthFieldOffset   = 2 (= the length of Header 1)
  lengthFieldLength   = 3
  lengthAdjustment    = 0
  initialBytesToStrip = 0
 
  BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
  +----------+----------+----------------+      +----------+----------+----------------+
  | Header 1 |  Length  | Actual Content |----->| Header 1 |  Length  | Actual Content |
  |  0xCAFE  | 0x00000C | "HELLO, WORLD" |      |  0xCAFE  | 0x00000C | "HELLO, WORLD" |
  +----------+----------+----------------+      +----------+----------+----------------+
  lengthFieldOffset   = 0
  lengthFieldLength   = 3
  lengthAdjustment    = 2 (= the length of Header 1)
  initialBytesToStrip = 0
 
  BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
  +----------+----------+----------------+      +----------+----------+----------------+
  |  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |
  | 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
  +----------+----------+----------------+      +----------+----------+----------------+
  lengthFieldOffset   = 1 (= the length of HDR1)
  lengthFieldLength   = 2
  lengthAdjustment    = 1 (= the length of HDR2)
  initialBytesToStrip = 3 (= the length of HDR1 + LEN)
 
  BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
  +------+--------+------+----------------+      +------+----------------+
  | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
  | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
  +------+--------+------+----------------+      +------+----------------+

Embedded测试

//测试预设长度解码器
public class TestLengthField {
    public static void main(String[] args) {
        //参1:最大长度,一般固定
        //参2:长度字段偏移量
        //参3:长度字段长度
        //参4:长度字段为基础,还有几个字节是内容
        //参5: 从头剥离几个字节
        LengthFieldBasedFrameDecoder decoder = new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4);
        LoggingHandler handler = new LoggingHandler();
        EmbeddedChannel channel = new EmbeddedChannel(decoder, handler);

        ByteBuf buf = channel.alloc().buffer();
        writeMsg(buf, "hello");
        writeMsg(buf, "-world");
        channel.writeInbound(buf);
    }

    private static void writeMsg(ByteBuf buf, String content) {
        byte[] bytes = content.getBytes();
        int length = bytes.length;
        buf.writeInt(length);
        buf.writeBytes(bytes);
    }
}

结果

15:33:52.243 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 5B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f                                  |hello           |
+--------+-------------------------------------------------+----------------+
15:33:52.243 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 6B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 2d 77 6f 72 6c 64                               |-world          |
+--------+-------------------------------------------------+----------------+
15:33:52.243 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE
posted @   jpy  阅读(94)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示