
抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(七)
7. 向SubReactorGroup中注册NioSocketChannel
客户端NioSocketChannel向Sub Reactor Group注册的流程完全和服务端NioServerSocketChannel向Main Reactor Group注册流程一样。
关于服务端NioServerSocketChannel的注册流程,笔者已经在?《详细图解Netty Reactor启动全流程》一文中做出了详细的介绍,对相关细节感兴趣的同学可以在回看下。
这里笔者在带大家简要回顾下整个注册过程并着重区别对比客户端NioSocetChannel与服务端NioServerSocketChannel注册过程中不同的地方。
7.1 从Sub Reactor Group中选取一个Sub Reactor进行绑定
7.2 向绑定的Sub Reactor上注册NioSocketChannel
•当时我们在介绍NioServerSocketChannel的注册过程时,这里的promise.channel()为NioServerSocketChannel。底层的unsafe操作类为NioMessageUnsafe。
•此时这里的promise.channel()为NioSocketChannel。底层的unsafe操作类为NioByteUnsafe。
注意此时传递进来的EventLoop eventLoop为Sub Reactor。
但此时的执行线程为Main Reactor线程,并不是Sub Reactor线程(此时还未启动)。
所以这里的eventLoop.inEventLoop()返回的是false。
image.png
在else分支中向绑定的Sub Reactor提交注册NioSocketChannel的任务。
当注册任务提交后,此时绑定的Sub Reactor线程启动。
7.3 register0
我们又来到了Channel注册的老地方register0方法。在?《详细图解Netty Reactor启动全流程》中我们花了大量的篇幅介绍了这个方法。这里我们只对比NioSocketChannel与NioServerSocketChannel不同的地方。
这里 doRegister()方法将NioSocketChannel注册到Sub Reactor中的Selector上。
这里是Netty客户端NioSocketChannel与JDK NIO 原生 SocketChannel关联的地方。此时注册的IO事件依然是0。目的也是只是为了获取NioSocketChannel在Selector中的SelectionKey。
同时通过SelectableChannel#register方法将Netty自定义的NioSocketChannel(这里的this指针)附着在SelectionKey的attechment属性上,完成Netty自定义Channel与JDK NIO Channel的关系绑定。这样在每次对Selector进行IO就绪事件轮询时,Netty 都可以从 JDK NIO Selector返回的SelectionKey中获取到自定义的Channel对象(这里指的就是NioSocketChannel)。
channel与SelectionKey对应关系.png
随后调用pipeline.invokeHandlerAddedIfNeeded()回调客户端NioSocketChannel上pipeline中的所有ChannelHandler的handlerAdded方法,此时pipeline的结构中只有一个ChannelInitializer。最终会在ChannelInitializer#handlerAdded回调方法中初始化客户端NioSocketChannel的pipeline。
客户端channel pipeline初始结构.png
关于对Channel中pipeline的详细初始化过程,对细节部分感兴趣的同学可以回看下?《详细图解Netty Reactor启动全流程》
此时客户端NioSocketChannel中的pipeline中的结构就变为了我们自定义的样子,在示例代码中我们自定义的ChannelHandler为EchoServerHandler。
客户端channel pipeline结构.png
当客户端NioSocketChannel中的pipeline初始化完毕后,netty就开始调用safeSetSuccess(promise)方法回调regFuture中注册的ChannelFutureListener,通知客户端NioSocketChannel已经成功注册到Sub Reactor上了。
在服务端NioServerSocketChannel注册的时候我们会在listener中向Main Reactor提交bind绑定端口地址任务。但是在NioSocketChannel注册的时候,只会在listener中处理一下注册失败的情况。
当Sub Reactor线程通知ChannelFutureListener注册成功之后,随后就会调用pipeline.fireChannelRegistered()在客户端NioSocketChannel的pipeline中传播ChannelRegistered事件。
传播ChannelRegister事件.png
这里笔者重点要强调下,在之前介绍NioServerSocketChannel注册的时候,我们提到因为此时NioServerSocketChannel并未绑定端口地址,所以这时的NioServerSocketChannel并未激活,这里的isActive()返回false。register0方法直接返回。
服务端NioServerSocketChannel判断是否激活的标准为端口是否绑定成功。
客户端NioSocketChannel判断是否激活的标准为是否处于Connected状态。那么显然这里肯定是处于connected状态的。
NioSocketChannel已经处于connected状态,这里并不需要绑定端口,所以这里的isActive()返回true。
最后调用pipeline.fireChannelActive()在NioSocketChannel中的pipeline传播ChannelActive事件,最终在pipeline的头结点HeadContext中响应并注册OP_READ事件到Sub Reactor中的Selector上。
传播ChannelActive事件.png
注意这里的readInterestOp为客户端NioSocketChannel在初始化时设置的OP_READ事件。
到这里,Netty中的Main Reactor接收连接的整个流程,我们就介绍完了,此时Netty中主从Reactor组的结构就变为:
主从Reactor组完整结构.png
总结
本文我们介绍了NioServerSocketChannel处理客户端连接事件的整个过程。
•接收连接的整个处理框架。
•影响Netty接收连接吞吐的Bug产生的原因,以及修复的方案。
•创建并初始化客户端NioSocketChannel。
•初始化NioSocketChannel中的pipeline。
•客户端NioSocketChannel向Sub Reactor注册的过程
其中我们也对比了NioServerSocketChannel与NioSocketChannel在创建初始化以及后面向Reactor注册过程中的差异之处。
当客户端NioSocketChannel接收完毕并向Sub Reactor注册成功后,那么接下来Sub Reactor就开始监听注册其上的所有客户端NioSocketChannel的OP_READ事件,并等待客户端向服务端发送网络数据。
后面Reactor的主角就该变为Sub Reactor以及注册在其上的客户端NioSocketChannel了。
下篇文章,我们将会讨论Netty是如何接收网络数据的~~~~ 我们下篇文章见~~
