• Netty(9)粘包与半包问题和解决


    粘包现象

    服务端代码

    public class HelloWorldServer {
        static final Logger log = LoggerFactory.getLogger(HelloWorldServer.class);
        void start() {
            NioEventLoopGroup boss = new NioEventLoopGroup(1);
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.channel(NioServerSocketChannel.class);
                serverBootstrap.group(boss, worker);
                serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //调试打印出服务器端收到的数据
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                    }
                });
                ChannelFuture channelFuture = serverBootstrap.bind(8080);
                channelFuture.sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                log.error("server error", e);
            } finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
                log.debug("stoped");
            }
        }
    
        public static void main(String[] args) {
            new HelloWorldServer().start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    客户端代码

    客户端代码希望发送 10 个消息,每个消息是 16 字节

    public class HelloWorldClient {
        static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);
        public static void main(String[] args) {
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.channel(NioSocketChannel.class);
                bootstrap.group(worker);
                bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            // 会在连接 channel 建立成功后,会触发 active 事件
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                ByteBuf buffer = ctx.alloc().buffer(16);
                                for (int i = 0; i < 10; i++) {
                                    buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
                                    ctx.writeAndFlush(buffer);
                                }
                            }
                        });
                    }
                });
                ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
                channelFuture.channel().closeFuture().sync();
    
            } catch (InterruptedException e) {
                log.error("client error", e);
            } finally {
                worker.shutdownGracefully();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    运行结果

    一次就接收了 160 个字节,而非分 10 次接收
    在这里插入图片描述

    半包现象

    服务端修改一下接收缓冲区,其它代码不变

    serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
    
    
    • 1
    • 2

    serverBootstrap.option(ChannelOption.SO_RCVBUF, 10) 影响的底层接收缓冲区(即滑动窗口)大小,仅决定了 netty 读取的最小单位,netty 实际每次读取的一般是它的整数倍
    在这里插入图片描述

    运行结果

    在这里插入图片描述

    现象分析

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

    只要是使用TCP/IP协议进行编程,就会出现黏包和半包想象

    UDP协议没有

    滑动窗口

    TCP 以一个段(segment)为单位,每发送一个段就需要进行一次确认应答(ack)处理,但如果这么做,缺点是包的往返时间越长性能就越差
    在这里插入图片描述
    为了解决此问题,引入了窗口概念,窗口大小即决定了无需等待应答而可以继续发送的数据最大值
    在这里插入图片描述
    窗口实际就起到一个缓冲区的作用,同时也能起到 流量控制的作用

    • 图中深色的部分即要发送的数据,高亮的部分即窗口
    • 窗口内的数据才允许被发送,当应答未到达前,窗口必须停止滑动
    • 如果 1001~2000 这个段的数据 ack 回来了,窗口就可以向前滑动
    • 接收方也会维护一个窗口,只有落在窗口内的数据才能允许接收

    TCP层面分析(滑动窗口)

    • 如果网络比较繁忙,接收方的窗口使用完了,不能继续向下滑动,其接收到的数据势必会容易发生半包现象(到一半窗口使用完了,如果这时候去读缓冲区,读到的就是半包的数据)

    • 如果滑动窗口比较空闲,里面还有许多的空间没有使用,这时候客户端发送的许多条数据过来,滑动窗口将这多条数据都接收到了,那就发生了黏包现象

    MSS 限制

    • 链路层对一次能够发送的最大数据有限制,这个限制称之为 MTU(maximum transmission unit),不同的链路设备的 MTU 值也有所不同,例如

    • 以太网的 MTU 是 1500

    • FDDI(光纤分布式数据接口)的 MTU 是 4352

    • 本地回环地址的 MTU 是 65535 - 本地测试不走网卡

    • MSS 是最大段长度(maximum segment size),它是 MTU 刨去 tcp 头和 ip 头后剩余能够作为数据传输的字节数

    • ipv4 tcp 头占用 20 bytes,ip 头占用 20 bytes,因此以太网 MSS 的值为 1500 - 40 = 1460

    • TCP 在传递大量数据时,会按照 MSS 大小将数据进行分割发送

    • MSS 的值在三次握手时通知对方自己 MSS 的值,然后在两者之间选择一个小值作为 MSS
      在这里插入图片描述

    Nagle 算法

    • 即使发送一个字节,也需要加入 tcp 头和 ip 头,也就是总字节数会使用 41 bytes,非常不经济。因此为了提高网络利用率,tcp 希望尽可能发送足够大的数据,这就是 Nagle 算法产生的缘由
    • 该算法是指发送端即使还有应该发送的数据,但如果这部分数据很少的话,则进行延迟发送
      • 如果 SO_SNDBUF 的数据达到 MSS,则需要发送
      • 如果 SO_SNDBUF 中含有 FIN(表示需要连接关闭)这时将剩余数据发送,再关闭
      • 如果 TCP_NODELAY = true,则需要发送
      • 已发送的数据都收到 ack 时,则需要发送
      • 上述条件不满足,但发生超时(一般为 200ms)则需要发送
      • 除上述情况,延迟发送

    粘包

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

    半包

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

    解决方法

    短链接

    短链接,发一个包建立一次连接,这样连接建立到连接断开之间就是消息的边界,缺点效率太低

    粘包解决

    服务器代码

    
    public class HelloWorldClient {
        static final Logger log = LoggerFactory.getLogger(HelloWorldClient.class);
    
        public static void main(String[] args) {
            // 分 10 次发送
            for (int i = 0; i < 10; i++) {
                send();
            }
            System.out.println("finish");
        }
    
        private static void send() {
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.channel(NioSocketChannel.class);
                bootstrap.group(worker);
                bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                ByteBuf buffer = ctx.alloc().buffer();
                                buffer.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
                                ctx.writeAndFlush(buffer);
                                // 发完即关
                                ctx.channel().close();
                            }
                        });
                    }
                });
                ChannelFuture channelFuture = bootstrap.connect("localhost", 8080).sync();
                channelFuture.channel().closeFuture().sync();
    
            } catch (InterruptedException e) {
                log.error("client error", e);
            } finally {
                worker.shutdownGracefully();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    客户端代码

    客户端代码只是需要将之前设置滑动窗口的代码注释掉即可

    如果不设置滑动窗口的话,现在的操作系统可以自行调整滑动窗口的大小

    ByteBuf默认是1024,足够大了

    运行结果

    在这里插入图片描述

    半包

    半包用这种办法还是不好解决,因为接收方的缓冲区大小是有限的

    定长解码器

    让所有数据包长度固定(假设长度为 8 字节),服务器端加入

    
    ch.pipeline().addLast(new FixedLengthFrameDecoder(8));
    
    • 1
    • 2

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

    • 长度定的太大,浪费
    • 长度定的太小,对某些数据包又显得不够

    服务器代码

    @Slf4j
    public class Server2 {
        void start() {
            NioEventLoopGroup boss = new NioEventLoopGroup();
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.channel(NioServerSocketChannel.class);
                // 调整系统的接收缓冲区(滑动窗口)
    //            serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
                // 调整 netty 的接收缓冲区(byteBuf)
                serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16));
                serverBootstrap.group(boss, worker);
                serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new FixedLengthFrameDecoder(10));
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                    }
                });
                ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                log.error("server error", e);
            } finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            new Server2().start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    客户端代码

    public class Client2 {
        static final Logger log = LoggerFactory.getLogger(Client1.class);
    
        public static void main(String[] args) {
            send();
            System.out.println("finish");
        }
    
        /*
            产生定长的消息
                产生一个内容为c的消息,消息的长度是len,不足len的长度使用下划线补全
         */
        public static byte[] fill10Bytes(char c, int len) {
            byte[] bytes = new byte[10];
            Arrays.fill(bytes, (byte) '_');
            for (int i = 0; i < len; i++) {
                bytes[i] = (byte) c;
            }
            System.out.println(new String(bytes));
            return bytes;
        }
    
        private static void send() {
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.channel(NioSocketChannel.class);
                bootstrap.group(worker);
                bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            // 会在连接 channel 建立成功后,会触发 active 事件
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) {
                                ByteBuf buf = ctx.alloc().buffer();
                                char c = '0';
                                Random r = new Random();
                                for (int i = 0; i < 10; i++) {
                                    //内容长度随机:1到10之间
                                    byte[] bytes = fill10Bytes(c, r.nextInt(10) + 1);
                                    c++;
                                    //将bytes数组里面的内容写入到buf中
                                    buf.writeBytes(bytes);
                                }
                                //向服务器端发送消息
                                ctx.writeAndFlush(buf);
                            }
                        });
                    }
                });
                ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                log.error("client error", e);
            } finally {
                worker.shutdownGracefully();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    运行结果

    客户端输出

    可以发现,在这里保证了每一条消息的长度都是10

    客户端将10*10=100个字节的消息一起发出去了

    
    23:39:02 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x7a473d9c] REGISTERED
    23:39:02 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x7a473d9c] CONNECT: /127.0.0.1:8080
    23:39:02 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x7a473d9c, L:/127.0.0.1:10305 - R:/127.0.0.1:8080] ACTIVE
    000_______
    11111_____
    222222____
    33333_____
    44444444__
    555_______
    6666666___
    77777_____
    8888______
    99________
    23:39:02 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x7a473d9c, L:/127.0.0.1:10305 - R:/127.0.0.1:8080] WRITE: 100B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 30 30 30 5f 5f 5f 5f 5f 5f 5f 31 31 31 31 31 5f |000_______11111_|
    |00000010| 5f 5f 5f 5f 32 32 32 32 32 32 5f 5f 5f 5f 33 33 |____222222____33|
    |00000020| 33 33 33 5f 5f 5f 5f 5f 34 34 34 34 34 34 34 34 |333_____44444444|
    |00000030| 5f 5f 35 35 35 5f 5f 5f 5f 5f 5f 5f 36 36 36 36 |__555_______6666|
    |00000040| 36 36 36 5f 5f 5f 37 37 37 37 37 5f 5f 5f 5f 5f |666___77777_____|
    |00000050| 38 38 38 38 5f 5f 5f 5f 5f 5f 39 39 5f 5f 5f 5f |8888______99____|
    |00000060| 5f 5f 5f 5f                                     |____            |
    +--------+-------------------------------------------------+----------------+
    23:39:02 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x7a473d9c, L:/127.0.0.1:10305 - R:/127.0.0.1:8080] FLUSH
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    服务器输出

    服务器将消息进行解码,虽然之前发出来的时候是黏包了,都是现在输出的是没有黏包的

    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] REGISTERED
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] ACTIVE
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 30 30 30 5f 5f 5f 5f 5f 5f 5f                   |000_______      |
    +--------+-------------------------------------------------+----------------+
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 31 31 31 31 31 5f 5f 5f 5f 5f                   |11111_____      |
    +--------+-------------------------------------------------+----------------+
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] 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 5f 5f 5f 5f                   |222222____      |
    +--------+-------------------------------------------------+----------------+
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 33 33 33 33 33 5f 5f 5f 5f 5f                   |33333_____      |
    +--------+-------------------------------------------------+----------------+
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] 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 34 34 5f 5f                   |44444444__      |
    +--------+-------------------------------------------------+----------------+
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 35 35 35 5f 5f 5f 5f 5f 5f 5f                   |555_______      |
    +--------+-------------------------------------------------+----------------+
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 36 36 36 36 36 36 36 5f 5f 5f                   |6666666___      |
    +--------+-------------------------------------------------+----------------+
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 37 37 37 37 37 5f 5f 5f 5f 5f                   |77777_____      |
    +--------+-------------------------------------------------+----------------+
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 38 38 38 38 5f 5f 5f 5f 5f 5f                   |8888______      |
    +--------+-------------------------------------------------+----------------+
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ: 10B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 39 39 5f 5f 5f 5f 5f 5f 5f 5f                   |99________      |
    +--------+-------------------------------------------------+----------------+
    23:39:02 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [id: 0xc0d7cca8, L:/127.0.0.1:8080 - R:/127.0.0.1:10305] READ COMPLETE
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    固定分隔符

    第一种方法:

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

      //以换行符作为分隔符 (Linux: \n 和 Windows: \r\n)
      ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
      
      
      • 1
      • 2
      • 3
    • 客户端在每条消息之后,加入 \n 分隔符

    第二种方法定义分隔符

    /*
    maxFrameLength: 最大长度
    delimiter: 字节定义的ByteBuf类型的分隔符
    */
    ch.pipeline().addLast(new DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf delimiter));
    
    • 1
    • 2
    • 3
    • 4
    • 5

    客户端代码

    循环十次,发送10条消息,只不过这些消息都会以\n结结束

    public class Client3 {
        static final Logger log = LoggerFactory.getLogger(Client1.class);
    
        public static void main(String[] args) {
            send();
            System.out.println("finish");
        }
    
        public static StringBuilder makeString(char c, int len) {
            StringBuilder sb = new StringBuilder(len + 2);
            for (int i = 0; i < len; i++) {
                sb.append(c);
            }
            sb.append("\n");
            return sb;
        }
    
        private static void send() {
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.channel(NioSocketChannel.class);
                bootstrap.group(worker);
                bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            // 会在连接 channel 建立成功后,会触发 active 事件
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) {
                                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) {
                log.error("client error", e);
            } finally {
                worker.shutdownGracefully();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    服务器代码

    @Slf4j
    public class Server3 {
        void start() {
            NioEventLoopGroup boss = new NioEventLoopGroup();
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.channel(NioServerSocketChannel.class);
                // 调整系统的接收缓冲区(滑动窗口)
    //            serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
                // 调整 netty 的接收缓冲区(byteBuf)
                serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16));
                serverBootstrap.group(boss, worker);
                serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                    }
                });
                ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                log.error("server error", e);
            } finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            new Server3().start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    预设长度

    在发送消息前,先约定用定长字节表示接下来数据的长度

    // 最大长度,长度偏移,长度占用字节,长度调整,剥离字节数
    /*
    maxFrameLength: 		最大长度
    lengthFieldoffset : 	长度字段偏移量
    lengthFieldLength : 	消息长度占用字节
    lengthAdjustment : 		长度字段为基准,还有几个字节(过后)才是内容
    initialBytesToStrip : 	从头剥离几个字节, 不需要的字节内容
    */
    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 1, 0, 1));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    具体的使用,可以看源码中的文档说明
    在这里插入图片描述

    代码

    
    public class TestLengthFieldDecoder {
        public static void main(String[] args) {
        	//使用的是writeInt, int占四个字节
        	//将版本进行了保留
            EmbeddedChannel channel = new EmbeddedChannel(
                    new LengthFieldBasedFrameDecoder(
                            1024, 0, 4, 1, 4),
                    new LoggingHandler(LogLevel.DEBUG)
            );
    
            //  4 个字节的内容长度, 实际内容
            ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
            //想buf中加入内容:Hello, world
            send(buffer, "Hello, world");
            send(buffer, "Hi!");
            //将消息写入channel
            channel.writeInbound(buffer);
        }
    
        private static void send(ByteBuf buffer, String content) {
            byte[] bytes = content.getBytes(); // 实际内容
            int length = bytes.length; // 实际内容长度
            buffer.writeInt(length);
            buffer.writeByte(1);//使用一个字节表示版本号为:1 
            buffer.writeBytes(bytes);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    运行结果

    00:02:23 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] REGISTERED
    00:02:23 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] ACTIVE
    00:02:23 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 13B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 01 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64          |.Hello, world   |
    +--------+-------------------------------------------------+----------------+
    00:02:23 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 4B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 01 48 69 21                                     |.Hi!            |
    +--------+-------------------------------------------------+----------------+
    00:02:23 [DEBUG] [main] i.n.h.l.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ COMPLETE
    
    Process finished with exit code 0
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
  • 相关阅读:
    《Linux从练气到飞升》No.31 多线程编程实践与线程安全技术
    Golang 语法入门
    python中的async和await
    物联网协议基础知识
    java计算机毕业设计酒店管理系统源码+mysql数据库+系统+lw文档+部署
    VueX的使用
    数据源报表
    【学习笔记】Servlet
    数据科学中的数据库简介
    python+django家政服务中介网站系统
  • 原文地址:https://blog.csdn.net/yyuggjggg/article/details/126476923