看了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就会处于就绪状态。
真的是配合图片食用,口感更佳。