NIO中的Selector 的使用方法介绍

NIO中的Selector 的使用方法介绍

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

}注意事项

在处理完一个 SelectionKey 后,一定要从 selectedKeys 集合中移除它,否则下次 select() 时,这个 SelectionKey 仍然处于选中状态,会重复处理。在处理 SelectionKey 时,要小心不要执行阻塞操作,因为这会阻塞整个 Selector 线程。如果需要进行耗时操作,请使用其他线程。停止选择的方法当使用 Selector 时,我们可能需要在某个时候停止选择操作或关闭 Selector。以下是您提到的两种方法的详细说明:

wakeup() 方法Selector 的 wakeup() 方法用于唤醒在 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() 方法Selector 的 close() 方法会关闭选择器,并唤醒所有在 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()。

相关文章