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就会处于就绪状态。


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



猜你喜欢

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