NIO中的Selector 的使用方法介绍

2024-05-08 09:10:09 浏览数 (1)

Selector 的创建

通过调用 Selector.open() 方法可以创建一个 Selector 对象:

代码语言:java复制
Selector selector = Selector.open();

注册 Channel 到 Selector

要实现 Selector 管理 Channel,需要将 Channel 注册到相应的 Selector 上。注册时还需要指定 Channel 所关心的事件类型,例如“接收”事件:

代码语言:java复制
// 1、获取 Selector 选择器  
Selector selector = Selector.open();  
  
// 2、获取通道  
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();  
  
// 3. 设置为非阻塞  
serverSocketChannel.configureBlocking(false);  
  
// 4、绑定连接  
serverSocketChannel.bind(new InetSocketAddress(9999));  
  
// 5、将通道注册到选择器上,并指定监听事件为:“接收”事件  
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

注意事项

(1)与 Selector 一起使用时,Channel 必须处于非阻塞模式下,否则将抛出异常 IllegalBlockingModeException

(2)一个通道,并没有一定要支持所有的四种操作(ACCEPT, CONNECT, READ, WRITE)。比如 ServerSocketChannel 支持 Accept 接受操作,而 SocketChannel 客户端通道则不支持 Accept。可以通过通道上的 validOps() 方法,来获取特定通道下所有支持的操作集合。

轮询查询就绪操作

通过 Selector 的 select() 方法,可以查询出已经就绪的通道操作,这些就绪的状态集合包存在一个元素是 SelectionKey 对象的 Set 集合中。

select() 方法重载

  • select(): 阻塞到至少有一个通道在你注册的事件上就绪了。
  • select(long timeout): 和 select() 一样,但最长阻塞事件为 timeout 毫秒。
  • selectNow(): 非阻塞,只要有通道就绪就立刻返回。

select() 方法返回的 int 值,表示有多少通道已经就绪。一旦调用 select() 方法,并且返回值不为 0 时,在 Selector 中有一个 selectedKeys() 方法,用来访问已选择键集合。可以迭代集合的每一个选择键元素,根据就绪操作的类型,完成对应的操作:

代码语言:java复制
Set<SelectionKey> selectedKeys = selector.selectedKeys();  
Iterator<SelectionKey> 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();  
}

注意事项

  • 在处理完一个 SelectionKey 后,一定要从 selectedKeys 集合中移除它,否则下次 select() 时,这个 SelectionKey 仍然处于选中状态,会重复处理。
  • 在处理 SelectionKey 时,要小心不要执行阻塞操作,因为这会阻塞整个 Selector 线程。如果需要进行耗时操作,请使用其他线程。

停止选择的方法

当使用 Selector 时,我们可能需要在某个时候停止选择操作或关闭 Selector。以下是您提到的两种方法的详细说明:

wakeup() 方法

Selectorwakeup() 方法用于唤醒在 select() 调用中阻塞的线程。无论 select() 是正在阻塞等待就绪的通道,还是已经被 select(long timeout) 设置了一个超时时间,调用 wakeup() 都会使得 select() 调用立即返回。

如果 select() 调用没有阻塞(即已经返回或者还没有开始调用),那么下一次对 select() 的调用将立即返回,无需等待。

代码语言:java复制
Selector selector = Selector.open();  
// ... 注册通道到 selector ...  
  
// 某个线程中调用 select()  
int readyChannels = selector.select();  
  
// 另一个线程或同一个线程的其他部分调用 wakeup()  
selector.wakeup();  
  
// 这将使得上面的 select() 调用返回,即使没有任何通道就绪

close() 方法

Selectorclose() 方法会关闭选择器,并唤醒所有在 select() 调用中阻塞的线程。与 wakeup() 不同的是,close() 会导致所有注册到该选择器的通道被注销,并且所有的 SelectionKey 实例都将被取消。但请注意,close() 方法并不会关闭通道本身,只是取消它们与选择器的关联。

一旦选择器被关闭,任何后续对选择器的访问(除了 isOpen())都将抛出 ClosedSelectorException

代码语言:java复制
Selector selector = Selector.open();  
// ... 注册通道到 selector ...  
  
// 某个线程中调用 select()  
int readyChannels = selector.select();  
  
// 另一个线程或同一个线程的其他部分调用 close()  
selector.close();  
  
// 这将使得上面的 select() 调用返回(如果它还在阻塞的话),  
// 并且所有注册的通道都被注销,所有的 SelectionKey 都被取消  
  
// 尝试再次使用 selector 将抛出 ClosedSelectorException  
// 例如:selector.select(); // 这将抛出 ClosedSelectorException

在实际应用中,应该根据具体需求选择使用wakeup()还是close()。如果你只是想唤醒阻塞的线程,并且希望继续使用该选择器,那么应该使用wakeup()。如果你打算完全关闭选择器并清理所有资源,那么应该使用close()

0 人点赞