首页 > 其他分享 >Netty进阶-黏包半包解决方案

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

时间:2022-10-24 15:12:37浏览次数:41  
标签:Netty 黏包 bytes new 半包 5f buf void channel

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

标签:Netty,黏包,bytes,new,半包,5f,buf,void,channel
From: https://www.cnblogs.com/jpymll/p/16821507.html

相关文章

  • Netty进阶-协议
    5.1、Redis协议//redis协议测试publicclassTestRedis{publicstaticvoidmain(String[]args){finalbyte[]line={13,10};NioEventLoop......
  • Netty入门-ButeBuf
    3.5、ByteBuf3.5.1、创建//结果:初始容量256,扩容到512//PooledUnsafeDirectByteBuf(ridx:0,widx:0,cap:256)//PooledUnsafeDirectByteBuf(ridx:0,widx:300,cap......
  • Netty入门-Future & Promise
    3.3、Future&PromiseNetty中的Future与Jdk中Future同名,但是是两个接口,继承关系:Promise---extends-->Future(Netty)-----extend--->Future(JDK)区别:jdkFuture只能......
  • Netty入门-Handler & Pipeline
    3.4、Handler&PipelineChannelHandler用来处理Channel上的各种事件,分为入站、出站两种。所有ChannelHandler被连成一串,就是Pipeline入站处理器通常是ChannelIn......
  • Netty入门-Hello World
    Netty入门1、Netty优势NettyVSNIO,工作量大,bug多需要自己构建协议解决TCP传输问题,如黏包,半包epoll空轮询导致cpu100%对API进行增强,ThreadLocal-->FastThreadLocal......
  • Netty Reactor模型
      1、netty抽象出两个线程池:BossGroup负责监听和建立连接;WorkerGroup负责网络IO的读写2、BossGroup和WorkerGroup类型都是NioEventLoopGroup,相当于一个事件......
  • 【Netty 从成神到升仙系列 大结局】全网一图流死磕解析 Netty 源码
    ......
  • 序列化器---netty
    packagecn.itcast.protocol;importcom.google.gson.*;importjava.io.*;importjava.lang.reflect.Type;importjava.nio.charset.StandardCharsets;/***用......
  • Netty的第一个例子
    importio.netty.bootstrap.ServerBootstrap;importio.netty.channel.ChannelFuture;importio.netty.channel.ChannelInitializer;importio.netty.channel.ChannelOptio......
  • 通过netty把百度地图API获取的地理位置从Android端发送到Java服务器端
    本篇记录我在实现时的思考过程,写给之后可能遇到困难的我自己也给到需要帮助的人。写的比较浅显,见谅。在写项目代码的时候,需要把Android端的位置信息传输到服务器端,通过Ne......