Netty接收请求源码剖析
——基于 Netty 4.1.39
文章目录
在前面的服务器启动源码分析中,可以得知:服务器端的 NioServerSocketChannel 实例将自己注册到了 bossGroup 上(讲得更细一些,是 bossGroup 中 EventLoop 的 Selector 上),监听客户端连接。
Netty 服务端接收客户端连接请求的总体流程为:监听 Accept 事件,接受连接–>创建一个新的 NioSocketChannel–>将新的 NioSocketChannel 注册到 workerGroup 上–>监听 NioSocketChannel 上的 Read 事件。下面追踪代码来验证这一过程。
一、监听accept事件,接受连接 & 创建一个NioSocketChannel
- 前面说过,NioEventLoop 中的run方法死循环,会不断执行以下三个过程:
- select:轮训注册在其中的 Selector 上的 Channel 的 IO 事件。
- processSelectedKeys:在对应的 Channel 上处理 IO 事件。
- runAllTasks:再去以此循环处理任务队列中的其他任务。
1、Debug processSelectedKeys()
PS: (本次仅以服务端接收客户端请求角度分析)
以 Debug 模式启动 Server 端,然后将断点放在 NioEventLoop 中 run 方法里面死循环代码块的 processSelectedKeys()语句上。再以 Run 模式启动 客户端。追踪服务端代码的执行,过程如下:
unsafe.read() 源码追踪:
实际调用的是 AbstractNioMessageChannel$NioMessageUnsafe.read 方法。NioMessageUnsafe 是一个定义在 AbstractNioMessageChannel 中的内部类。
// NioMessageUnsafe 定义 private final class NioMessageUnsafe extends AbstractNioUnsafe { // 可以看做存放请求数据的容器 private final List<Object> readBuf = new ArrayList<Object>(); @Override public void read() { //... } }
read() 源码:
@Override public void read() { assert eventLoop().inEventLoop();//(1)该断言用于检查当前线程是否在该eventLoop中 final ChannelConfig config = config(); final ChannelPipeline pipeline = pipeline(); final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle(); allocHandle.reset(config); boolean closed = false; Throwable exception = null; try { try { do { int localRead = doReadMessages(readBuf);//(2)doReadMessages用于读取bossGroup中EventLoop中的NioServerSocketChannel接收到的请求数据,并且把这些请求数据放入readBuf。调用doReadMessages结束后,readBuf中存放了一个处理客户端后续请求的NioSocketChannel。 if (localRead == 0) { break; } if (localRead < 0) { closed = true; break; } allocHandle.incMessagesRead(localRead); } while (allocHandle.continueReading()); } catch (Throwable t) { exception = t; } int size = readBuf.size(); for (int i = 0; i < size; i ++) { readPending = false; pipeline.fireChannelRead(readBuf.get(i));//(3)这个fireChannelRead会依次触发服务端的NioServerSocketChannel的pipeline中所有入站Handler中channelRead()方法的执行。例如LoggingHandler中channelRead方法的执行会打印出日志。同时这里会触发ServerBootstrapAcceptor的channelRead()方法。详细见2.2节内容。 } readBuf.clear(); allocHandle.readComplete(); pipeline.fireChannelReadComplete(); if (exception != null) { closed = closeOnReadError(exception); pipeline.fireExceptionCaught(exception); } if (closed) { inputShutdown = true; if (isOpen()) { close(voidPromise()); } } } 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(); } } }
- (1)该断言用于检查当前线程是否在该eventLoop中。
- (2)doReadMessages用于读取bossGroup中EventLoop中的NioServerSocketChannel接收到的请求数据,并且把这些请求数据放入readBuf。调用doReadMessages结束后,readBuf中存放了一个处理客户端后续请求的NioSocketChannel。
- (3)这个fireChannelRead会依次触发服务端的NioServerSocketChannel的pipeline中所有入站Handler中channelRead()方法的执行。例如LoggingHandler中channelRead方法的执行会打印出日志。同时这里会触发ServerBootstrapAcceptor的channelRead()方法。详细见2.2节内容。
2、doReadMessages(List<Object> buf)
定义:
/** * Read messages into the given array and return the amount which was read. */ protected abstract int doReadMessages(List<Object> buf) throws Exception;
源码追踪:
NioServerSocketChannel 实现了 doReadMessages(List<Object> buf) 方法。
- SocketUtils.accept(javaChannel()):调用服务端ServerSocketChannel的accept()方法产生一个处理客户端后续请求的SocketChannel。
- new NioSocketChannel(Channel parent, SocketChannel socket):将这个 SocketChannel 对象封装成 NioSocketChannel 对象,并且添加到buf容器中。封装的时候会添加 Pipeline 等成分,同时设置了 readInterestOp 等属性。参考前面通过 channelFactory.newChannel() 创建 NioServerSocketChannel 的过程。
二、将NioSocketChannel注册到workerGroup
前面提到 fireChannelRead() 方法会触发 ServerBootstrapAcceptor 的 channelRead() 方法。
1、ServerBootstrapAcceptor.channelRead()
源码追踪:
- 在 ServerBootstrapAcceptor.channelRead() 方法中,会对每个客户端的 NioSocketChannel 进行初始化。可以结合 1.3.1 节的内容(服务端 NioServerSocketChannel 的初始化)进行比较。
2、SingleThreadEventLoop.register()
源码追踪:
- 在 SingleThreadEventLoop.register() 方法中,会通过 register0() 方法注册到 workerGroup 其中一个 eventLoop 的 selector。(ops: 0 在这里猜测是ready的意思,因为后面会在 AbstractNioChannel.doBeginRead() 方法中真正设置key感兴趣的ops)。参考 1.3.1 节的内容(服务端 NioServerSocketChannel 的注册)。
三、监听NioSocketChannel的Read事件
前面提到会在 AbstractNioChannel.doBeginRead() 方法中真正设置NioSocketChannel对应的key感兴趣的ops。
1、AbstractNioChannel.doBeginRead()
源码追踪:
doBeginRead() 方法会在channel首次注册激活或者每次readComplete之后发生(如果开启了isAutoRead,默认是开启的)。需要注意的是,即使读事件发生的时候,readyOps是0,同样可以进行read。
四、总结
- (1)服务器端 bossGroup 中的 EventLoop 轮训 Accept 事件、获取事件后在 processSelectedKey() 方法中调用 unsafe.read()方法,这个 unsafe 是内部类 io.netty.channel.nio.AbstractNioChannel.NioUnsafe 的实例,unsafe.read()方法由两个核心步骤组成:doReadMessages()和 pipeline.fireChannelRead()。
- (2)doReadMessages() 用于创建 NioSocketChannel 对象,包装了 JDK 的 SocketChannel 对象,并且添加了 pipeline、unsafe、config 等成分。
- (3)pipeline.fireChannelRead() 用于触发服务端 NioServerSocketChannel 的所有入站 Handler 的 channelRead() 方法,在其中的一个类型为 ServerBootstrapAcceptor 的入站 Handler 的 channelRead() 方法中将新创建的 NioSocketChannel 对象注册到 workerGroup 中的一个 EventLoop 上,该 EventLoop 开始监听 NioSocketChannel 中的读事件。