Netty4学习笔记(9)-- Channel状态转换

来源:互联网 发布:网络专接本 编辑:程序博客网 时间:2024/06/14 10:34

转自:http://blog.csdn.net/zxhoo/article/details/17964353


前面有一篇文章分析过Bootstrap类如何引导NioSocketChannel。上篇文章简单讨论了一下Channel接口的方法,知道有四个方法用来查询Channel的状态:isOpen()isRegistered()isActive()isWritable()。这篇文章结合Bootstrap分析一下前三个方法,看看NioSocketChannel是如何到达这三个状态的。

Channel继承层次图

分析上面提到的三个状态的时候,会去看Channel继承层次里某些类的代码,为了方便参考,我画了一张(不太严格的)UML类图,如下所示:

open状态

先从isOpen()方法入手,isOpen()方法是在AbstractNioChannel抽象类里实现的,下面是这个类的关键代码:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public abstract class AbstractNioChannel extends AbstractChannel {  
  2.     ...  
  3.     private final SelectableChannel ch;  
  4.     ...  
  5.     @Override  
  6.     public boolean isOpen() {  
  7.         return ch.isOpen();  
  8.     }  
  9.     ...  
  10. }  
可以看出来,Netty的Channel是否open取决于Java的SelectableChannel是否open。换句话说,只要找出Netty何时open了这个SelectableChannel,就可以知道Channel何时到达了open状态。从Bootstrap的connect()方法开始顺藤摸瓜就能找出答案:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Bootstrap.connect(String inetHost, int inetPort)  
  2.   -> Bootstrap.doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress)  
  3.     -> AbstractBootstrap.initAndRegister()  
  4.       -> BootstrapChannelFactory.newChannel()  
  5.         -> NioSocketChannel()  
  6.           -> NioSocketChannel.newSocket()  
  7.             -> SocketChannel.open()  
重点看看initAndRegister()方法:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1.     // AbstractBootstrap.java  
  2.     final ChannelFuture initAndRegister() {  
  3.         final Channel channel = channelFactory().newChannel();  
  4.         try {  
  5.             init(channel);  
  6.         } catch (Throwable t) {  
  7.             channel.unsafe().closeForcibly();  
  8.             return channel.newFailedFuture(t);  
  9.         }  
  10.   
  11.         ChannelPromise regPromise = channel.newPromise();  
  12.         group().register(channel, regPromise);  
  13.         ...  
  14.         return regPromise;  
  15.     }  
initAndRegister()方法先创建了Channel实例(此时Channel已经处于open状态),然后把它注册到group里,所以大概能够知道,Channel是在open之后进入registered状态的,如下图所示:


registered状态

为了证明上面的猜测,我们从NioEventLoopGroup.register()方法接着看代码。NioEventLoopGroup并没有实现register()方法,真正的实现是在它的超类MultithreadEventLoopGroup里:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // MultithreadEventLoopGroup.java  
  2. @Override  
  3. public ChannelFuture register(Channel channel, ChannelPromise promise) {  
  4.     return next().register(channel, promise);  
  5. }  
根据这篇文章的介绍,next()方法返回的是一个NioEventLoop,看代码后知道,register()方法是在NioEventLoop的超类,SingleThreadEventLoop里实现的:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // SingleThreadEventLoop.java  
  2. @Override  
  3. public ChannelFuture register(final Channel channel, final ChannelPromise promise) {  
  4.     ...  
  5.     channel.unsafe().register(this, promise);  
  6.     return promise;  
  7. }  
好吧,继续看代码,知道调用的是AbstractChannel.AbstractUnsafe.register()方法,这个方法又调用了AbstractUnsafe.register0()方法,在register0()方法里,registered字段被设置为true。而AbstractChannel的isRegistered()方法正好是通过这个字段来判断是否是registered状态:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // AbstractChannel.java  
  2. @Override  
  3. public boolean isRegistered() {  
  4.     return registered;  
  5. }  
也就是说,上面的猜测是正确的,Channel先进入open状态,然后通过把自己注册到group进入registered状态。


active状态

还是先看看isActive()方法是如何实现的(在NioSocketChannel里):

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // NioSocketChannel.java  
  2. @Override  
  3. public boolean isActive() {  
  4.     SocketChannel ch = javaChannel();  
  5.     return ch.isOpen() && ch.isConnected();  
  6. }  
也就是说,NioSocketChannel的active状态取决于SocketChannel的状态。根据前面的分析知道,NioSocketChannel构造函数执行之后,SocketChannel已经处于open状态了,那么接下来就看SocketChannel的connect()方法是何时被调用的。回到Bootstrap类的doConnect()方法:
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Bootstrap.connect(String inetHost, int inetPort)  
  2.   -> Bootstrap.doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress)  
  3.     -> AbstractBootstrap.initAndRegister()  
  4.        Bootstrap.doConnect0(...)  
  5.          -> Channel.connect(SocketAddress remoteAddress, ChannelPromise promise  
doConnect()方法在initAndRegister()之后又调用了doConnect0()方法,doConnect0()方法调用了Channel的connect()方法。在AbstractChannel里有connect()方法的实现:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // AbstractChannel.java  
  2. @Override  
  3. public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {  
  4.     return pipeline.connect(remoteAddress, promise);  
  5. }  
也就是说,connect实际上是被当做事件交给pipeline去处理的,而且是个outbound事件,看DefaultChannelPipeline:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // DefaultChannelPipeline.java  
  2. @Override  
  3. public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {  
  4.     return tail.connect(remoteAddress, promise);  
  5. }  
tail是DefaultChannelHandlerContext实例:
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1.     // DefaultChannelHandlerContext.java  
  2.     @Override  
  3.     public ChannelFuture connect(SocketAddress remoteAddress, ChannelPromise promise) {  
  4.         return connect(remoteAddress, null, promise);  
  5.     }  
  6.   
  7.     @Override  
  8.     public ChannelFuture connect(final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {  
  9.         ...  
  10.         final DefaultChannelHandlerContext next = findContextOutbound();  
  11.         EventExecutor executor = next.executor();  
  12.         if (executor.inEventLoop()) {  
  13.             next.invokeConnect(remoteAddress, localAddress, promise);  
  14.         } else {  
  15.             safeExecute(executor, new Runnable() {  
  16.                 @Override  
  17.                 public void run() {  
  18.                     next.invokeConnect(remoteAddress, localAddress, promise);  
  19.                 }  
  20.             }, promise, null);  
  21.         }  
  22.   
  23.         return promise;  
  24.     }  
  25.   
  26.     private void invokeConnect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) {  
  27.         try {  
  28.             ((ChannelOutboundHandler) handler).connect(this, remoteAddress, localAddress, promise);  
  29.         } catch (Throwable t) {  
  30.             notifyOutboundHandlerException(t, promise);  
  31.         }  
  32.     }  
三个参数版的connect()方法看起来很复杂,但无非就是做了两件事:先沿着pipeline往前找到第一个outbound类型的context,接着调用这个context的invokeConnect()方法。然后context又调用了handler的connect()方法,而pipeline里必定会有一个outbound类型的context/handler,这个context就是head,相应的handler是内部类HeadHandler

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // DefaultChannelPipeline.java  
  2. static final class HeadHandler implements ChannelOutboundHandler {  
  3.     protected final Unsafe unsafe;  
  4.   
  5.     protected HeadHandler(Unsafe unsafe) {  
  6.         this.unsafe = unsafe;  
  7.     }  
  8.     ...  
  9.     @Override  
  10.     public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {  
  11.         unsafe.connect(remoteAddress, localAddress, promise);  
  12.     }  
  13.     ...  
  14. }  
HeadHandler只是调用了unsafe的connect()方法,unsafe是在构造函数里传进来的:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public DefaultChannelPipeline(AbstractChannel channel) {  
  2.     ...  
  3.     HeadHandler headHandler = new HeadHandler(channel.unsafe());  
  4.     head = new DefaultChannelHandlerContext(thisnull, generateName(headHandler), headHandler);  
  5.     ...  
  6. }  
Unsafe.connect()方法在AbstractNioChannel.AbstractNioUnsafe里实现,这个实现调用了AbstractNioChannel.doConnect()方法。doConnect()方法最终在NioSocketChannel里得以实现:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // NioSocketChannel.java  
  2. @Override  
  3. protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {  
  4.     if (localAddress != null) {  
  5.         javaChannel().socket().bind(localAddress);  
  6.     }  
  7.   
  8.     boolean success = false;  
  9.     try {  
  10.         boolean connected = javaChannel().connect(remoteAddress);  
  11.         if (!connected) {  
  12.             selectionKey().interestOps(SelectionKey.OP_CONNECT);  
  13.         }  
  14.         success = true;  
  15.         return connected;  
  16.     } finally {  
  17.         if (!success) {  
  18.             doClose();  
  19.         }  
  20.     }  
  21. }  

结论

代码分析的很复杂,但结论很简单:被Bootstrap引导的NioSocketChannel在构造好之后就进入了open状态,之后通过把自己注册进EventLoop进入registered状态,接着连接服务器进入active状态。


0 0
原创粉丝点击