IO学习——Selector

概述

Selector , 一般称为选择器。它是 Java NIO 核心组件中的一个,用于轮询一个或多个 NIO Channel 的状态是否处于可读、可写。如此,一个线程就可以管理多个 Channel ,也就说可以管理多个网络连接。也因此,Selector 也被称为多路复用器

 

轮询机制

  1. 首先,需要将 Channel 注册到 Selector 中;
  2. 然后,Selector 会不断地轮询注册在其上的 Channel
  3. 如果某个Channel上面发生了读或者写的事件,这个Channel就处于就绪状态。
  4. Selector轮询出符合条件的Channel,通过返回SelectionKey 获得Channel 的集合,然后进行业务操作

Selectorï¼éæ©å¨ï¼

 

优缺点

优点

使用一个线程管理多个Channel,减少线程上下文切换的资源消耗。

缺点

一个线程中处理了多个Channel,会导致Channel处理效率的降低。

ps.Netty 在设计实现上,通过 n 个线程处理多个 Channel ,从而很好的解决了这样的缺点。其中,n 的指的是有限的线程数,默认情况下为 CPU * 2(后面学习Netty的时候了解到)

创建

Selector selector = Selector.open();

注册

//设置该 Channel 必须是非阻塞
channel.configureBlocking(false); // <1>
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

第二个参数,表示一个“interest 集合”,意思是通过 Selector 监听 Channel 时要监听的事件

  • Connect :连接完成事件( TCP 连接 ),仅适用于客户端,对应 SelectionKey.OP_CONNECT ;一个 Client Channel Channel 成功连接到另一个服务器,称为“连接就绪”。
  • Accept :接受新连接事件,仅适用于服务端,对应 SelectionKey.OP_ACCEPT ;一个 Server Socket Channel 准备好接收新进入的连接,称为“接收就绪”。
  • Read :读事件,适用于两端,对应 SelectionKey.OP_READ ,表示 Buffer 可读;一个有数据可读的 Channel ,可以说是“读就绪”。
  • Write :写时间,适用于两端,对应 SelectionKey.OP_WRITE ,表示 Buffer 可写;一个等待写数据的 Channel ,可以说是“写就绪”。

变更监听事件

实际使用时,我们会有改变 Selector 对 Channel 感兴趣的事件集合,可以通过再次调用 #register(Selector selector, int interestSet) 方法来进行变更

//初始时,Selector 仅对 Channel 的 SelectionKey.OP_READ 事件感兴趣。
channel.register(selector, SelectionKey.OP_READ);
//修改后,Selector 仅对 Channel 的 SelectionKey.OP_READ 和 SelectionKey.OP_WRITE) 事件都感兴趣。
channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);

SelectionKey 

当我们调用 Channel 的 #register(...) 方法,向 Selector 注册一个 Channel 后,会返回一个 SelectionKey 对象。表示一个 Channel 和一个 Selector 的注册关系。

包含的主要内容:

  • interest set :感兴趣的事件集合。
  • ready set :就绪的事件集合。
  • Channel
  • Selector
  • attachment :可选的附加对象。
key.attachment(); //返回SelectionKey的attachment,attachment可以在注册channel的时候指定。
key.channel(); // 返回该SelectionKey对应的channel。
key.selector(); // 返回该SelectionKey对应的Selector。
key.interestOps(); //返回代表需要Selector监控的IO操作的bit mask
key.readyOps(); // 返回一个bit mask,代表在相应channel上可以进行的IO操作。

key.interestOps():

判断Selector是否对Channel的某种事件感兴趣

int interestSet = selectionKey.interestOps();

// 判断对哪些事件感兴趣
boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT != 0;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT != 0;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ != 0;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE != 0;

key.readyOps()

返回就绪的通道和集合

int readySet = selectionKey.readyOps();

// 判断哪些事件已就绪
selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

key.attachment()

通过调用 #attach(Object ob) 方法,可以向 SelectionKey 添加附加对象;通过调用 #attachment() 方法,可以获得 SelectionKey 获得附加对象

注册时添加对象
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
//添加对象
selectionKey.attach(theObject);
//获取对象
Object attachedObj = selectionKey.attachment();

 

主要方法

通过 Selector 选择 Channel


在 Selector 中,提供三种类型的选择( select )方法,返回当前有感兴趣事件准备就绪的 Channel 数量

// Selector.java

// 阻塞到至少有一个 Channel 在你注册的事件上就绪了。
public abstract int select() throws IOException;

// 在 `#select()` 方法的基础上,增加超时机制。
public abstract int select(long timeout) throws IOException;

// 和 `#select()` 方法不同,立即返回数量,而不阻塞。
public abstract int selectNow() throws IOException;
  • 有一点非常需要注意:select 方法返回的 int 值,表示有多少 Channel 已经就绪。亦即,自上次调用 select 方法后有多少 Channel 变成就绪状态。如果调用 select 方法,因为有一个 Channel 变成就绪状态则返回了 1 ;若再次调用 select 方法,如果另一个 Channel 就绪了,它会再次返回1。如果对第一个就绪的 Channel 没有做任何操作,现在就有两个就绪的 Channel ,但在每次 select 方法调用之间,只有一个 Channel 就绪了,所以才返回 1

获取可操作的 Channel

一旦调用了 select 方法,并且返回值表明有一个或更多个 Channel 就绪了,然后可以通过调用Selector 的 #selectedKeys() 方法,访问“已选择键集( selected key set )”中的就绪Channel 

  • 注意,当有新增就绪的 Channel ,需要先调用 select 方法,才会添加到“已选择键集( selected key set )”中。否则,我们直接调用 #selectedKeys() 方法,是无法获得它们对应的 SelectionKey 们。

唤醒

某个线程调用 #select() 方法后,发生阻塞了,即使没有通道已经就绪,也有办法让其从 #select() 方法返回

  • 只要让其它线程在第一个线程调用 select() 方法的那个 Selector 对象上,调用该 Selector 的 #wakeup() 方法,进行唤醒该 Selector 即可。
  • Selector 的 #select(long timeout) 方法,若未超时的情况下,也可以满足上述方式。

 

关闭

可以调用 Selector 的 #close() 方法,将它进行关闭

  • Selector 相关的所有 SelectionKey 都会失效
  • Selector 相关的所有 Channel 并不会关闭

demo

        try {
            //创建 Channel
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("地址", 80));
            // 创建 Selector
            Selector selector = Selector.open();
            //设置阻塞
            socketChannel.configureBlocking(false);
            socketChannel.register(selector,SelectionKey.OP_READ);
            while (true) {
                // 通过 Selector 选择 Channel
                int read = selector.select();
                if (read == 0) {
                    continue;
                }
                // 获得可操作的 Channel
                Set selectedKeys = selector.selectedKeys();
                // 遍历 SelectionKey 数组
                Iterator keyIterator = selectedKeys.iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    if (key.isAcceptable()) {
                        // a connection was accepted by a ServerSocketChannel.
                    } else if (key.isConnectable()) {
                        // a connection was established with a remote server.
                    } else if (key.isReadable()) {
                        // a channel is ready for reading
                    } else if (key.isWritable()) {
                        // a channel is ready for writing
                    }
                    // 移除
                    keyIterator.remove(); // <1>
                }
            }
        } catch (Exception e) {

        }

参考文章:

http://www.iocoder.cn/

你可能感兴趣的:(IO学习——Selector)