【源码解析】NioEventLoop事件处理流程

121 阅读3分钟

一、NioEventLoop

image
image
  1. 循环处理客户端的请求以及channel的就绪事件,通过Unsafe触发pipeline的链式处理
  2. 它是基于select的实现,下图是NioEventLoop的内部处理的结构图
  3. 事件处理的启动入口是execute方法(引导类或者其他地方都会有调用)
image
image

1、accept事件处理

accept事件处理

2、read事件处理

  1. 相对来说,read事件的处理逻辑要比accept事件简单一点;下图是简单的read事件的处理流程图
  2. 在NioEventLoop中read时会调用NioSocketChannel中Unsafe的read方法去处理,下面从这里开始分析
image
image

2.1 NioByteUnsafe.read

2.1.1 NioSocketChannelUnsafe

  1. 由于NioEventLoop将接收到的SocketChannel封装成了NioSocketChannel,所以这里的unsafe是NioSocketChannel的内部类NioSocketChannelUnsafe
  2. 真正的read方法就是在NioSocketChannelUnsafe的父类NioByteUnsafe中
private final class NioSocketChannelUnsafe extends NioByteUnsafe {
    @Override
    protected Executor prepareToClose() {
        try {
            if (javaChannel().isOpen() && config().getSoLinger() > 0) {
                // We need to cancel this key of the channel so we may not end up in a eventloop spin
                // because we try to read or write until the actual close happens which may be later due
                // SO_LINGER handling.
                // See https://github.com/netty/netty/issues/4449
                doDeregister();
                return GlobalEventExecutor.INSTANCE;
            }
        } catch (Throwable ignore) {
            // Ignore the error as the underlying channel may be closed in the meantime and so
            // getSoLinger() may produce an exception. In this case we just return null.
            // See https://github.com/netty/netty/issues/4449
        }
        return null;
    }
}

2.1.2 NioByteUnsafe.read

  1. 通过ByteBufAllocator创建ByteBuf
  2. 通过NioSocketChannel的doReadBytes方法将socket数据读取到ByteBuf中
  3. 触发pipeline的fireChannelRead和fireChannelReadComplete方法
protected class NioByteUnsafe extends AbstractNioUnsafe {
    ...
    @Override
    public final void read() {
        final ChannelConfig config = config();
        if (shouldBreakReadReady(config)) {
            clearReadPending();
            return;
        }
        final ChannelPipeline pipeline = pipeline();
        // 获取ByteBuf的内存分配器,其实底层还是nio的ByteBuffer
        final ByteBufAllocator allocator = config.getAllocator();
        // RecvByteBufAllocator处理输入字节数过大和过小的情况,拆包?数据过大的处理?
        final RecvByteBufAllocator.Handle allocHandle = recvBufAllocHandle();
        allocHandle.reset(config);

        ByteBuf byteBuf = null;
        boolean close = false;
        try {
            do {
                // 创建ByteBuf,详见ByteBuf分析
                byteBuf = allocHandle.allocate(allocator);
                // doReadBytes是将Socket的字节数据读取到byteBuf中
                // 设置上次读取的字节数,为下次读取数据时分配字节数提供参考
                allocHandle.lastBytesRead(doReadBytes(byteBuf));
                if (allocHandle.lastBytesRead() <= 0) {
                    // nothing was read. release the buffer.
                    byteBuf.release();
                    byteBuf = null;
                    close = allocHandle.lastBytesRead() < 0;
                    if (close) {
                        // There is nothing left to read as we received an EOF.
                        readPending = false;
                    }
                    break;
                }

                allocHandle.incMessagesRead(1);
                readPending = false;
                pipeline.fireChannelRead(byteBuf);
                byteBuf = null;
            // continueReading用来判断socket数据是否已经读完,是根据ByteBuf可读字节数和本次读取的字节数来判断
            } while (allocHandle.continueReading());

            // 计算下一次分配的ByteBuf的空间大小
            allocHandle.readComplete();
            pipeline.fireChannelReadComplete();

            if (close) {
                closeOnRead(pipeline);
            }
        } catch (Throwable t) {
            handleReadException(pipeline, byteBuf, t, close, allocHandle);
        } finally {
            // Check if there is a readPending which was not processed yet.
            // This could be for two reasons:
            // * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
            // * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
            //
            // See https://github.com/netty/netty/issues/2254
            if (!readPending && !config.isAutoRead()) {
                removeReadOp();
            }
        }
    }
}

2.2 NioSocketChannel.doReadBytes

  1. 设置byteBuf可以读入的字节数,用于后面判断是否读取完数据
  2. 调用AbstractByteBuf读取socket数据
@Override
protected int doReadBytes(ByteBuf byteBuf) throws Exception {
    final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
    // 设置byteBuf可以读入的字节数,用于后面判断是否读取完数据
    allocHandle.attemptedBytesRead(byteBuf.writableBytes());
    // 将socket的数据写入到byteBuf中,底层调用的Nio的ByteBuffer
    return byteBuf.writeBytes(javaChannel(), allocHandle.attemptedBytesRead());
}

2.2.1 AbstractByteBuf.writeBytes

  1. 返回本次读取的字节数
@Override
public int writeBytes(ScatteringByteChannel in, int length) throws IOException {
    ensureWritable(length);
    int writtenBytes = setBytes(writerIndex, in, length);
    if (writtenBytes > 0) {
        writerIndex += writtenBytes;
    }
    return writtenBytes;
}

@Override
public final int setBytes(int index, ScatteringByteChannel in, int length) throws IOException {
    try {
        // internalNioBuffer返回nio的ByteBuffer
        return in.read(internalNioBuffer(index, length));
    } catch (ClosedChannelException ignored) {
        return -1;
    }
}

3、write事件处理

  1. 一般write事件都是在用户自定义的handler中,比如用户在最后一个channelHandler的channelRead中使用write,那么就会逐个往前调用channelHandler的channelWrite方法
class MyHandler implements ChannelInboundHandler {
    ...
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 将客户端数据反写回去
        ctx.write(msg);
    }
    ...
}