Netty框架之编解码机制一(ByteBuf以及Tcp粘包拆包)

Posted 木兮君

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty框架之编解码机制一(ByteBuf以及Tcp粘包拆包)相关的知识,希望对你有一定的参考价值。

前言

继上次文章后小编很久都没有发布文章了,已经一个多月了,小编还是要努力更新的,最近确实比较忙,并且台风烟花也过来了,愿各地灾情早点过去吧!好了话不多说,今天继续我们的netty的编解码机制。在编解码之前,我们先说一下netty的ByteBuf,以及tcp的粘包和拆包。

netty核心组件之ByteBuf

上篇文章中Netty框架之核心组件,主要讲了Netty Channel,ChannelPipeline等重要组件,但是小编忘记了一个、那就是ByteBuf。那小编首先带大家了解一下netty的ByteBuf。讲到ByteBuf大家是否还有印象nio中的ByteBuf。不过netty的ByteBuf不是在nio的ByteBuf上进行封装(这话有点饶),而是重新定义了一个ByteBuf。先看下图来了解一下吧:

小编简单说明:
特性:
1、和nio的bytebuf不一样,他有读写双索引,不像nio的只有一个索引position,读和写都是往前,写好还需要flip进行读取。这样操作跟简单。
2、手动释放回收。
3、复制视图,和源ByteBuf共享一份数据,但是对视图的读写索引进行单独操作不会对源ByteBuf的索引进行改变。
4、自动扩容,相对于nio的ByteBuf一旦确认大小不可修改外,他可以自动扩容,但是不可以超过最大容量。
结构:
1、维护读写索引,默认为0,写数据的时候,写索引往前加,读数据一样往前加,读的区域为写的索引,且判断可读只需要readerIndex < writerIndex。并且可读区域也很明显。如果读取的索引大于写的索引则就会报错。
2、当writerIndex到达一开始设置的capacity时,则会自动扩容,但是扩容不会超过max capacity。

下面小编通过代码来演示一下ByteBuf:

public class ByteBufTest 
    @Test
    public void byteBufRwTest() 
        //声明缓冲区
        int initialCapacity = 5;
        int maxCapacity = 100;
        ByteBuf buffer = Unpooled.buffer(initialCapacity, maxCapacity);
        buffer.writeByte(1);
        buffer.writeByte(2);
        buffer.readByte();
        buffer.readByte();
        //再次读取就报数组越界异常
        try 
            buffer.readByte();
         catch (Exception e) 
            e.printStackTrace();
        

        buffer.writeByte(3);
        buffer.writeByte(4);
        buffer.writeByte(5);
        //回收缓存空间,并且将读索引返回到0,即释放读过的数据
        //并且将345上移到012的索引位置,写索引变成3,则下面的写入扩容不会进行
        //并且不会改变原来写入的数据即3和4的索引位置还是4和5
        buffer.discardReadBytes();
        //不进行上面回收操作,则进行扩容 扩容后为64
        buffer.writeByte(6);
        System.out.println(buffer.capacity());

    
    @Test
    public void copyTest()
        ByteBuf buffer = Unpooled.wrappedBuffer(new byte[]1,2,3,4,5);
        //复制视图
        ByteBuf duplicate = buffer.duplicate();
        //操作视图的读索引不会影响到源buffer
        duplicate.readByte();
        //设置会改变原buffer的值
        duplicate.setByte(4,6);
        //复制可读的缓冲区域
        ByteBuf slice = buffer.slice();
        //复制可读范围索引,范围超出会报错
        ByteBuf slice2 = buffer.slice(1,4);
        //复制一个值并且移动了原来buffer读索引+1
        ByteBuf slice3 = buffer.readSlice(1);
        //完全copy了一份新的byteBuf
        ByteBuf copy = buffer.copy();
    


好了ByteBuf大家知道怎么用了并且了解其特性与数据结构即可,下面小编再来聊聊TCP的拆包和粘包。

TCP拆包与粘包

讲到TCP拆包和粘包问题,也是面试中比较常见的问题,咱们先来看下面这张图:


小编先给大家解释一下上图:
1、红色是一个消息,绿色是一个消息,但是这tcp流式传输的时候,是没有边界的,也就是说他区分不了两个消息,他只会不停的往缓存区里面写,应用(解码处理器)不停的往里面拿数据。
2、粘包即上面在传输过程中红色和绿色在一起,到了缓冲区则需要拆包,即解码处理的时候只读了绿色的消息块。这与udp不一样udp是一个消息一个消息的传输。当然返回来也一样,如果一个消息太大,那tcp传输的过程中就会将消息拆包。

下面小编模拟一下拆包和粘包的过程:
服务端代码如下

public class PacketSplicingTest 
    private ServerBootstrap serverBootstrap;

    @Before
    public void initSocketServer() 
        serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup(5));
        serverBootstrap.channel(NioserverSocketChannel.class);
    

    @Test
    public void splicingTest() throws InterruptedException 

        serverBootstrap.childHandler(new ChannelInitializer<Channel>() 
            @Override
            protected void initChannel(Channel ch) throws Exception 
                ch.pipeline().addLast(new TrackHandler());
            
        );
        ChannelFuture sync = serverBootstrap.bind(8080).sync();
        sync.channel().closeFuture().sync();

    

    private class TrackHandler extends SimpleChannelInboundHandler 
        int count = 0;

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception 
            ByteBuf byteBuf = (ByteBuf) msg;
            String message = byteBuf.toString(Charset.defaultCharset());
            System.out.println(String.format("message%s:%s", ++count, message));
        
    

测试

控制台:

这里很直观的看到粘包和拆包的结果,当然这里小编设置了发送的缓存区和发送的大小。那假如消息出现这样的情况,那我们怎么来解决粘包和拆包的问题呢。

拆包粘包的解决方案

解决粘包拆包的问题,其实最主要的是约定客户端的发送消息,以及服务端解析消息的规则。这样即可,看起来很简单对吧。
1、固定长度:最简单的方式。消息端发送消息发送固定长度,不能大于或小于这个长度,服务端就读取固定长度,这种场景一般是心跳保活场景。假设消息长度不固定则不适用了。
2、消息分割:特殊字符的分割,比方说换行符号拆分。
3、请求头,标示大小:消息分割场景太过单一,万一需要用到特殊字符的时候则很难区分,那么就会产生自定义协议,目前websocket协议,dubbo协议以及http协议都是采用此方法。

小结:

这篇文章出炉的时候和写的时候又过去一个多星期,从郑州的洪水到浙江的台风到现在南京的新冠疫情又严重了,目前是奥运会看得小编很气愤。
上面是题外话啊,下面几篇文章都是会对netty框架的应用,这篇文章比较简单,大家继续打好基础,之后将会是大量的应用场景,包括了http的简易协议怎么处理的,包括dubbo协议等等。让你从网络架构的高层俯瞰如何定义,以及一系列应用场景的编码。小编还是继续努力,不偷懒一直学习下去。

以上是关于Netty框架之编解码机制一(ByteBuf以及Tcp粘包拆包)的主要内容,如果未能解决你的问题,请参考以下文章

Netty框架之编解码机制二(自定义协议)

Netty框架之编解码机制二(自定义协议)

Netty框架之编解码机制二(自定义协议)

Netty之编解码

Netty之启动类编解码器等源码解析及粘包拆包问题

Netty之ByteBuf原理解析及应用