看了netty源码后发现,它不过是封装在jdk的nio之上的框架,虽然大致猜到nio的原理,但还是忍不住要去jdk底层一探究竟。
要用selector,第一句话无非Selector selector = Selector.open();但里面如何实现的?
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
我们看到这里需要先使用SelectorProvider的provider()方法来取得相应的SelectorProvider。
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
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;
}
});
}
}
先从系统属性中去找provider,如果没取到,看是否在规定路径下写了配置文件,安装在jar文件下对系统加载器可见,则用系统类加载器加载,若该类在META-INF / services下则用serviceLoader加载,若还是没找到,则使用DefaultSelectorProvider的creat()方法来获取默认的SelectorProvider。
public static SelectorProvider create() {
return new WindowsSelectorProvider();
}
如果没有配置,就采用这个类,其构造方法是空的,其openSelect()
public AbstractSelector openSelector() throws IOException {
return new WindowsSelectorImpl(this);
}
WindowsSelectorImpl有没很熟悉?对啊,netty默认的selesctor用的就是这个。jdk的nio默认情况下就是这个。
WindowsSelectorImpl(SelectorProvider var1) throws IOException {
super(var1);
this.wakeupSourceFd = ((SelChImpl)this.wakeupPipe.source()).getFDVal();
SinkChannelImpl var2 = (SinkChannelImpl)this.wakeupPipe.sink();
var2.sc.socket().setTcpNoDelay(true);
this.wakeupSinkFd = var2.getFDVal();
this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0);
}
看起构造,好像都用到wakeupPipe,看其成员private final Pipe wakeupPipe = Pipe.open();
public static Pipe open() throws IOException {
return SelectorProvider.provider().openPipe();
}
继续往下看吧,具体逻辑在SelectorProviderImpl类中
public Pipe openPipe() throws IOException {
return new PipeImpl(this);
}
我们看下PipImpl的构造方法
PipeImpl(SelectorProvider var1) throws IOException {
try {
AccessController.doPrivileged(new PipeImpl.Initializer(var1));
} catch (PrivilegedActionException var3) {
throw (IOException)var3.getCause();
}
}
(稍微提一下成员private static final Random RANDOM_NUMBER_GENERATOR = new SecureRandom();无非生成一个随机数。)可以看到AccessController的doPrivileged是个native方法,从注释中可以得出它调用了传入的action中的run方法,并保证其内部所涉及的权限问题。我们直接来看下PipeImpl的内部类的Initializer的run()方法
public Void run() throws IOException {
PipeImpl.Initializer.LoopbackConnector var1 = new PipeImpl.Initializer.LoopbackConnector();
var1.run();
if(this.ioe instanceof ClosedByInterruptException) {
this.ioe = null;
Thread var2 = new Thread(var1) {
public void interrupt() {
}
};
var2.start();
while(true) {
try {
var2.join();
break;
} catch (InterruptedException var4) {
;
}
}
Thread.currentThread().interrupt();
}
if(this.ioe != null) {
throw new IOException("Unable to establish loopback connection", this.ioe);
} else {
return null;
}
}
该方法主要是启动了个线程?看LoopbackConnector(),内部类Initializer的内部类LoopbackConnector。看其run方法吧。
public void run() {
ServerSocketChannel var1 = null;
SocketChannel var2 = null;
SocketChannel var3 = null;
try {
ByteBuffer var4 = ByteBuffer.allocate(16);
ByteBuffer var5 = ByteBuffer.allocate(16);
InetAddress var6 = InetAddress.getByName("127.0.0.1");
assert var6.isLoopbackAddress();
InetSocketAddress var7 = null;
while(true) {
if(var1 == null || !var1.isOpen()) {
var1 = ServerSocketChannel.open();
var1.socket().bind(new InetSocketAddress(var6, 0));
var7 = new InetSocketAddress(var6, var1.socket().getLocalPort());
}
var2 = SocketChannel.open(var7);
PipeImpl.RANDOM_NUMBER_GENERATOR.nextBytes(var4.array());
do {
var2.write(var4);
} while(var4.hasRemaining());
var4.rewind();
var3 = var1.accept();
do {
var3.read(var5);
} while(var5.hasRemaining());
var5.rewind();
if(var5.equals(var4)) {
PipeImpl.this.source = new SourceChannelImpl(Initializer.this.sp, var2);
PipeImpl.this.sink = new SinkChannelImpl(Initializer.this.sp, var3);
break;
}
var3.close();
var2.close();
}
} catch (IOException var18) {
try {
if(var2 != null) {
var2.close();
}
if(var3 != null) {
var3.close();
}
} catch (IOException var17) {
;
}
Initializer.this.ioe = var18;
} finally {
try {
if(var1 != null) {
var1.close();
}
} catch (IOException var16) {
;
}
}
}
这里尝试通过两条SocketChannel来联通一个ServerSocketChannel,先看val1通过ServerSocketChannel.open(),通过SelectorProviderImpl的openServerSocketChannel()
public ServerSocketChannel openServerSocketChannel() throws IOException {
return new ServerSocketChannelImpl(this);
}
返回ServerSocketChannelImpl
ServerSocketChannelImpl(SelectorProvider var1) throws IOException {
super(var1);
this.fd = Net.serverSocket(true);
this.fdVal = IOUtil.fdVal(this.fd);
this.state = 0;
}
在构造方法中,先创建socket,再得到它的fd跟fdVal。然后将创建的socket绑定本地机的0号端口(var1.socket().bind(new InetSocketAddress(var6, 0));)然后根据这个socket的ip跟端口生成一个SocketChannel(var2)。然后向var2中写之前生成的随机数RANDOM_NUMBER_GENERATOR。然后另一条无非是var1监听连接请求生成的一条socketChannel(var3)。从var3中读取之前var2发送的随机数,如果传输成功(即前后收发的随机数相同),如果无误,说明一条Pipe被成功建立起来。然后把var2设为source,var3设为sink。成功建立起Pipe后我们回到WindowsSelectorImpl的构造方法中
WindowsSelectorImpl(SelectorProvider var1) throws IOException {
super(var1);
this.wakeupSourceFd = ((SelChImpl)this.wakeupPipe.source()).getFDVal();
SinkChannelImpl var2 = (SinkChannelImpl)this.wakeupPipe.sink();
var2.sc.socket().setTcpNoDelay(true);
this.wakeupSinkFd = var2.getFDVal();
this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0);
}
得到source的fd跟sink的fd并保存,把wakeupSourceFd加到PoolWrapper中。
void addWakeupSocket(int var1, int var2) {
this.putDescriptor(var2, var1);
this.putEventOps(var2, Net.POLLIN);
}
void putDescriptor(int var1, int var2) {
this.pollArray.putInt(SIZE_POLLFD * var1 + 0, var2);
}
void putEventOps(int var1, int var2) {
this.pollArray.putShort(SIZE_POLLFD * var1 + 4, (short)var2);
}
这里将source的POLLIN事件标识为感兴趣的,当sink端有数据写入时,source对应的文件描述符wakeupSourceFd就会处于就绪状态。
到此为止selector算是创建好了。
下面插一张大图(网上找的),我们看着图把整个过程总结一下。
Selector selector = Selector.open(),默认情况下生成了一个WindowsSelectorImpl实例,并建立了Pipe,同时把Pipe的Source端传入到poolArray中。其中Pipe建立,先生成一个监听本地地址,0端口的serverSocket,再生成一个Socket连接ServerSocket,这个SocketChannel是source端,serverSocket的accept得到一个SocketChannel是sink端,于是从source向sink传一随机数,sink收到正确的后,证明Pipe建立成功。将Source端传入poolArray后,当sink端有数据传入时,source端对应的文件描述符wakeupSourceFd就会处于就绪状态。
真的是配合图片食用,口感更佳。