如何查找和取消选择器的空闲通道?

时间:2013-10-08 11:31:56

标签: java selector nio socketchannel

假设我们有一个Java NIO Selector,可以在多个SocketChannels上选择超时进行读取操作:

Selector selector = Selector.open();
channel1.register(selector, SelectionKey.OP_READ);
channel2.register(selector, SelectionKey.OP_READ);
channel3.register(selector, SelectionKey.OP_READ);
channel4.register(selector, SelectionKey.OP_READ);
// ... maybe even more ...

while (true) {
    if (selector.select(TIMEOUT) > 0) {
        Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
        while (iterator.hasNext()) {
            SelectionKey key = iterator.next();
            if (key.isValid() && key.isReadable())
                read(key);
            iterator.remove();
        }
    } else {
        // All channels timed-out! Cancel and close them all
        cancelAndCloseAll(selector.keys());
    }
}

我们需要取消并关闭某个频道,如果该频道在特定时间内处于空闲状态,这就是我们使用selector.select(TIMEOUT)方法的原因。

但如果我们有一些非常活跃的频道,这不起作用。这些活动频道永远不会让select超时,而所有其他频道可能都是空闲的。

一个天真的解决方案,如下所示(也提到here):

  

使用SelectionKey方法将最后一次活动附加到每个频道的key.attach(object)。每次成功选择后,更新所有就绪键的活动时间。然后遍历所有密钥,找到空闲时间超过特定阈值的密钥。

这可能效率非常低,因为活动通道会导致select非常频繁地触发,并且每次都会遍历所有键集。

那么有更好(更有效)的方法来解决这个问题吗?

2 个答案:

答案 0 :(得分:1)

您可以调整天真的解决方案:

  • 选择
  • 后更新就绪键的时间
  • 迭代所有其他键,如果已经过了特定的时间间隔
    • 保留全局变量阈值和变量 last_check_time ,计算当前时间 每当钥匙准备就绪时,如果(last_check_time - 当前时间)&gt;阈值,将 last_check_time 设置为当前时间并迭代密钥。
    • 或使用单个时间任务来启动迭代检查

答案 1 :(得分:0)

使用Java.util.Timer,并提交一个在闲置期后关闭通道的TimerTask。如果任务存在则取消该任务,并在运行之前获得一次通道活动时提交新任务。将TimerTasks保存在某个地方,您可以通过频道(例如,存储为密钥附件的会话对象)找到它们,这也可以保存频道的ByteBuffer,用户ID等,无论您在每个会话中需要什么。

你会遇到并发问题,这需要你 唤醒计时器任务中的选择器,并让选择器线程正确处理当没有任何准备就绪时被唤醒(select()返回由于在select()中选择器阻塞时close(()中的计时器任务被阻塞,因此,由于计时器任务在close()中被阻塞,会准确地按时完全关闭。