假设我有简单的基于nio的java服务器。例如(简化代码):
while (!self.isInterrupted()) {
if (selector.select() <= 0) {
continue;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
SelectableChannel channel = key.channel();
if (key.isValid() && key.isAcceptable()) {
SocketChannel client = ((ServerSocketChannel) channel).accept();
if (client != null) {
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_READ);
}
} else if (key.isValid() && key.isReadable()) {
channel.read(buffer);
channel.close();
}
}
}
所以,这是一个简单的单线程非阻塞服务器。
问题在于以下代码。
channel.read(buffer);
channel.close();
当我在同一个线程(接受连接和读取数据的线程)中关闭通道时,一切正常。但是当连接在另一个线程中关闭时,我遇到了问题。例如
((SocketChannel) channel).read(buffer);
executor.execute(new Runnable() {
public void run() {
channel.close();
}
});
在这种情况下,我最终在服务器上的状态为TIME_WAIT,在客户端上为ESTABLISHED。所以连接没有优雅地关闭。有什么想法有什么不对吗?我错过了什么?
答案 0 :(得分:1)
TIME_WAIT表示操作系统已收到关闭套接字的请求,但等待来自客户端的可能的后期通信。客户显然没有得到RST,因为它仍然认为它是ESTABLISHED。它不是Java的东西,它是操作系统。无论出于何种原因,RST显然被操作系统推迟了。
为什么只有当你在另一个线程中关闭它时才会发生 - 谁知道?可能是OS认为在另一个线程中关闭应该等待原始线程退出,或者其他什么。正如我所说,它是操作系统的内部机制。
答案 1 :(得分:0)
我不明白为什么除非近距离抛出异常,否则会产生影响。如果是,你就不会看到异常。我建议将关闭放在一个catch(Throwable t)并打印出异常(假设有一个)
答案 2 :(得分:0)
你知道,经过一些仔细测试后,我无法在Mac上重现你的结果。
虽然在服务器端关闭后大约1分钟内连接仍然在TIME_WAIT中,但它在客户端立即关闭(当我使用telnet客户端连接到它进行测试时)。
无论我关闭通道的哪个线程都是如此。你在运行什么机器以及什么版本的java?
答案 3 :(得分:0)
这可能与the problem mentioned here有关。如果确实是 BSD / OS X poll()方法的行为,我认为你运气不好。
我认为我会将此代码标记为不可移植,因为 - 据我所知 - BSD / OS X中的错误。
答案 4 :(得分:0)
您的示例中存在一个主要问题。
使用Java NIO,执行 accept() 的线程必须才能执行 accept()。除了玩具示例之外,您可能正在使用Java NIO,因为预期会有大量连接。如果你甚至认为关于在与选择相同的线程中进行读取,则挂起的未接受的选择将超时等待建立连接。当这个过度的线程绕过接受连接时,任何一方的操作系统都会放弃,而accept()将会失败。
仅在选择线程中执行绝对最小值。再做一遍,你只需要重写代码,直到你只做最小的。
[回应评论]
只有在玩具示例中才能在主线程上处理读数。
尝试处理:
实际上,需要在尝试机器断开连接之前的500ms-1500ms内建立连接。
由于所有这些问题,在另一端的机器放弃连接尝试之前,单个线程将无法足够快地建立所有连接。读取必须在不同的线程中。周期。
[要点] 我忘记了这一点。但是执行读取的线程将拥有自己的Selector。用于建立连接的Selector不应用于侦听新数据。
添加(回应Gnarly的论点,即在java调用期间实际上没有发生I / O来读取流。
每个图层都有一个已定义的缓冲区大小。一旦缓冲区已满,IO就会暂停。例如,TCP / IP缓冲区每个连接有8K-64K缓冲区。一旦TCP / IP缓冲区填满,接收计算机就会告诉发送计算机停止。如果接收计算机没有足够快地处理缓冲的字节,则发送计算机将断开连接。
如果接收计算机正在处理缓冲的字节,发送方将继续流式传输字节,同时正在进行java io读取调用。
此外,要意识到要到达的第一个字节触发选择器上的“可读取的字节”。无法保证到达了多少人。
java代码中定义的缓冲区大小与OS的缓冲区大小无关。