《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工作原理的图:(来源自参考资料所贴出的博客)
这张图自己目前也还没有全部弄懂(只知道大概流程确实是这样),有待于自己进一步的接触后才能更好的理解。