如何正确关闭Java NIO中的SocketChannel?

我有一个简单的非阻塞服务器与主循环:

try {
    while (selector.select() > -1) {

        // Wait for an event one of the registered channels

        // Iterate over the set of keys for which events are available
        Iterator selectedKeys = selector.selectedKeys().iterator();
        while (selectedKeys.hasNext()) {
            SelectionKey key = (SelectionKey) selectedKeys.next();
            selectedKeys.remove();
            try {
                if (!key.isValid()) {
                    continue;
                }

                if (key.isConnectable()) {
                    connect(key);
                }

                // Check what event is available and deal with it
                if (key.isAcceptable()) {
                    accept(key);
                }

                if (key.isReadable()) {
                    read(key);
                }

                if (key.isWritable()) {
                    write(key);
                }
            } catch (Exception e) {
                e.printStackTrace();
                close(key);
            }
        }
    }
} catch (Exception e) {
    e.printStackTrace();
}

在读/写部分,我检查是否有东西要读/写 – 如果没有 – 然后我尝试关闭通道:

if (channel.read(attachment.buffer) < 1) 
    close(key);

关闭方法:

private void close(SelectionKey key) throws IOException {
    key.cancel();
    key.channel().close();
}

但是在处理这段代码的过程中,我在主循环中得到了异常(它被捕获但我认为有些错误)我得到了这个堆栈跟踪:

java.nio.channels.CancelledKeyException
    at sun.nio.ch.SelectionKeyImpl.ensureValid(Unknown Source)
    at sun.nio.ch.SelectionKeyImpl.readyOps(Unknown Source)
    at java.nio.channels.SelectionKey.isWritable(Unknown Source)

因此,当进入写入部分,关闭通道并在“可写”部分中返回到主循环时,它在主循环上失败,并且失败并出现此类异常.有什么建议?

解决方法:

错误很简单.

if (!key.isValid()) {
    continue;
}

if (key.isConnectable()) {
    connect(key);
}

// Check what event is available and deal with it
if (key.isAcceptable()) {
    accept(key);
}

if (key.isReadable()) {
    read(key);
}

if (key.isWritable()) {
    write(key);
}

您的read方法是取消SelectionKey的方法.但是,从读取返回后,您再次测试该通道是否可写的密钥 – 可能只需取消相同的密钥!您的初步检查在这里无济于事.

一种解决方案是检查密钥在可能被取消的任何地方是否有效:

...
if (key.isValid() && key.isWritable()) {
  write(key);
}
...

或者,您也可以尝试在任何特定频道上一次只注册一个兴趣,因此所有准备事件都是互斥的:

if (!key.isValid()) {
  continue;
}

if (key.isConnectable()) {
  connect(key);
} else if (key.isAcceptable()) {
  accept(key);
} else if (key.isReadable()) {
  read(key);
} else if (key.isWritable()) {
  write(key);
}

这在情况下可能是有益的;因为通常一个通道几乎总是可以写入就绪,保持对沿着读取就绪的写入准备感兴趣可能使Selector循环保持旋转,这很可能是不可取的.在大多数情况下,通常仅在底层套接字输出缓冲区已满时才注册写入就绪感兴趣.

作为附注,知道SocketChannel.read可以返回值< 1没有它是一个错误.

A read operation might not fill the buffer, and in fact it might not read any bytes at all. Whether or not it does so depends upon the nature and state of the channel. A socket channel in non-blocking mode, for example, cannot read any more bytes than are immediately available from the socket’s input buffer;

另外,Selector.select没有说明返回< -1表示它已关闭.

Returns: The number of keys, possibly zero, whose ready-operation sets were updated

上一篇:java – 缓冲区大小如何影响NIO通道性能?


下一篇:Mysql安装