Netty如何高效接收网络数据?聊透ByteBuffer动态自适应扩缩 五

发布于 2022-7-28 17:52
浏览
0收藏

3.2 从NioSocketChannel中读取数据

public class NioSocketChannel extends AbstractNioByteChannel implements io.netty.channel.socket.SocketChannel {

    @Override
    protected int doReadBytes(ByteBuf byteBuf) throws Exception {
        final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
        allocHandle.attemptedBytesRead(byteBuf.writableBytes());    
        return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
    }
}

这里会直接调用底层JDK NIO的SocketChannel#read方法将数据读取到DirectByteBuffer中。读取数据大小为本次分配的DirectByteBuffer容量,初始为2048。

 

4. ByteBuffer动态自适应扩缩容机制


由于我们一开始并不知道客户端会发送多大的网络数据,所以这里先利用PooledByteBufAllocator分配一个初始容量为2048的DirectByteBuffer用于接收数据。

 byteBuf = allocHandle.allocate(allocator);

这就好比我们需要拿着一个桶去排队装水,但是第一次去装的时候,我们并不知道管理员会给我们分配多少水,桶拿大了也不合适拿小了也不合适,于是我们就先预估一个差不多容量大小的桶,如果分配的多了,我们下次就拿更大一点的桶,如果分配少了,下次我们就拿一个小点的桶。

 

在这种场景下,我们需要ByteBuffer可以自动根据每次网络数据的大小来动态自适应调整自己的容量。

 

而ByteBuffer动态自适应扩缩容机制依赖于AdaptiveRecvByteBufAllocator类的实现。让我们先回到AdaptiveRecvByteBufAllocator类的创建起点开始说起~~

 

4.1 AdaptiveRecvByteBufAllocator的创建


在前文?《Netty是如何高效接收网络连接》中我们提到,当Main Reactor监听到OP_ACCPET事件活跃后,会在NioServerSocketChannel中accept完成三次握手的客户端连接。并创建NioSocketChannel,伴随着NioSocketChannel的创建其对应的配置类NioSocketChannelConfig类也会随之创建。

    public NioSocketChannel(Channel parent, SocketChannel socket) {
        super(parent, socket);
        config = new NioSocketChannelConfig(this, socket.socket());
    }

最终会在NioSocketChannelConfig的父类DefaultChannelConfig的构造器中创建AdaptiveRecvByteBufAllocator。并保存在RecvByteBufAllocator rcvBufAllocator字段中。

public class DefaultChannelConfig implements ChannelConfig {

    //用于Channel接收数据用的buffer分配器  AdaptiveRecvByteBufAllocator
    private volatile RecvByteBufAllocator rcvBufAllocator;

    public DefaultChannelConfig(Channel channel) {
            this(channel, new AdaptiveRecvByteBufAllocator());
    }

}

new AdaptiveRecvByteBufAllocator()创建AdaptiveRecvByteBufAllocator类实例的时候会先触发AdaptiveRecvByteBufAllocator类的初始化。

 

我们先来看下AdaptiveRecvByteBufAllocator类的初始化都做了些什么事情:

 

4.2 AdaptiveRecvByteBufAllocator类的初始化

public class AdaptiveRecvByteBufAllocator extends DefaultMaxMessagesRecvByteBufAllocator {

    //扩容步长
    private static final int INDEX_INCREMENT = 4;
    //缩容步长
    private static final int INDEX_DECREMENT = 1;

    //RecvBuf分配容量表(扩缩容索引表)按照表中记录的容量大小进行扩缩容
    private static final int[] SIZE_TABLE;

   static {
        //初始化RecvBuf容量分配表
        List<Integer> sizeTable = new ArrayList<Integer>();
        //当分配容量小于512时,扩容单位为16递增
        for (int i = 16; i < 512; i += 16) {
            sizeTable.add(i);
        }

        //当分配容量大于512时,扩容单位为一倍
        for (int i = 512; i > 0; i <<= 1) {
            sizeTable.add(i);
        }

        //初始化RecbBuf扩缩容索引表
        SIZE_TABLE = new int[sizeTable.size()];
        for (int i = 0; i < SIZE_TABLE.length; i ++) {
            SIZE_TABLE[i] = sizeTable.get(i);
        }
    }
}

AdaptiveRecvByteBufAllocator 主要的作用就是为接收数据的ByteBuffer进行扩容缩容,那么每次怎么扩容?扩容多少?怎么缩容?缩容多少呢??

 

这四个问题将是本小节笔者要为大家解答的内容~~~

 

Netty中定义了一个int型的数组SIZE_TABLE来存储每个扩容单位对应的容量大小。建立起扩缩容的容量索引表。每次扩容多少,缩容多少全部记录在这个容量索引表中。

 

在AdaptiveRecvByteBufAllocatorl类初始化的时候会在static{}静态代码块中对扩缩容索引表SIZE_TABLE进行初始化。

 

从源码中我们可以看出SIZE_TABLE的初始化分为两个部分:

 

•当索引容量小于512时,SIZE_TABLE中定义的容量索引是从16开始按16递增。

Netty如何高效接收网络数据?聊透ByteBuffer动态自适应扩缩 五-开源基础软件社区 image.png


•当索引容量大于512时,SIZE_TABLE中定义的容量索引是按前一个索引容量的2倍递增。

Netty如何高效接收网络数据?聊透ByteBuffer动态自适应扩缩 五-开源基础软件社区

标签
已于2022-7-28 17:52:26修改
收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐