看了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就會處於就緒狀態。
真的是配合圖片食用,口感更佳。