抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(七)
7. 向SubReactorGroup中注册NioSocketChannel
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
客户端NioSocketChannel向Sub Reactor Group注册的流程完全和服务端NioServerSocketChannel向Main Reactor Group注册流程一样。
关于服务端NioServerSocketChannel的注册流程,笔者已经在?《详细图解Netty Reactor启动全流程》一文中做出了详细的介绍,对相关细节感兴趣的同学可以在回看下。
这里笔者在带大家简要回顾下整个注册过程并着重区别对比客户端NioSocetChannel与服务端NioServerSocketChannel注册过程中不同的地方。
7.1 从Sub Reactor Group中选取一个Sub Reactor进行绑定
public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {
@Override
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
@Override
public EventExecutor next() {
return chooser.next();
}
}
7.2 向绑定的Sub Reactor上注册NioSocketChannel
public abstract class SingleThreadEventLoop extends SingleThreadEventExecutor implements EventLoop {
@Override
public ChannelFuture register(Channel channel) {
//注册channel到绑定的Reactor上
return register(new DefaultChannelPromise(channel, this));
}
@Override
public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
//unsafe负责channel底层的各种操作
promise.channel().unsafe().register(this, promise);
return promise;
}
}
•当时我们在介绍NioServerSocketChannel的注册过程时,这里的promise.channel()为NioServerSocketChannel。底层的unsafe操作类为NioMessageUnsafe。
•此时这里的promise.channel()为NioSocketChannel。底层的unsafe操作类为NioByteUnsafe。
@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
..............省略....................
//此时这里的eventLoop为Sub Reactor
AbstractChannel.this.eventLoop = eventLoop;
/**
* 执行channel注册的操作必须是Reactor线程来完成
*
* 1: 如果当前执行线程是Reactor线程,则直接执行register0进行注册
* 2:如果当前执行线程是外部线程,则需要将register0注册操作 封装程异步Task 由Reactor线程执行
* */
if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
..............省略....................
}
}
}
注意此时传递进来的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不同的地方。
private void register0(ChannelPromise promise) {
try {
................省略..................
boolean firstRegistration = neverRegistered;
//执行真正的注册操作
doRegister();
//修改注册状态
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
if (isActive()) {
if (firstRegistration) {
//触发channelActive事件
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
................省略..................
}
}
这里 doRegister()方法将NioSocketChannel注册到Sub Reactor中的Selector上。
private void register0(ChannelPromise promise) {
try {
................省略..................
boolean firstRegistration = neverRegistered;
//执行真正的注册操作
doRegister();
//修改注册状态
neverRegistered = false;
registered = true;
pipeline.invokeHandlerAddedIfNeeded();
if (isActive()) {
if (firstRegistration) {
//触发channelActive事件
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
beginRead();
}
}
} catch (Throwable t) {
................省略..................
}
}
这里是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
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
if (initChannel(ctx)) {
//初始化工作完成后,需要将自身从pipeline中移除
removeState(ctx);
}
}
}
protected abstract void initChannel(C ch) throws Exception;
}
关于对Channel中pipeline的详细初始化过程,对细节部分感兴趣的同学可以回看下?《详细图解Netty Reactor启动全流程》
此时客户端NioSocketChannel中的pipeline中的结构就变为了我们自定义的样子,在示例代码中我们自定义的ChannelHandler为EchoServerHandler。
客户端channel pipeline结构.png
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.write(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
当客户端NioSocketChannel中的pipeline初始化完毕后,netty就开始调用safeSetSuccess(promise)方法回调regFuture中注册的ChannelFutureListener,通知客户端NioSocketChannel已经成功注册到Sub Reactor上了。
childGroup.register(child).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
forceClose(child, future.cause());
}
}
});
在服务端NioServerSocketChannel注册的时候我们会在listener中向Main Reactor提交bind绑定端口地址任务。但是在NioSocketChannel注册的时候,只会在listener中处理一下注册失败的情况。
当Sub Reactor线程通知ChannelFutureListener注册成功之后,随后就会调用pipeline.fireChannelRegistered()在客户端NioSocketChannel的pipeline中传播ChannelRegistered事件。
传播ChannelRegister事件.png
这里笔者重点要强调下,在之前介绍NioServerSocketChannel注册的时候,我们提到因为此时NioServerSocketChannel并未绑定端口地址,所以这时的NioServerSocketChannel并未激活,这里的isActive()返回false。register0方法直接返回。
服务端NioServerSocketChannel判断是否激活的标准为端口是否绑定成功。
public class NioServerSocketChannel extends AbstractNioMessageChannel
implements io.netty.channel.socket.ServerSocketChannel {
@Override
public boolean isActive() {
return isOpen() && javaChannel().socket().isBound();
}
}
客户端NioSocketChannel判断是否激活的标准为是否处于Connected状态。那么显然这里肯定是处于connected状态的。
@Override
public boolean isActive() {
SocketChannel ch = javaChannel();
return ch.isOpen() && ch.isConnected();
}
NioSocketChannel已经处于connected状态,这里并不需要绑定端口,所以这里的isActive()返回true。
if (isActive()) {
/**
* 客户端SocketChannel注册成功后会走这里,在channelActive事件回调中注册OP_READ事件
* */
if (firstRegistration) {
//触发channelActive事件
pipeline.fireChannelActive();
} else if (config().isAutoRead()) {
.......省略..........
}
}
}
最后调用pipeline.fireChannelActive()在NioSocketChannel中的pipeline传播ChannelActive事件,最终在pipeline的头结点HeadContext中响应并注册OP_READ事件到Sub Reactor中的Selector上。
传播ChannelActive事件.png
public abstract class AbstractNioChannel extends AbstractChannel { {
@Override
protected void doBeginRead() throws Exception {
..............省略................
final int interestOps = selectionKey.interestOps();
/**
* 1:ServerSocketChannel 初始化时 readInterestOp设置的是OP_ACCEPT事件
* 2:SocketChannel 初始化时 readInterestOp设置的是OP_READ事件
* */
if ((interestOps & readInterestOp) == 0) {
//注册监听OP_ACCEPT或者OP_READ事件
selectionKey.interestOps(interestOps | readInterestOp);
}
}
}
注意这里的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是如何接收网络数据的~~~~ 我们下篇文章见~~