前面有一篇文章分析过Bootstrap类如何引导NioSocketChannel。上篇文章简单讨论了一下Channel接口的方法,知道有四个方法用来查询Channel的状态:isOpen()、isRegistered()、isActive()和isWritable()。这篇文章结合Bootstrap分析一下前三个方法,看看NioSocketChannel是如何到达这三个状态的。
分析上面提到的三个状态的时候,会去看Channel继承层次里某些类的代码,为了方便参考,我画了一张(不太严格的)UML类图,如下所示:
先从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状态的,如下图所示:
为了证明上面的猜测,我们从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状态。
还是先看看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 promisedoConnect()方法在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状态。