抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(七)

r660926
发布于 2022-8-1 19:30
浏览
0收藏

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

抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(七)-鸿蒙开发者社区

 image.png

 

else分支中向绑定的Sub Reactor提交注册NioSocketChannel的任务。

 

当注册任务提交后,此时绑定的Sub Reactor线程启动。


7.3 register0


我们又来到了Channel注册的老地方register0方法。在?《详细图解Netty Reactor启动全流程》中我们花了大量的篇幅介绍了这个方法。这里我们只对比NioSocketChannelNioServerSocketChannel不同的地方。

 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)。

抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(七)-鸿蒙开发者社区

 channel与SelectionKey对应关系.png


随后调用pipeline.invokeHandlerAddedIfNeeded()回调客户端NioSocketChannel上pipeline中的所有ChannelHandler的handlerAdded方法,此时pipeline的结构中只有一个ChannelInitializer。最终会在ChannelInitializer#handlerAdded回调方法中初始化客户端NioSocketChannelpipeline

抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(七)-鸿蒙开发者社区

 客户端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中的结构就变为了我们自定义的样子,在示例代码中我们自定义的ChannelHandlerEchoServerHandler

抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(七)-鸿蒙开发者社区

 客户端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事件

抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(七)-鸿蒙开发者社区

 传播ChannelRegister事件.png


这里笔者重点要强调下,在之前介绍NioServerSocketChannel注册的时候,我们提到因为此时NioServerSocketChannel并未绑定端口地址,所以这时的NioServerSocketChannel并未激活,这里的isActive()返回falseregister0方法直接返回。

 

服务端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上。

抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(七)-鸿蒙开发者社区

 传播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组的结构就变为:

抓到Netty一个Bug,聊一下Netty是如何高效接收网络连接的(七)-鸿蒙开发者社区

 主从Reactor组完整结构.png


总结


本文我们介绍了NioServerSocketChannel处理客户端连接事件的整个过程。

 

•接收连接的整个处理框架。


•影响Netty接收连接吞吐的Bug产生的原因,以及修复的方案。


•创建并初始化客户端NioSocketChannel


•初始化NioSocketChannel中的pipeline


•客户端NioSocketChannelSub Reactor注册的过程


其中我们也对比了NioServerSocketChannelNioSocketChannel在创建初始化以及后面向Reactor注册过程中的差异之处。

 

当客户端NioSocketChannel接收完毕并向Sub Reactor注册成功后,那么接下来Sub Reactor就开始监听注册其上的所有客户端NioSocketChannelOP_READ事件,并等待客户端向服务端发送网络数据。

 

后面Reactor的主角就该变为Sub Reactor以及注册在其上的客户端NioSocketChannel了。

 

下篇文章,我们将会讨论Netty是如何接收网络数据的~~~~ 我们下篇文章见~~

标签
已于2022-8-1 19:30:06修改
收藏
回复
举报
回复
    相关推荐