《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
- 《Java 源码分析》:Java NIO 之 Selector(第一部分Selector.open())
- 《Java 源码分析》:Java NIO 之 Selector(第二部分selector.select())
- 《Java源码解析》之NIO的Selector机制(Part1:Selector.open())
- JAVA NIO之selector
- Java NIO之Selector
- Java NIO之Selector
- Java NIO 之selector
- Java NIO 之 Selector
- Java NIO之Selector
- Java NIO之Selector
- NIO Selector源码分析
- java-nio之Selector组件
- Java NIO——Selector机制源码分析---转
- Java NIO中Selector类源码
- 《Java源码解析》之NIO的Selector机制(Part2:SelectableChannel.register(Selector sel, int ops))
- 《Java源码解析》之NIO的Selector机制(Part3:Selector.select())
- Java NIO Selector
- Java NIO(7-Selector)
- 状态压缩DP 与 树状DP
- Android 定时器使用
- 通过Function Score Query优化Elasticsearch搜索结果
- js-图片的放大和缩小
- 我所了解的计算机
- 《Java 源码分析》:Java NIO 之 Selector(第一部分Selector.open())
- 人生十字路口
- opencv3.0学习之路【10/14】split,merge
- sum
- 20161012 Python 读书笔记之 输出、循环
- 项目实践——语音计算器
- ggplot 的 legend 是一门学问
- 笔记:粗略学习c的内存4区及函数调用过程
- 数据结构的连通性问题