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

2018-06-05  本文已影响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();
    }
上一篇下一篇

猜你喜欢

热点阅读