Netty4學習筆記(9)-- Channel狀態轉換
阿新 • • 發佈:2019-02-12
。上篇文章簡單討論了一下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()方法開始順藤摸瓜就能找出答案:
重點看看initAndRegister()方法:Bootstrap.connect(String inetHost, int inetPort) -> Bootstrap.doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) -> AbstractBootstrap.initAndRegister() -> BootstrapChannelFactory.newChannel() -> NioSocketChannel() -> NioSocketChannel.newSocket() -> SocketChannel.open()
initAndRegister()方法先建立了Channel例項(此時Channel已經處於open狀態),然後把它註冊到group裡,所以大概能夠知道,Channel是在open之後進入registered狀態的,如下圖所示:// 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; }
registered狀態
// 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狀態。