《Java 源码分析》:Java NIO 之 Selector(第一部分Selector.open())

来源:互联网 发布:软件过程管理期末试卷 编辑:程序博客网 时间:2024/06/13 21:28

《Java 源码分析》 :Java NIO 之 Selector(第一部分Selector.open())

关于Selector类主要涉及两个重要的方法,如下:

1、Selector.open()

2、select()

由于篇幅限制,这篇主要从源码的角度来介绍Selector selector = Selector.open()背后主要做了什么,发生了什么。

Selector类中的open()源码如下:

    public static Selector open() throws IOException {        return SelectorProvider.provider().openSelector();    }

函数功能:打开一个选择器。这个新的选择器的是通过调用系统级默认 SelectorProvider 对象的 openSelector 方法来创建的。

SelectorProvider.provider().openSelector();这行代码中我们先看SelectorProvider.provider();具体做了些什么。

provider()方法的源码如下:

    public static SelectorProvider provider() {        synchronized (lock) {            if (provider != null)//保证只有一个provider对象实例                return provider;            return AccessController.doPrivileged(                new PrivilegedAction<SelectorProvider>() {                    public SelectorProvider run() {                            if (loadProviderFromProperty())                                return provider;                            if (loadProviderAsService())                                return provider;                            provider = sun.nio.ch.DefaultSelectorProvider.create();                            return provider;                        }                    });        }    }

此函数功能:返回此时调用JVM的系统级默认的SelectorProvider

在代码中

    而if (provider != null)            returnprovider;

是用来保证了整个程序中只有一个WindowsSelectorProvider对象;

由于我自己看的源码不是openjdk,因此在类库中根本就没有sun.nio.ch这个包。

在这里可以看到:http://www.docjar.com/html/api/sun/nio/ch/DefaultSelectorProvider.java.html。或者是直接下载openjdk来进行源码的追踪。

provider由sun.nio.ch.DefaultSelectorProvider.create();创建

sun.nio.ch.DefaultSelectorProvider类的源码如下:

    /**       * Creates this platform's default SelectorProvider       */      public class DefaultSelectorProvider {          /**           * Prevent instantiation.           */          private DefaultSelectorProvider() { }          /**           * Returns the default SelectorProvider.           */          public static SelectorProvider create() {              return new sun.nio.ch.WindowsSelectorProvider();          }      }

所以从上面我们就可以看到:DefaultSelectorProvider 类只有一个私有的构造函数和一个create方法。其中在类 SelectorProvider 中的provider()方法中
provider = sun.nio.ch.DefaultSelectorProvider.create();会根据操作系统来返回不同的实现类,windows平台就返回WindowsSelectorProvider对象实例;

以上就是 SelectorProvider.provider() 产生的一个 SelectorProvider (子类 WindowsSelectorProvider)对象实例的过程。

结论:有上面我们知道Selector.open()方法中的SelectorProvider.provider()实际上就是实例化了一个WindowsSelectorProvider对象,其中WindowsSelectorProvider为SelectorProvider的子类。

    public static Selector open() throws IOException {        return SelectorProvider.provider().openSelector();    }

有了上面的基础,我们接着看上面代码块中的后面一半:SelectorProvider.openSelector()方法的具体实现过程。

SelectorProvider.provider().openSelector();根据前面的分析实际上就是 WindowsSelectorProvider. openSelector()。

因此,下面主要看下:WindowsSelectorProvider. openSelector()这个主要做了些什么操作。

WindowsSelectorProvider类的源码如下:

 public class WindowsSelectorProvider extends SelectorProviderImpl {          public AbstractSelector openSelector() throws IOException {             return new WindowsSelectorImpl(this);         }  }

因此 WindowsSelectorProvider. openSelector()实现的逻辑就是直接实例化 WindowsSelectorImpl对象。

以上的思路还是相当清晰且容易理解的哈。简单来说:Selector selector = Selector.open();实际上就是new 了一个 WindowsSelectorImpl对象实例。

既然是实例化一个 WindowsSelectorImpl。因此,我们这有必要看下这个类的构造函数。

这个类才是我们要关注的重点:WindowsSelectorImpl

继承关系如下:

final class WindowsSelectorImpl extends SelectorImpl

其构造函数为:

      WindowsSelectorImpl(SelectorProvider sp) throws IOException {             super(sp);             pollWrapper = new PollArrayWrapper(INIT_CAP);             wakeupPipe = Pipe.open();             wakeupSourceFd = ((SelChImpl)wakeupPipe.source()).getFDVal();             // Disable the Nagle algorithm so that the wakeup is more immediate             SinkChannelImpl sink = (SinkChannelImpl)wakeupPipe.sink();             (sink.sc).socket().setTcpNoDelay(true);             wakeupSinkFd = ((SelChImpl)sink).getFDVal();             pollWrapper.addWakeupSocket(wakeupSourceFd, 0);         }

这段代码中做了如下几个事情

1、Pipe.open()打开一个管道(打开管道的实现后面再看);

2、拿到wakeupSourceFd和wakeupSinkFd两个文件描述符;

3、把唤醒端的文件描述符(wakeupSourceFd)放到pollWrapper里;PollArrayWrapper类会在文章后面进行介绍。

那么为什么需要一个管道,这个管道是怎么实现的?接下来看Pipe.open()做了什么

Pipe类在 java.nio.channels包下面

    //函数功能:打开一个管道    public static Pipe open() throws IOException {        return SelectorProvider.provider().openPipe();    }

Pipe.open()方法直接是 调用了 SelectorProvider.openPipe()方法来实现的。

进一步来看 SelectorProvider.openPipe() 这个方法。

在SelectorProvider中的openPipe()是一个抽象方法,如下:

public abstract Pipe openPipe()    throws IOException;

由于SelectorProvider.provider()实际上返回的是SelectorProvider的子类WindowSelectorProvider的实例对象。沿着 WindowsSelectorProvider的继承关系找了下,SelectorProvider中的openPipe()抽象方法
是在 SelectorProviderImpl类中实现的,即是在 WindowSelectorProvider类的父类来实现的。

这里有必要说下 WindowsSelectorProvider的继承关系:

WindowSelectorProvider 的直接父类是 SelectorProviderImpl;SelectorProviderImpl 的直接父类是 SelectorProvider。

SelectorProviderImpl 类的代码如下:

      public abstract class SelectorProviderImpl              extends SelectorProvider          {                //...省略了一些不相干的函数              public Pipe openPipe() throws IOException {                  return new PipeImpl(this);              }               //...          }

从上面可知:

打开管道Pipe.open()方法 直接调用的 SelectorProvider.openPipe()方法,而SelectorProvider类中的openPipe()方法 直接返回的是:new PipeImpl(this),即PipeImpl类的对象实例。

看到这里的时候,我在想为什么不从最开始的Pipe类的open()中直接返回PipeImpl的实例对象呢,而是要委托给SelectorProviderImpl(具体代码看下面)呢,原因可能在于PipeImpl实例需要一个WindowsSelectorProvider且所有环境有且只有一个,如果不采用这种方式可能会更复杂,不想了,继续往后面看

Pipe类的open()方法

    //函数功能:打开一个管道    public static Pipe open() throws IOException {        return SelectorProvider.provider().openPipe();    }

经过上面的分析,我们已经知道了Pipe.open()在代码层面的表现为:实例化了一个PipeImpl对象。

下面看下PipeImpl 类的构造函数

        PipeImpl(final SelectorProvider sp) throws IOException {             try {                 AccessController.doPrivileged(new Initializer(sp));             } catch (PrivilegedActionException x) {                 throw (IOException)x.getCause();             }         }

这个构造方法中的代码虽然比较不熟悉,是自己第一次见到,但是我们还是要想办法来看下,是吧。

先不看Initializer这个类里面的具体实现,我们来看下PipeImpl类的构造函数中

AccessController.doPrivileged(new Initializer(sp))
这行代码中所涉及的:dePrivileged这个方法是干什么的?

AccessController 类中的 doPrivileged(PrivilegedAction action) 方法是一个native方法,如下:

@CallerSensitivepublic static native <T> T doPrivileged(PrivilegedAction<T> action);

关于 AccessController.doPrivileged方法的介绍,可以参考下篇博文:

1、http://www.blogjava.net/DLevin/archive/2012/11/02/390637.html(自己目前也没有太理解)

2、http://huangyunbin.iteye.com/blog/1942509

看了一些关于AccessController.doPrivileged的资料,还没有怎么懂,但是可以这里来理解:

首先:AccessController.doPrivileged意思是这个是特别的,不用做权限检查.

在什么地方会用到呢

答:假设1.jar中有类可以读取一个文件,现在我们要使用1.jar去做这个事情.
但是我们的类本生是没有权限去读取那个文件的,一般情况下就是眼睁睁的看着了. 但是java提供了doPrivileged.在1.jar中如果读取文件的方法是通过doPrivileged来实现的.
就不会有后面的检查了,现在我们就可以使用1.jar去读取那个文件了.

利用doPrivileged就实现了没有权限的人借用有权限的人来达到一定的目的。

回到原题:

AccessController.doPrivileged(new Initializer(sp)) 经过权限的检查之后就会直接执行Initializer中的run方法
,下面来看下Initializer这个类中的run方法。

Initializer是PipeImpl类的内部类,源代码如下:

       private class Initializer              implements PrivilegedExceptionAction<Void>          {              private final SelectorProvider sp;              private Initializer(SelectorProvider sp) {                  this.sp = sp;              }              public Void run() throws IOException {                  ServerSocketChannel ssc = null;                  SocketChannel sc1 = null;                  SocketChannel sc2 = null;                  try {                      // loopback address                      InetAddress lb = InetAddress.getByName("127.0.0.1");                      assert(lb.isLoopbackAddress());                      // bind ServerSocketChannel to a port on the loopback address                      // 将ServerSocketChannel绑定本地环回地址,端口号为 0                      ssc = ServerSocketChannel.open();                      ssc.socket().bind(new InetSocketAddress(lb, 0));                      // Establish connection (assumes connections are eagerly                      // accepted)                      // 建立连接                      InetSocketAddress sa                          = new InetSocketAddress(lb, ssc.socket().getLocalPort());                      sc1 = SocketChannel.open(sa);                      //向SocketChannel中写入数据                      ByteBuffer bb = ByteBuffer.allocate(8);                     long secret = rnd.nextLong();                     bb.putLong(secret).flip();                     sc1.write(bb);                     // Get a connection and verify it is legitimate                     for (;;) {                         sc2 = ssc.accept();                         bb.clear();                         sc2.read(bb);                         bb.rewind();                         if (bb.getLong() == secret)                             break;                         sc2.close();                     }                     // Create source and sink channels                     source = new SourceChannelImpl(sp, sc1);                     sink = new SinkChannelImpl(sp, sc2);                 } catch (IOException e) {                     try {                         if (sc1 != null)                             sc1.close();                         if (sc2 != null)                             sc2.close();                     } catch (IOException e2) { }                     IOException x = new IOException("Unable to establish"                                                     + " loopback connection");                     x.initCause(e);                     throw x;                 } finally {                     try {                         if (ssc != null)                             ssc.close();                     } catch (IOException e2) { }                 }                 return null;             }         }

从 Initializer中run方法中,我们可以得到的一点是:建立了一个 loopback connection.

windows下的实现是创建两个本地的socketChannel,然后连接(链接的过程通过写一个随机long做两个socket的链接校验),两个socketChannel分别实现了管道的source与sink端。
source端由前面提到的WindowsSelectorImpl放到了pollWrapper中(pollWrapper.addWakeupSocket(wakeupSourceFd, 0))

最后,看下PollArrayWrapper这个类

PollArrayWrapper类主要在前面的WindowsSelectorImpl的构造函数中有这样一行代码:pollWrapper.addWakeupSocket(wakeupSourceFd, 0)(作用:把唤醒端的文件描述符(wakeupSourceFd)放到pollWrapper里)

PollArrayWrapper类中addWakeupSocket方法的源代码如下:

    // Adds Windows wakeup socket at a given index.    void addWakeupSocket(int fdVal, int index) {        putDescriptor(index, fdVal);        putEventOps(index, POLLIN);    }    // Access methods for fd structures    void putDescriptor(int i, int fd) {        pollArray.putInt(SIZE_POLLFD * i + FD_OFFSET, fd);    }    void putEventOps(int i, int event) {        pollArray.putShort(SIZE_POLLFD * i + EVENT_OFFSET, (short)event);    }

这里将source的POLLIN事件标识为感兴趣的,当sink端有数据写入时,source对应的文件描述符wakeupSourceFd就会处于就绪状态

到这里从源码的角度来看了Selector selector = Selector.open()主要做了些什么

主要完成建立Pipe,并把pipe的wakeupSourceFd放入pollArray中,这个pollArray是Selector的枢纽。这里是以Windows的实现来看,在windows下通过两个链接的socketChannel实现了Pipe,linux下则是直接使用系统的pipe。

小结

Selector selector = Selector.open();实际上就是new 了一个 WindowsSelectorImpl对象实例。

以及建立了Pipe,并把pipe的wakeupSourceFd放入pollArray中,这个pollArray是Selector的枢纽。这里是以Windows的实现来看,在windows下通过两个链接的socketChannel实现了Pipe,linux下则是直接使用系统的pipe。

关于第二部分就是从源码的角度来看下selector.select()背后做了些什么,敬请期待。

最后上一张关于Selector工作原理的图:(来源自参考资料所贴出的博客)

这张图自己目前也还没有全部弄懂(只知道大概流程确实是这样),有待于自己进一步的接触后才能更好的理解。

参考资料

1、http://goon.iteye.com/blog/1775421

2、http://www.myexception.cn/program/1598318.html

0 0