Netty如何高效接收网络数据?聊透ByteBuffer动态自适应扩缩 五
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递增。
image.png
•当索引容量大于512时,SIZE_TABLE中定义的容量索引是按前一个索引容量的2倍递增。