jdk的nio之selector(3)

来看看selector最核心的select()方法吧

我们之前分析了Selector默认是WindowSelectorImpl的一个实例,调用selector(timeout),timout是给Selector设置的时间参数,之后调用lockAndDoSelector,lockAndDoSelector方法核心就是调用抽象方法doSelect,此时加了synchronize同步。我们看下子类的doSelect的实现,默认实现是在WindowsSelectorImpl类下。

    protected int doSelect(long var1) throws IOException {
        if(this.channelArray == null) {
            throw new ClosedSelectorException();
        } else {
            this.timeout = var1;
            this.processDeregisterQueue();
            if(this.interruptTriggered) {
                this.resetWakeupSocket();
                return 0;
            } else {
                this.adjustThreadsCount();
                this.finishLock.reset();
                this.startLock.startThreads();

                try {
                    this.begin();

                    try {
                        this.subSelector.poll();
                    } catch (IOException var7) {
                        this.finishLock.setException(var7);
                    }

                    if(this.threads.size() > 0) {
                        this.finishLock.waitForHelperThreads();
                    }
                } finally {
                    this.end();
                }

                this.finishLock.checkForException();
                this.processDeregisterQueue();
                int var3 = this.updateSelectedKeys();
                this.resetWakeupSocket();
                return var3;
            }
        }
    }

首先调用processDeregisterQueue()方法,把已经解除注册的channel进行注销注册。

    void processDeregisterQueue() throws IOException {
        Set var1 = this.cancelledKeys();
        synchronized(var1) {
            if(!var1.isEmpty()) {
                Iterator var3 = var1.iterator();

                while(var3.hasNext()) {
                    SelectionKeyImpl var4 = (SelectionKeyImpl)var3.next();

                    try {
                        this.implDereg(var4);
                    } catch (SocketException var11) {
                        throw new IOException("Error deregistering key", var11);
                    } finally {
                        var3.remove();
                    }
                }
            }

        }
    }

遍历加到cancelledKeys中的SelectorKey(需要解除注册的key),依次调用implDereg()进行注销。

    protected void implDereg(SelectionKeyImpl var1) throws IOException {
        int var2 = var1.getIndex();

        assert var2 >= 0;

        Object var3 = this.closeLock;
        synchronized(this.closeLock) {
            if(var2 != this.totalChannels - 1) {
                SelectionKeyImpl var4 = this.channelArray[this.totalChannels - 1];
                this.channelArray[var2] = var4;
                var4.setIndex(var2);
                this.pollWrapper.replaceEntry(this.pollWrapper, this.totalChannels - 1, this.pollWrapper, var2);
            }

            var1.setIndex(-1);
        }

        this.channelArray[this.totalChannels - 1] = null;
        --this.totalChannels;
        if(this.totalChannels != 1 && this.totalChannels % 1024 == 1) {
            --this.totalChannels;
            --this.threadsCount;
        }

        this.fdMap.remove(var1);
        this.keys.remove(var1);
        this.selectedKeys.remove(var1);
        this.deregister(var1);
        SelectableChannel var7 = var1.channel();
        if(!var7.isOpen() && !var7.isRegistered()) {
            ((SelChImpl)var7).kill();
        }

    }

如果当前解除注册key的channel是Selector中最后一个,那么直接移除,否则,需要将当前需移除的channel跟最后一个channel调换位置,再移除,以保证数组的连续性,剩下无非是注册的逆操作。

完成真正注销后,然后调用adjustThreadCount()

    private void adjustThreadsCount() {
        int var1;
        if(this.threadsCount > this.threads.size()) {
            for(var1 = this.threads.size(); var1 < this.threadsCount; ++var1) {
                WindowsSelectorImpl.SelectThread var2 = new WindowsSelectorImpl.SelectThread(var1);
                this.threads.add(var2);
                var2.setDaemon(true);
                var2.start();
            }
        } else if(this.threadsCount < this.threads.size()) {
            for(var1 = this.threads.size() - 1; var1 >= this.threadsCount; --var1) {
                ((WindowsSelectorImpl.SelectThread)this.threads.remove(var1)).makeZombie();
            }
        }

    }

该方法是对于线程数量进行调整,要求的线程数量是根据channel数目统计的,每1024个channel注册,则多一个线程来负责。根据现有线程进行动态调整。

之后调用begin()方法

    protected final void begin() {
        if (interruptor == null) {
            interruptor = new Interruptible() {
                    public void interrupt(Thread ignore) {
                        AbstractSelector.this.wakeup();
                    }};
        }
        AbstractInterruptibleChannel.blockedOn(interruptor);
        Thread me = Thread.currentThread();
        if (me.isInterrupted())
            interruptor.interrupt(me);
    }

这里先判断interruptor是否为空,空的话则创建一个,这个interruptor保证了:当前线程阻塞在io上时,当其被interruptor时,可以从select阻塞中被唤醒。在begin()完成后,将会调用其subSelector的poll,正式开始select过程。

                    private int poll() throws IOException {
            return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress,
                             Math.min(WindowsSelectorImpl.this.totalChannels, 1024),
                             this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
        }

        private int poll(int var1) throws IOException {
            return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress +
                         (long)(this.pollArrayIndex * PollArrayWrapper.SIZE_POLLFD),
                          Math.min(1024, WindowsSelectorImpl.this.totalChannels - (var1 + 1) * 1024), this.readFds,
                          this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
        }

        private native int poll0(long var1, int var3, int[] var4, int[] var5, int[] var6, long var7);

这里的poll0()是native方法。主要是为了监听pollWrapper中所保存的fd是否有数据进出,如果没有进出,则会在在此处在timeout的时间里一直保持阻塞状态。

当poll取得数据后,会重新检查一遍已经被取消注册的channel,并注销它,之后调用updateSelectedKeys()

    private int updateSelectedKeys() {
        ++this.updateCount;
        byte var1 = 0;
        int var4 = var1 + this.subSelector.processSelectedKeys(this.updateCount);

        WindowsSelectorImpl.SelectThread var3;
        for(Iterator var2 = this.threads.iterator(); var2.hasNext(); 
                    var4 += var3.subSelector.processSelectedKeys(this.updateCount)) {
            var3 = (WindowsSelectorImpl.SelectThread)var2.next();
        }

        return var4;
    }
这里对所有的线程调用processSelectedKeys来处理所有线程在poll过程中取得的结果并处理。然后返回所有线程中处理的channel数量的总和。我们来看下processSelectedKeys()方法
        private int processSelectedKeys(long var1) {
            byte var3 = 0;
            int var4 = var3 + this.processFDSet(var1, this.readFds, Net.POLLIN, false);
            var4 += this.processFDSet(var1, this.writeFds, Net.POLLCONN | Net.POLLOUT, false);
            var4 += this.processFDSet(var1, this.exceptFds, Net.POLLIN | Net.POLLCONN | Net.POLLOUT, true);
            return var4;
        }

        private int processFDSet(long var1, int[] var3, int var4, boolean var5) {
            int var6 = 0;

            for(int var7 = 1; var7 <= var3[0]; ++var7) {
                int var8 = var3[var7];
                if(var8 == WindowsSelectorImpl.this.wakeupSourceFd) {
                    synchronized(WindowsSelectorImpl.this.interruptLock) {
                        WindowsSelectorImpl.this.interruptTriggered = true;
                    }
                } else {
                    WindowsSelectorImpl.MapEntry var9 = WindowsSelectorImpl.this.fdMap.get(var8);
                    if(var9 != null) {
                        SelectionKeyImpl var10 = var9.ski;
                        if(!var5 || !(var10.channel() instanceof SocketChannelImpl) || 
                                    !WindowsSelectorImpl.this.discardUrgentData(var8)) {
                            if(WindowsSelectorImpl.this.selectedKeys.contains(var10)) {
                                if(var9.clearedCount != var1) {
                                    if(var10.channel.translateAndSetReadyOps(var4, var10) && var9.updateCount != var1) {
                                        var9.updateCount = var1;
                                        ++var6;
                                    }
                                } else if(var10.channel.translateAndUpdateReadyOps(var4, var10) 
                                                                        && var9.updateCount != var1) {
                                    var9.updateCount = var1;
                                    ++var6;
                                }

                                var9.clearedCount = var1;
                            } else {
                                if(var9.clearedCount != var1) {
                                    var10.channel.translateAndSetReadyOps(var4, var10);
                                    if((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {
                                        WindowsSelectorImpl.this.selectedKeys.add(var10);
                                        var9.updateCount = var1;
                                        ++var6;
                                    }
                                } else {
                                    var10.channel.translateAndUpdateReadyOps(var4, var10);
                                    if((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {
                                        WindowsSelectorImpl.this.selectedKeys.add(var10);
                                        var9.updateCount = var1;
                                        ++var6;
                                    }
                                }

                                var9.clearedCount = var1;
                            }
                        }
                    }
                }
            }

            return var6;
        }

这里首先根据poll监听到发生io事件所需要处理的fd,根据fd从selector中读取相应的已经注册了的channel,根据所发生的io事件(读,写,异常)更新channel的状态。相应的Selector的select到这里算是结束了。

最后还想看一下WindowsSelectorImpl.wakeup()的具体实现

    public Selector wakeup() {
        Object var1 = this.interruptLock;
        synchronized(this.interruptLock) {
            if(!this.interruptTriggered) {
                this.setWakeupSocket();
                this.interruptTriggered = true;
            }

            return this;
        }
    }    
    private void setWakeupSocket() {
        this.setWakeupSocket0(this.wakeupSinkFd);
    }

    private native void setWakeupSocket0(int var1);

最后找资料,windowsSelectorImpl.c中实现send(scoutFd, (char*)&POLLIN, 1, 0);这儿就跟我们之前selector.open中构造的pipe联系在了一起,向sink端写入了一个自己,注册在pollArray中的wakeupsource端的fd有变化,poll方法返回,并将线程从selector的select中唤醒。

小结

我觉得jdk 的nio的selector底层分析到这里算是完结撒花了,相信看完这几篇后那个图应该算是掌握了。


如果有机会,要去jdk的native方法底层再一探nio的究竟。在jdk层面分析也就到这里结束了。



猜你喜欢

转载自blog.csdn.net/panxj856856/article/details/80432669