NIO边看边记 之 selector选择器(六)

12 篇文章 0 订阅

Selector(选择器)可同时监听多个通道上感兴趣的事件(如accept事件、read就绪事件、write就绪事件)。使用Selector可以实现一个线程管理多个通道,进而可以管理多个连接。

1.为什么要使用Selector

如果不使用Selector要监听多个channel上感兴趣的事件,则需要多线程操作,一个线程监听一个通道的事件。这样导致线程上下文切换的开销(内存)、增加了编程的复杂度。

2.Selector的创建、注册

(1)创建

Selector selector = Selector.open();

(2)注册
创建了Selector之后,需要将需要监听的通道以及该通道上感兴趣的事件注册在selector上。
其中感兴趣的事件包括一下4种:
SelectionKey中定义的常量:

  /**
     * Operation-set bit for read operations.
     *
     * <p> Suppose that a selection key's interest set contains
     * <tt>OP_READ</tt> at the start of a <a
     * href="Selector.html#selop">selection operation</a>.  If the selector
     * detects that the corresponding channel is ready for reading, has reached
     * end-of-stream, has been remotely shut down for further reading, or has
     * an error pending, then it will add <tt>OP_READ</tt> to the key's
     * ready-operation set and add the key to its selected-key set.  </p>
     */
    public static final int OP_READ = 1 << 0;
    /**
     * Operation-set bit for write operations.  </p>
     *
     * <p> Suppose that a selection key's interest set contains
     * <tt>OP_WRITE</tt> at the start of a <a
     * href="Selector.html#selop">selection operation</a>.  If the selector
     * detects that the corresponding channel is ready for writing, has been
     * remotely shut down for further writing, or has an error pending, then it
     * will add <tt>OP_WRITE</tt> to the key's ready set and add the key to its
     * selected-key set.  </p>
     */
    public static final int OP_WRITE = 1 << 2;
    /**
     * Operation-set bit for socket-connect operations.  </p>
     *
     * <p> Suppose that a selection key's interest set contains
     * <tt>OP_CONNECT</tt> at the start of a <a
     * href="Selector.html#selop">selection operation</a>.  If the selector
     * detects that the corresponding socket channel is ready to complete its
     * connection sequence, or has an error pending, then it will add
     * <tt>OP_CONNECT</tt> to the key's ready set and add the key to its
     * selected-key set.  </p>
     */
    public static final int OP_CONNECT = 1 << 3;
    /**
     * Operation-set bit for socket-accept operations.  </p>
     *
     * <p> Suppose that a selection key's interest set contains
     * <tt>OP_ACCEPT</tt> at the start of a <a
     * href="Selector.html#selop">selection operation</a>.  If the selector
     * detects that the corresponding server-socket channel is ready to accept
     * another connection, or has an error pending, then it will add
     * <tt>OP_ACCEPT</tt> to the key's ready set and add the key to its
     * selected-key set.  </p>
     */
    public static final int OP_ACCEPT = 1 << 4;

SelectionKey.OP_READ表示关注读数据就绪事件
SelectionKey.OP_WRITE表示关注写数据就绪事件
SelectionKey.OP_CONNECT表示关注socket channel的连接完成事件
SelectionKey.OP_ACCEPT表示关注server-socket channel的accept事件
从上面的常量定义可以看到,在注册一个通道时可以关注该通道上的多个感兴趣事件,感兴趣事件之间使用“或”运算

int intrestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE

注册通道使用下面的方法:

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

从上面代码可以看到,需要将channel设置成非阻塞的,因此,FileChannel不可注册到selector上。因为,FileChannel不可工作在非阻塞模式下。而所有套接字通道都可以

3.SelectionKey

从上面的通道注册方法可以看到,register方法会返回一个SelectionKey对象。通过该对象可以得到以下几个属性:
interest集合
ready集合
channel
selector
附加对象
(1)interest集合
可以通过SelectionKey#interestOps()方法获取 注册的感兴趣的事件。

int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;

(2)ready集合
ready集合是通道已经准备好的就绪事件集合。

int readySet = selectionKey.readyOps();

也可以使用如下方法获取一个boolean值:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

(3)channel和selector

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();

(4)附加对象
将一个对象或者更多信息附着在SelectionKey上。
**selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();**
也可以在使用register向selector注册channel时附加对象。

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

4.通过Selector选择通道

(1)select()方法
一旦在Selector上注册了感兴趣事件的通道channel,调用select方法就可以返回这些感兴趣事件就绪的通道。select方法有以下三种重载版本。
a.select() 阻塞到至少有一个通道在你注册的事件上就绪
b.select(long timeOut) 阻塞到至少有一个通道在你注册的事件上就绪或者超时timeOut
c.selectNow() 立即返回。如果没有就绪的通道则返回0
select方法的返回值表示就绪通道的个数。
(2)selectedKeys()
调用select方法并且返回了一个或多个就绪的通道之后,就可以调用selectedKeys方法来返回已就绪通道的集合。

Set selectedKeys = selector.selectedKeys();

就像调用register方法时会返回感兴趣事件的通道的一个SelectionKey一样。这个selectedKeys返回所有已就绪(已选择)通道的集合。
示例代码如下:

Set selectedKeys = selector.selectedKeys();
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();
}

上面代码遍历这个已就绪(已选择)通道的集合,并分别处理就绪事件。
注意每次循环调用都要记得remove因为selector不会自己从已选择集合中移除selectionKey实例。必须在处理完通道时自己移除。这样,在下次select时,会将这个就绪通道添加到已选择通道集合中。
SelectionKey#channel()需要将channel类型转换成需要处理的channel类型,如ServerSocketChannel或者SocketChannel。

5.wakeUp()

当一个Selector调用select方法时,会阻塞直到有感兴趣的事件通道就绪。但是也可在其他线程中在那个selector上调用wakeUp方法,使阻塞在select上的线程立即返回。
如果调用wakeUp时并没有select线程阻塞,则下次调用select时会立即返回。

6.close()

用完selector之后可以通过调用close方法来关闭它,此时会将所有注册在该selector上的selectionKey实例失效。但是相应的channel并没有关闭。
示例代码:

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
  int readyChannels = selector.select();
  if(readyChannels == 0) continue;
  Set selectedKeys = selector.selectedKeys();
  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();
  }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值