Netty服务端源码阅读笔记(六)服务端拆包粘包和解决方法代码示例

58 篇文章 0 订阅
16 篇文章 0 订阅

Netty服务端源码阅读笔记(五)AdaptiveRecvByteBufAllocator

上篇在看服务端读NioSocketChannel的源码的时候,读入时的缓冲区ByteBuf是由AdaptiveRecvByteBufAllocator对象动态调整的,那么存在拆包粘包问题

目录

1、示例

1.1、粘包示例

2.2、拆包示例

2、解决方法

2.1 LengthFieldBasedFrameDecoder  

2.2 DelimiterBasedFrameDecoder  

2.3 FixedLengthFrameDecoder  

2.4 LineBasedFrameDecoder 


 

1、示例

1.1、粘包示例

客户端Main代码:

public class Client {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new ClientHandler());
                        }
                    });

            ChannelFuture future = bootstrap.connect("localhost", 8888).sync();
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

客户端handler代码:

public class ClientHandler extends ChannelInboundHandlerAdapter {
    private int count = 1;
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        ByteBuf buffer = null;
        for(int i=0; i<100; i++) {
            String message = "=Hello "+ count++ +" World=";
            byte[] bytes = message.getBytes();
            // 申请缓存空间buff
            buffer = Unpooled.buffer(bytes.length);
            // 将数据写入到缓存
            buffer.writeBytes(bytes);
            // 将缓存中的数据写入到Channel
            ctx.writeAndFlush(buffer);
        }
    }
}

服务端Main代码:

public class Server {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup parentGroup = new NioEventLoopGroup();
        NioEventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(parentGroup, childGroup)
                     .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new ServerHandler());
                        }
                    });
            ChannelFuture future = bootstrap.bind(8888).sync();
            System.out.println("服务器已启动");
            future.channel().closeFuture().sync();
        } finally {
            parentGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }
}

服务端handler代码:

public class ServerHandler extends SimpleChannelInboundHandler<String> {

    private int counter;
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("第【" + ++counter + "】个数据包:" + msg);
    }
}

执行结果:每次都不太一样

 

2.2、拆包示例

只改动客户端handler代码

   @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        StringBuffer bf = new StringBuffer();

        // 1024个s
        for (int i = 0; i < 1024; i++) {
            bf.append("s");
        }
        // + 1个d
        bf.append("d");

        byte[] bytes = bf.toString().getBytes();
        ByteBuf buffer = null;
        // 申请缓存空间
        buffer = Unpooled.buffer(bytes.length);
        // 将数据写入到缓存
        buffer.writeBytes(bytes);
        // 将缓存中的数据写入到Channel
        ctx.writeAndFlush(buffer);
    }

消息为1024个s和一个d的字符串,因为上篇看源码,服务端接收消息时初始byteBuf大小是1024,预想情况应该是服务端输出两次,一次1024个s,一次一个d

执行结果和预想的一致

如果放2049个字符,是不会读三次的哈,因为第一次发现没读完,缓冲区就扩容了,扩2*4倍到16384,测试一下

  @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        StringBuffer bf = new StringBuffer();
        int size = 16384 + 1024;
        for (int i = 0; i < size; i++) {
            bf.append("s");
        }
        bf.append("d");

        byte[] bytes = bf.toString().getBytes();
        ByteBuf buffer = null;
        // 申请缓存空间
        buffer = Unpooled.buffer(bytes.length);
        // 将数据写入到缓存
        buffer.writeBytes(bytes);
        // 将缓存中的数据写入到Channel
        ctx.writeAndFlush(buffer);
    }

执行结果很完美:

 

客户端ctx.writeAndFlush(msg)的一次消息msg过大,会被拆分,小了服务端读到的就是客户端多次写的消息,如何让服务端每次读到的数据和客户端每次writeAndFlush()是一致的呢

2、解决方法

Netty 中已经定义好了很多的接收方粘包拆包解决方案,即在发送消息时,客户端给消息添加某种标记,服务端根据这个标记来进行组装数据,拆分或者合并
 

2.1 LengthFieldBasedFrameDecoder  

基于长度域的帧解码器,用于对 LengthFieldPrepender 编码器编码后的数据进行解码的

本文1中示例粘包和拆包代码,只修改客户端Main和服务端Main

客户端Main:输出处理器增加LengthFieldPrepender,为消息加上长度信息供服务端识别,因为出去的消息调用handler加工是从后往前依次处理,所以得第一个添加

服务端Main:输入处理器头增加LengthFieldBasedFrameDecoder解码器,进来的从前往后,也加在第一个

public class Client3 {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new LengthFieldPrepender(4));
                            pipeline.addLast(new SomeClientHandler());
                        }
                    });

            ChannelFuture future = bootstrap.connect("localhost", 8888).sync();
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}


public class Server3 {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup parentGroup = new NioEventLoopGroup();
        NioEventLoopGroup childGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(parentGroup, childGroup)
                     .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(1029, 0, 4, 0, 4));
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new SomeServerHandler());
                        }
                    });
            ChannelFuture future = bootstrap.bind(8888).sync();
            System.out.println("服务器已启动");
            future.channel().closeFuture().sync();
        } finally {
            parentGroup.shutdownGracefully();
            childGroup.shutdownGracefully();
        }
    }

LengthFieldPrepender,构造方法参数4表示为消息加上四位的长度标识

LengthFieldBasedFrameDecoder服务端解码,有个1029的参数表示能接收的最大消息长度,这里的1029是随便设的,为了测试结果方便,示例拆包代码里边1024个s加1个d再加4位信息标识 = 1029。超过长度会报错

执行结果:

 把消息加长了一位s  超长报错:

LengthFieldBasedFrameDecoder  源码分析:Netty服务端源码阅读笔记(六)服务端拆包粘包--LengthFieldBasedFrameDecoder  

2.2 DelimiterBasedFrameDecoder  

基于分隔符的帧解码器,即会按照指定分隔符 对数据进行拆包粘包
 
示例代码,修改客户端handler和服务端Main
客户端handler发送消息每次消息后增加分隔符,我定义了分割符是###
服务端Main增加 DelimiterBasedFrameDecoder处理器,定义这个分隔符是啥,最大每次长度设为了1024,我想这种大小是不是生产都设置为Integer.Max
  ByteBuf delimiter = Unpooled.copiedBuffer("###".getBytes());
  pipeline.addLast(new DelimiterBasedFrameDecoder(1024, delimiter));

执行结果:

 

2.3 FixedLengthFrameDecoder  

固定长度帧解码器,即会按照指定的长度对 数据进行拆粘包
 
示例拆包粘包代码只需修改服务端Main,handler开头增加
pipeline.addLast(new FixedLengthFrameDecoder(1024));

固定长度1024,对于示例粘包代码来说,没解决问题,还必须是读到这个长度才调用一次。得设置为  15才能一一对应,感觉不太方便,不看了

 
 
 

2.4 LineBasedFrameDecoder 

 
基于行的帧解码器,即会按照行分隔符对 数据进行拆包粘包
 
 
示例拆包粘包代码,修改客户端Handler和服务端Main
客户端Handler,消息后增加行分隔符  (1026长度的字符串)
服务端Main中增加 LineBasedFrameDecoder处理器
 
 
执行结果:
 
参数中最大长度设了2048,超长会报错,把参数改为1024测试执行如下:
 
 
 
 
 
 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值