假设我们有一个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
非常频繁地触发,并且每次都会遍历所有键集。
那么有更好(更有效)的方法来解决这个问题吗?
答案 0 :(得分:1)
您可以调整天真的解决方案:
答案 1 :(得分:0)
使用Java.util.Timer,并提交一个在闲置期后关闭通道的TimerTask。如果任务存在则取消该任务,并在运行之前获得一次通道活动时提交新任务。将TimerTasks保存在某个地方,您可以通过频道(例如,存储为密钥附件的会话对象)找到它们,这也可以保存频道的ByteBuffer,用户ID等,无论您在每个会话中需要什么。
你会遇到并发问题,这需要你 唤醒计时器任务中的选择器,并让选择器线程正确处理当没有任何准备就绪时被唤醒(select()返回由于在select()中选择器阻塞时close(()中的计时器任务被阻塞,因此,由于计时器任务在close()中被阻塞,或会准确地按时完全关闭。