2023-01-14
原文作者: HelloWorld_EE 原文地址:https://blog.csdn.net/u010412719/category_6159934_2.html

《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方法,如下:

    @CallerSensitive
    public 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工作原理的图:(来源自参考资料所贴出的博客)

102802845.jpg

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

参考资料

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

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

阅读全文