1

假设我们有一个 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)方法的原因。

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

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

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

这可能非常低效,因为活动通道会导致select非常频繁地触发并且每次都在整个键集上迭代。

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

4

2 回答 2

1

您可以调整您的幼稚解决方案:

  • 选择后更新就绪键的时间
  • 如果特定时间间隔已经过去,则仅迭代所有其他键
    • 保留一个全局变量threshold和一个变量last_check_time , 每次密钥准备好时计算当前时间,如果(last_check_time - current time) > threshold,将last_check_time设置为当前时间并迭代密钥。
    • 或者使用单个 timertask 启动迭代检查
于 2014-11-18T14:29:15.877 回答
0

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

您将遇到并发问题,这将需要您在计时器任务唤醒选择器,并让选择器线程正确处理在没有准备好时被唤醒(select() 返回零),或者忍受通道不存在由于计时器任务在 close() 中阻塞,而选择器在 select() 中阻塞,因此准时关闭。

于 2013-10-08T23:17:22.490 回答