Netty4學習筆記(9)-- Channel狀態轉換

前面有一篇文章分析過Bootstrap類如何引導NioSocketChannel上篇文章簡單討論了一下Channel接口的方法,知道有四個方法用來查詢Channel的狀態:isOpen()isRegistered()isActive()isWritable()。這篇文章結合Bootstrap分析一下前三個方法,看看NioSocketChannel是如何到達這三個狀態的。

Channel繼承層次圖

分析上面提到的三個狀態的時候,會去看Channel繼承層次裏某些類的代碼,爲了方便參考,我畫了一張(不太嚴格的)UML類圖,如下所示:

open狀態

先從isOpen()方法入手,isOpen()方法是在AbstractNioChannel抽象類裏實現的,下面是這個類的關鍵代碼:

public abstract class AbstractNioChannel extends AbstractChannel {
    ...
    private final SelectableChannel ch;
    ...
    @Override
    public boolean isOpen() {
        return ch.isOpen();
    }
    ...
}
可以看出來,Netty的Channel是否open取決於Java的SelectableChannel是否open。換句話說,只要找出Netty何時open了這個SelectableChannel,就可以知道Channel何時到達了open狀態。從Bootstrap的connect()方法開始順藤摸瓜就能找出答案:

Bootstrap.connect(String inetHost, int inetPort)
  -> Bootstrap.doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress)
    -> AbstractBootstrap.initAndRegister()
      -> BootstrapChannelFactory.newChannel()
        -> NioSocketChannel()
          -> NioSocketChannel.newSocket()
            -> SocketChannel.open()
重點看看initAndRegister()方法:

    // AbstractBootstrap.java
    final ChannelFuture initAndRegister() {
        final Channel channel = channelFactory().newChannel();
        try {
            init(channel);
        } catch (Throwable t) {
            channel.unsafe().closeForcibly();
            return channel.newFailedFuture(t);
        }

        ChannelPromise regPromise = channel.newPromise();
        group().register(channel, regPromise);
        ...
        return regPromise;
    }
initAndRegister()方法先創建了Channel實例(此時Channel已經處於open狀態),然後把它註冊到group裏,所以大概能夠知道,Channel是在open之後進入registered狀態的,如下圖所示:


registered狀態

爲了證明上面的猜測,我們從NioEventLoopGroup.register()方法接着看代碼。NioEventLoopGroup並沒有實現register()方法,真正的實現是在它的超類MultithreadEventLoopGroup裏:

    // MultithreadEventLoopGroup.java
    @Override
    public ChannelFuture register(Channel channel, ChannelPromise promise) {
        return next().register(channel, promise);
    }
根據這篇文章的介紹,next()方法返回的是一個NioEventLoop,看代碼後知道,register()方法是在NioEventLoop的超類,SingleThreadEventLoop裏實現的:

    // SingleThreadEventLoop.java
    @Override
    public ChannelFuture register(final Channel channel, final ChannelPromise promise) {
        ...
        channel.unsafe().register(this, promise);
        return promise;
    }
好吧,繼續看代碼,知道調用的是AbstractChannel.AbstractUnsafe.register()方法,這個方法又調用了AbstractUnsafe.register0()方法,在register0()方法裏,registered字段被設置爲true。而AbstractChannel的isRegistered()方法正好是通過這個字段來判斷是否是registered狀態:

    // AbstractChannel.java
    @Override
    public boolean isRegistered() {
        return registered;
    }
也就是說,上面的猜測是正確的,Channel先進入open狀態,然後通過把自己註冊到group進入registered狀態。


active狀態

還是先看看isActive()方法是如何實現的(在NioSocketChannel裏):

    // NioSocketChannel.java
    @Override
    public boolean isActive() {
        SocketChannel ch = javaChannel();
        return ch.isOpen() && ch.isConnected();
    }
也就是說,NioSocketChannel的active狀態取決於SocketChannel的狀態。根據前面的分析知道,NioSocketChannel構造函數執行之後,SocketChannel已經處於open狀態了,那麼接下來就看SocketChannel的connect()方法是何時被調用的。回到Bootstrap類的doConnect()方法:
Bootstrap.connect(String inetHost, int inetPort)
  -> Bootstrap.doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress)
    -> AbstractBootstrap.initAndRegister()
       Bootstrap.doConnect0(...)
         -> Channel.connect(SocketAddress remoteAddress, ChannelPromise promise
doConnect()方法在initAndRegister()之後又調用了doConnect0()方法,doConnect0()方法調用了Channel的connect()方法。在AbstractChannel裏有connect()方法的實現:

    // AbstractChannel.java
    @Override
    public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
        return pipeline.connect(remoteAddress, promise);
    }
也就是說,connect實際上是被當做事件交給pipeline去處理的,而且是個outbound事件,看DefaultChannelPipeline:

    // DefaultChannelPipeline.java
    @Override
    public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
        return tail.connect(remoteAddress, promise);
    }
tail是DefaultChannelHandlerContext實例:
    // DefaultChannelHandlerContext.java
    @Override
    public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {
        return connect(remoteAddress, null, promise);
    }

    @Override
    public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {
        ...
        final DefaultChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeConnect(remoteAddress, localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeConnect(remoteAddress, localAddress, promise);
                }
            }, promise, null);
        }

        return promise;
    }

    private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {
        try {
            ((ChannelOutboundHandler) handler).connect(this, remoteAddress, localAddress, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    }
三個參數版的connect()方法看起來很複雜,但無非就是做了兩件事:先沿着pipeline往前找到第一個outbound類型的context,接着調用這個context的invokeConnect()方法。然後context又調用了handler的connect()方法,而pipeline裏必定會有一個outbound類型的context/handler,這個context就是head,相應的handler是內部類HeadHandler

// DefaultChannelPipeline.java
static final class HeadHandler implements ChannelOutboundHandler {
    protected final Unsafe unsafe;

    protected HeadHandler(Unsafe unsafe) {
        this.unsafe = unsafe;
    }
    ...
    @Override
    public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
        unsafe.connect(remoteAddress, localAddress, promise);
    }
    ...
}
HeadHandler只是調用了unsafe的connect()方法,unsafe是在構造函數裏傳進來的:

public DefaultChannelPipeline(AbstractChannel channel) {
    ...
    HeadHandler headHandler = new HeadHandler(channel.unsafe());
    head = new DefaultChannelHandlerContext(this, null, generateName(headHandler), headHandler);
    ...
}
Unsafe.connect()方法在AbstractNioChannel.AbstractNioUnsafe裏實現,這個實現調用了AbstractNioChannel.doConnect()方法。doConnect()方法最終在NioSocketChannel裏得以實現:

    // NioSocketChannel.java
    @Override
    protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {
        if (localAddress != null) {
            javaChannel().socket().bind(localAddress);
        }

        boolean success = false;
        try {
            boolean connected = javaChannel().connect(remoteAddress);
            if (!connected) {
                selectionKey().interestOps(SelectionKey.OP_CONNECT);
            }
            success = true;
            return connected;
        } finally {
            if (!success) {
                doClose();
            }
        }
    }

結論

代碼分析的很複雜,但結論很簡單:被Bootstrap引導的NioSocketChannel在構造好之後就進入了open狀態,之後通過把自己註冊進EventLoop進入registered狀態,接着連接服務器進入active狀態。




發佈了61 篇原創文章 · 獲贊 72 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章