美文网首页
Netty源码分析----注册监听事件

Netty源码分析----注册监听事件

作者: _六道木 | 来源:发表于2018-06-05 23:35 被阅读72次

    (*文章基于Netty4.1.22版本)
    上篇服务启动的文章讲了3个步骤

    1. 创建Channel并设置非阻塞
    2. Channel绑定地址
    3. Channel注册Selector

    但是其实,NIO还有一步是注册感兴趣的事件,在上一篇文章中,只是将感兴趣的事件存放到一个变量中,而没有进行注册,这里,我们看下注册的流程。
    一开始我也没找到到底哪里进行注册的,后来先通过AbstractNioChannel的readInterestOp变量找到使用到的地方,发现doBeginRead方法用到了

        protected void doBeginRead() throws Exception {
            // Channel.read() or ChannelHandlerContext.read() was called
            final SelectionKey selectionKey = this.selectionKey;
            if (!selectionKey.isValid()) {
                return;
            }
    
            readPending = true;
    
            final int interestOps = selectionKey.interestOps();
            if ((interestOps & readInterestOp) == 0) {
                selectionKey.interestOps(interestOps | readInterestOp);
            }
        }
    

    看来这里是注册感兴趣事件的地方,再看下哪里使用了这个方法。
    先看下AbstractChannel#register0方法的一部分代码

    if (isActive()) {
          if (firstRegistration) {
                pipeline.fireChannelActive();
          } else if (config().isAutoRead()) {
                beginRead();
          }
    }
    

    当channel已经打开且连接的时候,会有两个分支,第一个分支流程调用pipeline的fireChannelActive方法,首先会调用HeadContext的channelActive方法(具体原因看pipeline分析的文章),第二个是调用beginRead方法,先看下HeadContext的channelActive方法

            @Override
            public void channelActive(ChannelHandlerContext ctx) throws Exception {
                ctx.fireChannelActive();
    
                readIfIsAutoRead();
            }
            private void readIfIsAutoRead() {
                if (channel.config().isAutoRead()) {
                    channel.read();
                }
            }
    

    如果autoRead打开了,那么会在连接打开和建立的时候默认调用Channel的read方法,然后最终会调用到AbstractUnsafe的beginRead方法(具体原因看pipeline分析的文章),刚说了另外一个分支流程的beginRead也是调用AbstractUnsafe的beginRead方法,看下其实现

            public final void beginRead() {
                //....
              
                    doBeginRead();
               //....
            }
    

    doBeginRead就是上面说的注册感兴趣事件的地方。
    总结一下:
    在channelActive事件发生的时候,如果开启了autoRead,那么会自动去注册一个感兴趣的事件。
    另外再channelReadComplete也是如此

    注册OP_ACCEPT事件

    初始化NioServerSocketChannel的代码如下

        public NioServerSocketChannel(ServerSocketChannel channel) {
            super(null, channel, SelectionKey.OP_ACCEPT);
            config = new NioServerSocketChannelConfig(this, javaChannel().socket());
        }
    

    会将OP_ACCEPT设置到AbstractNioChannel的readInterestOp属性中,然后是怎么触发beginRead方法的呢?上面讲了触发pipeline的那几个方法的时候会调用beginRead,那么找一下哪里调用了,发现有两个地方,第一个是AbstractUnsafe类的register0方法中

                    if (isActive()) {
                        if (firstRegistration) {
                            pipeline.fireChannelActive();
                        } else if (config().isAutoRead()) {
                            beginRead();
                        }
                    }
    

    这里会调用,但是在这个阶段isActive会为true吗?isActive方法如下

    javaChannel().socket().isBound()
    

    当socket绑定了端口之后,isBound会返回true,而bind的阶段,是在register之后的,此时并不符合要求,所以isActive会返回false,那么再看下AbstractUnsafe类的bind方法

                //....
                if (!wasActive && isActive()) {
                    invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            pipeline.fireChannelActive();
                        }
                    });
                }
    

    在doBind方法之后又调用了fireChannelActive方法,由于doBind已经调用过了,那么if的条件满足,这时候再注册OP_ACCEPT事件

    注册OP_READ事件

    NioServerSocketChannel是ServerSocketChannel的封装,而NioSocketChannel是SocketChannel的封装,OP_ACCEPT方法是在构造方法中设置的,同理,看一下构造方法

        public NioSocketChannel(Channel parent, SocketChannel socket) {
            super(parent, socket);
            config = new NioSocketChannelConfig(this, socket.socket());
        }
       protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
            super(parent, ch, SelectionKey.OP_READ);
        }
    

    在NioSocketChannel父类的构造方法中设置了事件,接下来要看下是哪里开始调用了channelActive方法。
    boss线程接收到请求后,会将Channel注册到worker线程中,这个注册过程和之前讲的注册一致,那么这种情况就会调用到abstractUnsafe的register0方法:

        public boolean isActive() {
            SocketChannel ch = javaChannel();
            return ch.isOpen() && ch.isConnected();
        }
    

    相关文章

      网友评论

          本文标题:Netty源码分析----注册监听事件

          本文链接:https://www.haomeiwen.com/subject/rfbbjftx.html