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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11854382/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 06:40:25  来源:igfitidea点击:

How correctly close SocketChannel in Java NIO?

javaniosocketchannel

提问by sphinks

I have a simple non-blocking server with main loop:

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

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();
}

In read/write section I check if there is something to read/write if not - then I try to close channel:

在读/写部分,我检查是否有要读/写的内容,如果没有 - 然后我尝试关闭通道:

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

Close method:

关闭方法:

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

But during processing this code I get exception in main loop (it is catched but I supposed something wrong) I get this stacktrace:

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

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)

So it fails on main loop when enter write section, close channel and came back to main loop in 'writable' if section and fails with such exception. Any suggestions?

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

回答by oldrinb

The error is very simple.

错误很简单。

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);
}

Your readmethod is the one which cancels the SelectionKey. However, after returning from read, you again test the key for whether the channel is writable -- potentially after just cancelling that very same key! Your initial check cannot help here.

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



One solution would be to check for whether the key is valid wherever it might've just been cancelled:

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

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


Alternatively, you could also try only registering one interest at a time as you need to on any particular channel, and thus all readiness events are mutually exclusive:

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

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);
}

This might be beneficial in situations; as generally a channel will almost always be write-ready, keeping an interest in write-readiness along side read-readiness might keep the Selectorloop spinning, which is more than likely notdesirable. For the most part, generally register interest in write-readiness only when the underlying socket output buffer is full.

这在某些情况下可能是有益的;作为一般的信道几乎总是写就绪,保持沿侧读准备在写就绪的兴趣可能保持Selector环纺纱,这是更可能理想。在大多数情况下,通常只有在底层套接字输出缓冲区已满时才对写入准备感兴趣。



As a side note, know that SocketChannel.readcan return a value < 1without it being an error.

作为旁注,知道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;

读取操作可能不会填满缓冲区,实际上它可能根本不会读取任何字节。是否这样做取决于通道的性质和状态。例如,处于非阻塞模式的套接字通道不能读取比套接字输入缓冲区立即可用的字节多的字节数;

Additionally, Selector.selectdoes not state anything about returning < -1to indicate it is closed.

此外,Selector.select没有说明有关返回的任何内容< -1以表明它已关闭。

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

返回:已更新就绪操作集的键数,可能为零