之前的文章当中,我们研究了在客户端发起连接的时候,netty执行了怎样的动作。今天我们来研究一下,客户端发送数据过来的时候,netty具体是怎么执行读取任务的。
仍然是从NioEventLoop开始,还是NioEventLoop.processSelectedKey这个方法,
private static void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
// close the channel if the key is not valid anymore
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
// to a spin loop
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
if (!ch.isOpen()) {
// Connection already closed - no need to handle write.
return;
}
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush();
}
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
} catch (CancelledKeyException e) {
unsafe.close(unsafe.voidPromise());
}
}
红色字体部分,unsafe.read(),进入到AbstractNioByteChannel$NioByteUnsafe,要从io当中读取数据,首先需要分配一个ByteBuf,这里涉及到了2个问题,1、从哪里分配内存,2、每次具体分配多大的内存。首先是第一个问题,首先大家需要了解(allocate和allocateDirect2中方式的不同),具体的实现在 allocator.ioBuffer 当中,会根据平台不同,来决定能否直接分配系统内存。第二个问题,大家可以再看一下 allocHandle.record 方法,这个当中会根据当前从io当中读取的数据量,来决定后续分配内存的大小。然后就是从socket当中读取数据,写入bytebuf,并触发一系列的handler。
public void read() {
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final ByteBufAllocator allocator = config.getAllocator();
final int maxMessagesPerRead = config.getMaxMessagesPerRead();
RecvByteBufAllocator.Handle allocHandle = this.allocHandle;
if (allocHandle == null) {
this.allocHandle = allocHandle = config.getRecvByteBufAllocator().newHandle();
}
if (!config.isAutoRead()) {
removeReadOp();
}
ByteBuf byteBuf = null;
int messages = 0;
boolean close = false;
try {
int byteBufCapacity = allocHandle.guess();
int totalReadAmount = 0;
do {
byteBuf = allocator.ioBuffer(byteBufCapacity);
int writable = byteBuf.writableBytes();
int localReadAmount = doReadBytes(byteBuf);
if (localReadAmount <= 0) {
// not was read release the buffer
byteBuf.release();
close = localReadAmount < 0;
break;
}
pipeline.fireChannelRead(byteBuf);
byteBuf = null;
if (totalReadAmount >= Integer.MAX_VALUE - localReadAmount) {
// Avoid overflow.
totalReadAmount = Integer.MAX_VALUE;
break;
}
totalReadAmount += localReadAmount;
if (localReadAmount < writable) {
// Read less than what the buffer can hold,
// which might mean we drained the recv buffer completely.
break;
}
} while (++ messages < maxMessagesPerRead);
pipeline.fireChannelReadComplete();
allocHandle.record(totalReadAmount);
if (close) {
closeOnRead(pipeline);
close = false;
}
} catch (Throwable t) {
handleReadException(pipeline, byteBuf, t, close);
}
}
到此为止,netty server的read流程基本已经清楚了。
总结:
1、在分配bytebuf的时候,会根据平台的不同,来判断能否直接分配系统内存(对应的是java的heap内存),这里我们了解到了一个概念,unsafe,这是jdk提供的一个用于执行低级别、不安全操作的方法集合。那么这个unsafe和netty当中的unsafe有没有什么关联呢?通过分析代码,我们发现,并没有直接的联系,可能只是一种名称和概念上的借用,在netty当中,把对系统io的操作,视做不安全的,并把这一系列不安全的操作进行了分装。我们在netty当中找到了这个文件,io.netty.channel.Channel,这个文件当中同时定义了2个interface, Channel和Unsafe,也就是说,从根子上来讲,unsafe和channel就是一同出现的。
2、在之前的一系列文章当中,我们从代码级别,分析了从netty server启动,到接收客户端数据的一系列动作。到目前为止,netty具体是怎么工作的,我们基本已经清楚了。