JAVA NIO源码分析 之Selector

      先上一段代码

 public static void main(String[] args) throws Exception{

        Selector selector = SelectorProvider.provider().openSelector();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel
                .open()
                .bind(new InetSocketAddress("127.0.0.1",9999));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("等待连接");
        while (true) {
            while (selector.select()>0){
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                for (SelectionKey key:selectionKeys) {
                    if (key.isAcceptable()){
                        System.out.println("接受");
                        selectionKeys.remove(key);
                        System.out.println(selector.select());
                        System.out.println(selector.select());
                    }
                }
            }

        }


    }

        selector.select() 选择一组其相应通道准备好进行I / O操作的键,如果没有的话会阻塞直到有通道准备好。但是上面的代码。两个输出你可以自己运行一下看一下结果。第一个输出会立即返回1,第二个输出会立即返回0。很奇怪,不是应该阻塞的吗。

       来分析一下,首先我们要知道,selector选择器是怎么被创建的。selector的创建有两种方式。1、selector.open() 2、

SelectorProvider.provider().openSelector() 。  可以看到1 方法调用的2方法,创建的selector。

    先进入provider()方法里面看一下

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是怎么返回的就好。有三个地方会返回provider。

     第一个loadProviderFromProperty 是从系统属性里找到 SelectorProvider 的全限定名然后用反射实例化,第二个是SPI,不了解的可以自己查一下。就是根据类路径下所有META-INFO/service目录下下有没有一个java.nio.channels.spi.SelectorProvider文件。有的话根据文件里的class全限定名,加载类并初始化。 但是这两种一般都没有。现在只剩下最有一个,调用sun.nio.ch.DefaultSelectorProvider.create()方法创建了。那我们之间进入这个create方法里面。看一下发生了什么。 

public class DefaultSelectorProvider {
    private DefaultSelectorProvider() {
    }

    public static SelectorProvider create() {
        return new WindowsSelectorProvider();
    }
}

    里面跟简单。返回了一个WindowsSelectorProvider对象。进入这个对象看到了一个openselector方法。现在清楚了。选择器创建的过程其实就是创建一个windowSelectorProvider 并调用他的openSelector方法。返回一个windowsSelectorImpl。当然,一看名字就知道。这是在windows环境下返回的对象。要是在linux上返回的会是其他的对象。                  

​
public class WindowsSelectorProvider extends SelectorProviderImpl {
    public WindowsSelectorProvider() {
    }

    public AbstractSelector openSelector() throws IOException {
        return new WindowsSelectorImpl(this);
    }
}

    在第一段代码。先关注一下register方法。

debug第一段代码,在register方法打断点,然后点进去看是一个抽象方法,但是方法左边有绿色的点。点击它就会进入到子类AbstractSelectableChannel的实现方法

 public final SelectionKey register(Selector sel, int ops,
                                       Object att)
        throws ClosedChannelException
    {
        synchronized (regLock) {
            if (!isOpen())
                throw new ClosedChannelException();
            if ((ops & ~validOps()) != 0)
                throw new IllegalArgumentException();
            if (blocking)
                throw new IllegalBlockingModeException();
            SelectionKey k = findKey(sel);
            if (k != null) {
                k.interestOps(ops);
                k.attach(att);
            }
            if (k == null) {
                // New registration
                synchronized (keyLock) {
                    if (!isOpen())
                        throw new ClosedChannelException();
                    k = ((AbstractSelector)sel).register(this, ops, att);
                    addKey(k);
                }
            }
            return k;
        }
    }
看一下,他又调用了一个register方法。这个方法依然是抽象方法,再点击绿点,找到实现方法。在类SelectorImpl里
protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
        if (!(var1 instanceof SelChImpl)) {
            throw new IllegalSelectorException();
        } else {
            SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
            var4.attach(var3);
            Set var5 = this.publicKeys;
            synchronized(this.publicKeys) {
                this.implRegister(var4);
            }

            var4.interestOps(var2);
            return var4;
        }
    }

它创建了一个selectionKey并且,调用了implRegister在点进去看

 

 protected void implRegister(SelectionKeyImpl var1) {
        Object var2 = this.closeLock;
        synchronized(this.closeLock) {
            if (this.pollWrapper == null) {
                throw new ClosedSelectorException();
            } else {
                this.growIfNeeded();
                this.channelArray[this.totalChannels] = var1;
                var1.setIndex(this.totalChannels);
                this.fdMap.put(var1);
                this.keys.add(var1);
                this.pollWrapper.addEntry(this.totalChannels, var1);
                ++this.totalChannels;
            }
        }
    }

重点看一下fdMap.put。他往WindowsSelectorImpl.MapEntry里放了创建的SelectionKey。记住这一步下面会用到。

    下面回到我有疑问的select方法,点击select方法。这是一个抽象方法,那么他怎么实现的呢。不要着急,因为真正的对象是windowsSelevtorImpl ,咱们看一下他里面有没有。进去看也没有,但是注意它还继承了一个selectorImpl类点进去看

  public int select(long var1) throws IOException {
        if (var1 < 0L) {
            throw new IllegalArgumentException("Negative timeout");
        } else {
            return this.lockAndDoSelect(var1 == 0L ? -1L : var1);
        }
    }

    public int select() throws IOException {
        return this.select(0L);
    }

 

 private int lockAndDoSelect(long var1) throws IOException {
        synchronized(this) {
            if (!this.isOpen()) {
                throw new ClosedSelectorException();
            } else {
                Set var4 = this.publicKeys;
                int var10000;
                synchronized(this.publicKeys) {
                    Set var5 = this.publicSelectedKeys;
                    synchronized(this.publicSelectedKeys) {
                        var10000 = this.doSelect(var1);
                    }
                }

                return var10000;
            }
        }
    }

经过一层一层的调用,你会看到一个doselector方法,他的子类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方法是处理,处理取消注册的selectonkey的方法。其他的不用看。先关注一下。subSelector.poll()点击去看他最后调用了。

 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);

        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;
        }

一个native方法poll0,你可以试着调试一下,windows系统,会用两个socketchannel实现一个管道。但是具体的我也迷糊,但是,如果在selector注册的channel没有准备好。就会在poll0方法阻塞住。当他返回后继续执行直到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;
}

他更新了一个叫updateCount的值。然后调用了  this.subSelector.processSelectedKeys(this.updateCount);方法,再点进去看一下,重点来了。

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;
    }
}

         这个方法 会 进入到第一个else块里,并取出 fdMap里面之前放进去的SelectionKeyImpl对象。记得吗,在注册时这个 SelectionKeyImpl就有了。经过判断selectorKeys里面有没有这个key,但是selectorKeys里面之前还没有放进去值。所以,就会跳到  

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;
        }

里面。然后进入translateAndSetReadyOps方法。这个方法也是抽象方法,实际调用的是ServerSocketChannelImpl里面的方法,

 public boolean translateReadyOps(int var1, int var2, SelectionKeyImpl var3) {
        int var4 = var3.nioInterestOps();
        int var5 = var3.nioReadyOps();
        int var6 = var2;
        if ((var1 & Net.POLLNVAL) != 0) {
            return false;
        } else if ((var1 & (Net.POLLERR | Net.POLLHUP)) != 0) {
            var3.nioReadyOps(var4);
            return (var4 & ~var5) != 0;
        } else {
            if ((var1 & Net.POLLIN) != 0 && (var4 & 16) != 0) {
                var6 = var2 | 16;
            }

            var3.nioReadyOps(var6);
            return (var6 & ~var5) != 0;
        }
    }

    这里面都是位操作。首先取出感兴趣的操作insterestOps,此时他是OP_ACCEPT,也就是16。再取出准备好的操作readyOps.,但是readyOps之前还没有被赋值,此时是空的,在这里var2直接传进来的是0,所以var6 就为0。一般会进入最后一个lese块里面。进行位操作 0|16 ,此时var6等于16.然后调用var3,nioReadyOps(var6)把16赋给readyOps 。最后判断一下var5和var6的位操作。16 &^0  等于16所以返回 true 。然后返回到processFDset方法。判断redsyOPs和insertOPS 的 & 造作。 16 & 16 当然不为0. 所以 会把updateCount加1然后把processFDset 方法的局部变量var6加1。之后把clearCuont加1。因为只有注册了一个channel的accept事件所以直接把var6返回了。Var6就是准备好的channel数。跟其他的processFDset方法返回值加起来,就是准备好的总数了。这里就是1了。这都很正常,记得开头说过最后调用的两个select方法,一个返回1一个返回0,并且都不阻塞吗?下面分析为什么。

    还记得之前的poll0方法吗,如果之间已有通道准备好,但是你没处理,下次再调用select方法时候他就会直接返回。以为那个在那个通道你感兴趣操作依然是准备好的状态可以返回。当我在第一段代码的selectionKeys.remove(key) 操作时,selectionKeys在这里里面已经没有值了,但是开头让大家记住的FDmap里面的selectionKey依然存在,而在上面的的processFDSet方法中,已经说过会取出FDmap里面的key,如果不为空,继续执行判断selectionKeys是否为空,由于我之前把它清空了,当然就会重复执行,上面说的一系列操作,返回结果依然是1。但是我如果不调用selectionKeys.remove(key)操作。判断selectionKeys不为空就会执行

  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;
                                }

这里的代码。不管怎样都执行translateAndSetReadyOps,还记得吗他会取出感兴趣的造作和准备好的操作,因为readyOps第一次已经赋值16,那么var6就会为16再跟下面的var5也就是readyOps进行&^操作, 就会为0返回false,没有给processFDSet方法里的var6执行加1。它就自然就为0了,然后执行了余下的processFDSet也返回0这样最后select方法就直接返回的0并且没有阻塞,也就是说,上次准备好的channel事件依然准备好在哪里等待着被处理,返回0带表上次准备好的channel还有没处理,下一次的也没进来。当我调用selectionKeys.remove(key)后,返回1是只是我之前把准备的好的key给删了,select方法忘了它刚才处理过了,所以才可以继续返回1。这就是为什么奇怪select可以直接返回0的原因了。

关于sector里面管道什么的和其他的如果想了解,可以看一下下面的博客

https://blog.csdn.net/u010412719/article/details/52809669

https://blog.csdn.net/u010412719/article/details/52819191

猜你喜欢

转载自blog.csdn.net/qq_35368651/article/details/82317642