Netty(4)粘包半包与解决方案

4 篇文章 0 订阅

一. 粘包半包现象分析

服务端代码:

 public static void main(String[] args) {
        // 创建线程组
        // bossGroup 处理链接请求
        // workerGroup 处理客户端业务逻辑
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            // 创建服务端启动对象
            new ServerBootstrap()
                    .group(bossGroup, workerGroup) // 设置两个线程组
                    .channel(NioServerSocketChannel.class) //设置tcpSocket通道 使用nio作为服务器通道
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    log.debug("收到消息");
                                    super.channelRead(ctx, msg);
                                }
                            });
                        }
                    }).bind(8000).sync();
        } catch (Exception e){
            e.printStackTrace();
        }
    }

客户端代码:

    public static void main(String[] args) {
        EventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            // 创建客户端启动对象
            ChannelFuture channelFuture = new Bootstrap()
                    .group(eventExecutors) // 设置线程组
                    .channel(NioSocketChannel.class) //设置 客户端通道的实现类
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new StringEncoder()); // 解码器
                        }
                    })
                    .connect("localhost", 8000);
            channelFuture.addListener(new ChannelFutureListener() {
                public void operationComplete(ChannelFuture future) {
                    Channel channel = future.channel();
                    for (int i = 0; i < 20; i++) {
                        channel.writeAndFlush("加入了连接!");
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

问题: 客户端连接成功后,分20次发送一串字符串,结果服务端只收到一次,包含了这20次发送的所有消息
在这里插入图片描述

分析原因: 以上问题就是由于tcp粘包导致的

客户端发送的报文(字节数据)会缓冲在服务端的滑动窗口(缓冲区)中,当滑动窗口中缓冲了多个报文就会粘包,如果超过报文大小超过滑动窗口大小就会发生半包现象

注意:UDP是基于报文发送的,UDP报文的首部会有16bit来表现UDP数据的长度,所以不同的报文之间是可以区别隔离出来的,所以应用层接收传输层的报文时,不会存在拆包和粘包的问题

二. 解决:

1. 服务端设置固定报文大小

服务端设置缓冲区大小20个字节

ch.pipeline().addLast(new FixedLengthFrameDecoder(20));

客户端发送的字节小于20的话就补充到20

channel.writeAndFlush("加入了连接!__"); // __ 补充两个字节凑成20字节
2. 行解码器

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

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

客户端

channel.writeAndFlush("加入了连接!\n");
3. LTC解码器

服务端

  • 第一个:最大长度 报文的总长度
  • 第二个:长度字段偏移量 比如:设置成10 就是从报文的第11个字节开始就是代表长度
  • 第三个:长度占用字节 因为这个解码器类似于协议,报文开头需要指定报文的字节数 如:20加入了连接!__,20就是报文的长度,后面的内容就是报文真实数据,如果这个20是int类型那就是4个字节,long类型就是8个字节
  • 第四个:长度字节后的偏移量 比如:设置成10,就是从去掉长度后的报文第11个字节开始才是真实数据
  • 第五个:剥离字节数 比如:设置成4 就是从报文的第5个字节开始就是报文的真实数据(ps:因为前四个字节是长度,所以建议剥离)
// 最大长度,长度偏移,长度占用字节,长度调整,剥离字节数
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));

客户端:

public static void main(String[] args) {
        EventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            // 创建客户端启动对象
            ChannelFuture channelFuture = new Bootstrap()
                    .group(eventExecutors) // 设置线程组
                    .channel(NioSocketChannel.class) //设置 客户端通道的实现类
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new StringEncoder()); // 解码器
                        }
                    })
                    .connect("localhost", 8000);
            channelFuture.addListener(new ChannelFutureListener() {
                public void operationComplete(ChannelFuture future) {
                    Channel channel = future.channel();
                    for (int i = 0; i < 20; i++) {
                        byte[] bytes = "加入了连接!".getBytes();
                        ByteBuf buffer = channel.alloc().buffer();
                        buffer.writeInt(bytes.length);
                        buffer.writeBytes(bytes);
                        // 18加入了连接!
                        channel.writeAndFlush(buffer);
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值