jdk的nio之selector(1)

看了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就會處於就緒狀態。


真的是配合圖片食用,口感更佳。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章