盘一盘 NIO (三)—— Selector解析

Selector是个啥?

Selector是Java NIO核心组件中的选择器,用于检查一个或多个Channel(通道)的状态是否处于可读、可写。实现一个单独的线程可以管理多个channel,从而管理多个网络连接。使用一个线程进行处理,也避免了线程上下文切换带来的开销。
 
而在传统IO模型下,服务器处理请求就是accept()方法阻塞等待请求进来,有请求连接之后,创建一个线程去保持连接直到socket关闭。虽然你可以采用复用线程池的方式减少开销,但在应对大量请求时,也无可避免的会造成性能问题。

 

 

继承关系图

 

抽象类方法

Selector是一个抽象类,并没有写什么具体实现。我们先简单看看有哪些功能。
public abstract class Selector implements Closeable {

    // 构造方法
    protected Selector() { }

    // 打开选择器
    public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
    }

    // 判断此选择器是否已打开。
    public abstract boolean isOpen();

    // 返回创建Channel的选择器生产者
    public abstract SelectorProvider provider();

    // 返回选择器的key set
    public abstract Set<SelectionKey> keys();

    // 返回此选择器的selected-key集。
    public abstract Set<SelectionKey> selectedKeys();

    // 对选择的一组键所对应的通道准备进行IO操作。
    public abstract int selectNow() throws IOException;

    public abstract int select(long timeout)
        throws IOException;

    public abstract int select() throws IOException;

    // 唤醒阻塞在selector.select上的线程,让该线程及时去处理其他事情,
  // 例如注册channel,改变interestOps、判断超时等等。 
public abstract Selector wakeup(); // 关闭选择器 public abstract void close() throws IOException; }

 

SelectableChannel是个啥?

SelectableChannel提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。继承了SelectableChannel类的Channel都是可选择的。而FileChannel类并没有继承SelectableChannel,因此不是可选通道。
PS:一个通道可以被注册到多个选择器上,但对于每个选择器而言只能被注册一次。
 

抽象类方法

public abstract class SelectableChannel
    extends AbstractInterruptibleChannel
    implements Channel
{
    // 构造方法
    protected SelectableChannel() { }

    // 返回创建Channel的选择器生产者
    public abstract SelectorProvider provider();

    // 返回有效操作集
    public abstract int validOps();

    // 判断此Channel是否在Selector中注册
    public abstract boolean isRegistered();

        // 返回Channel在Selector中的注册的选择键
    public abstract SelectionKey keyFor(Selector sel);

    public abstract SelectionKey register(Selector sel, int ops, Object att)
        throws ClosedChannelException;

    public final SelectionKey register(Selector sel, int ops)
        throws ClosedChannelException
    {
        return register(sel, ops, null);
    }

    // 调整此通道为阻塞模式。
    public abstract SelectableChannel configureBlocking(boolean block)
        throws IOException;

    // 调整此通道是否阻塞
    public abstract boolean isBlocking();

    // 返还阻塞模式锁定的对象
    public abstract Object blockingLock();
}
 

SelectionKey是个啥?

SelectionKey顾名思义选择键,选择键中包含了注册在Selector的通道操作的类型,也包含了了特定的通道与特定的选择器的注册关系。
Channel和Selector进行绑定,一旦通道处于某种就绪的状态,就可以被Selector感知。Selector会根据对应的选择键,进行不同的业务逻辑处理。
 

继承关系图

 

抽象类方法

public abstract class SelectionKey {

    // 构造方法
    protected SelectionKey() { }

    // 返回当前选择键对应的SelectableChannel
    public abstract SelectableChannel channel();

    // 返回当前选择键对应的Selector
    public abstract Selector selector();

        // 判断当前选择键是否有效。
    public abstract boolean isValid();

    // 取消特定的注册关系。
    public abstract void cancel();

        // selector中感兴趣的集合
    public abstract int interestOps();

    public abstract SelectionKey interestOps(int ops);

    // 获取相关通道已经就绪的操作
    public abstract int readyOps();

    // SelectionKey中的四种操作类型:读、写、连接、接受。
    public static final int OP_READ = 1 << 0;
    public static final int OP_WRITE = 1 << 2;
    public static final int OP_CONNECT = 1 << 3;
    public static final int OP_ACCEPT = 1 << 4;

    public final boolean isReadable() {
        return (readyOps() & OP_READ) != 0;
    }

    public final boolean isWritable() {
        return (readyOps() & OP_WRITE) != 0;
    }

    public final boolean isConnectable() {
        return (readyOps() & OP_CONNECT) != 0;
    }

    public final boolean isAcceptable() {
        return (readyOps() & OP_ACCEPT) != 0;
    }
    // SelectionKey上的附加对象
    private volatile Object attachment = null;

    private static final AtomicReferenceFieldUpdater<SelectionKey,Object>
        attachmentUpdater = AtomicReferenceFieldUpdater.newUpdater(
            SelectionKey.class, Object.class, "attachment"
        );

        // 将附加对象绑定到SelectionKey上,便于识别给定的通道
    public final Object attach(Object ob) {
        return attachmentUpdater.getAndSet(this, ob);
    }

    // 取出绑定在SelectionKey上的附加对象
    public final Object attachment() {
        return attachment;
    }
}

 

关键方法解析

在了解了Selector,SelectableChannel,SelectionKey的概念后,我们进一步深入源码

open方法

打开选择器,创建Selector对象

public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();
}
 
SelectorProvider的静态方法provider
public static SelectorProvider provider() {
    synchronized (lock) {
        // 判断provider是否已经产生,若已产生则直接返回
        if (provider != null)
            return provider;
        // 若未产生,则需要调用AccessController的静态方法doPrivileged,创建一个新的Selector对象
        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;
                    }
                });
    }
}

 

select方法

// 由其基类SelectorImpl中实现
public int select() throws IOException {
    return this.select(0L);
}

public int select(long var1) throws IOException {
    if (var1 < 0L) {
        throw new IllegalArgumentException("Negative timeout");
    } else {
        // slect()无参时默认传入0,实则交给lockAndDoSelect方法去完成,并且令参数为-1
        return this.lockAndDoSelect(var1 == 0L ? -1L : var1);
    }
}

private int lockAndDoSelect(long var1) throws IOException {
    synchronized(this) {
        // 先判断当前的Selector对象是否关闭
        if (!this.isOpen()) {
            throw new ClosedSelectorException();
        } else {
            // 分别以publicKeys以及publicSelectedKeys为锁,最终的实现交给抽象方法doSelect完成;
            int var10000;
            synchronized(this.publicKeys) {
                synchronized(this.publicSelectedKeys) {
                    // doSelect方法由WindowsSelectorImpl实现
                    var10000 = this.doSelect(var1);
                }
            }
            return var10000;
        }
    }
}

 

doSelect方法由WindowsSelectorImpl实现:

// channelArray是一个SelectionKeyImpl数组,SelectionKeyImpl负责记录Channel和SelectionKey状态
// channelArray是根据连接的Channel数量动态维持的,初始化大小是8。
private SelectionKeyImpl[] channelArray = new SelectionKeyImpl[8];

protected int doSelect(long var1) throws IOException {
    if (this.channelArray == null) {
        throw new ClosedSelectorException();
    } else {
        this.timeout = var1;
        // 调用processDeregisterQueue方法来取消准备撤销的集合
        this.processDeregisterQueue();
        if (this.interruptTriggered) {
            // 若是发生了中断,调用resetWakeupSocket方法恢复中断
            this.resetWakeupSocket();
            return 0;
        } else {
            // 未发生中断,调用adjustThreadsCount调整轮询线程数量
            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;
        }
    }
}

 

Selector使用示例

服务端

public static void main(String[] args) throws Exception {
    try {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8000));
        ssc.configureBlocking(false);

        Selector selector = Selector.open();
        // 注册 channel,并且指定感兴趣的事件是 Accept
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        ByteBuffer readBuff = ByteBuffer.allocate(1024);
        ByteBuffer writeBuff = ByteBuffer.allocate(128);
        writeBuff.put("received".getBytes());
        writeBuff.flip();

        while (true) {
            int nReady = selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();

            while (it.hasNext()) {
                SelectionKey key = it.next();
                it.remove();

                if (key.isAcceptable()) {
                    // 创建新的连接,并且把连接注册到selector上
                    // 声明这个channel只对读操作感兴趣。
                    SocketChannel socketChannel = ssc.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }
                else if (key.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    readBuff.clear();
                    socketChannel.read(readBuff);

                    readBuff.flip();
                    System.out.println("received : " + new String(readBuff.array()));
                    key.interestOps(SelectionKey.OP_WRITE);
                }
                else if (key.isWritable()) {
                    writeBuff.rewind();
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    socketChannel.write(writeBuff);
                    key.interestOps(SelectionKey.OP_READ);
                }
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

 

客户端

public static void main(String[] args) throws Exception {
    try {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));

        ByteBuffer writeBuffer = ByteBuffer.allocate(32);
        ByteBuffer readBuffer = ByteBuffer.allocate(32);

        writeBuffer.put("hello".getBytes());
        writeBuffer.flip();


        while (true) {
            Thread.sleep(1000);
            writeBuffer.rewind();
            socketChannel.write(writeBuffer);
            readBuffer.clear();
            socketChannel.read(readBuffer);
        }
    } catch (IOException e) {
    }
}

 

 

 

 

posted @ 2019-08-25 20:58  柠檬五个半  阅读(787)  评论(0编辑  收藏  举报