为什么在通过密钥时会得到CancelledKeyException?

时间:2011-06-02 13:20:08

标签: java exception networking nio

为什么我每天都会CancelledKeyException几次?我该怎么办呢?我的代码错了吗?

        Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
        while (keys.hasNext()) {

            SelectionKey key = (SelectionKey) keys.next();
            keys.remove();

            try {
                if (key.isValid()) {
                    if (key.isReadable()) {
                        readHandler.handle((Connection) key.attachment());
                    }
                    if (key.isWritable()) {
                        writeHandler.handle((Connection) key.attachment());
                    }
                    if (key.isAcceptable()) {
                        acceptHandler.handle(key);
                    }
                }
            } catch (CancelledKeyException e) {
                _logger.error("CanceledKeyException in while loop:", e);
            }
        }

例外:

java.nio.channels.CancelledKeyException: null
    at sun.nio.ch.SelectionKeyImpl.ensureValid(SelectionKeyImpl.java:55) ~[na:1.6.0_12]
    at sun.nio.ch.SelectionKeyImpl.readyOps(SelectionKeyImpl.java:69) ~[na:1.6.0_12]
    at java.nio.channels.SelectionKey.isWritable(SelectionKey.java:294) ~[na:1.6.0_12]
    at project.engine.io.SimpleReactor.work(SimpleReactor.java:194) ~[engine-02.06.11.jar:na]
    at project.server.work.AbstractWorker$1.run(AbstractWorker.java:20) [server-21.05.11.jar:na]
    at java.lang.Thread.run(Thread.java:619) [na:1.6.0_12]

3 个答案:

答案 0 :(得分:7)

其中一个处理程序可能会关闭该频道。例如,读取处理程序关闭通道,如果它读取-1。因此写处理程序将失败。确实isWritable()会失败,因为我现在可以从堆栈跟踪中看到。因此,您必须在每个其他条件下测试isValid(),例如isValid() && isReadable(), isValid() && isWritable(),等。

答案 1 :(得分:0)

EJP的解决方案似乎不是理想的...我认为处理在选择器循环中间取消的选择键的正确方法是用try / catch包围它并检查CancelledKeyException,如:

try {
   if (key.isReadable())
      processMessage(key);

   if (key.isAcceptable())
      acceptConnection(key);
}
catch (CancelledKeyException e) {
   logger.debug("Key cancelled... Closing channel.");
   key.channel().close();
}

答案 2 :(得分:0)

在我的情况下,执行取消的线程与注册通道进行选择的线程不同。看起来这个异常发生在首先取消密钥时,另一个线程再次注册密钥而中间没有选择。所以我只是在再次注册频道进行选择之前做一个虚拟选择来解决它。似乎有些簿记是在选择中完成的,这对于注册行动是成功的是必要的。 两个线程在共享静态对象上使用同步块(总共3个)来分别锁定注册,取消和选择操作。