前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >NIO中的Selector 的使用方法介绍

NIO中的Selector 的使用方法介绍

原创
作者头像
一个风轻云淡
发布2024-05-08 09:10:09
1700
发布2024-05-08 09:10:09

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()

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Selector 的创建
  • 注册 Channel 到 Selector
  • 轮询查询就绪操作
  • 停止选择的方法
    • wakeup() 方法
      • close() 方法
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档