Java NIO中的异步通道关闭

时间:2009-05-09 13:11:23

标签: java asynchronous nio

假设我有简单的基于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。所以连接没有优雅地关闭。有什么想法有什么不对吗?我错过了什么?

5 个答案:

答案 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()将会失败。

仅在选择线程中执行绝对最小值。再做一遍,你只需要重写代码,直到你只做最小的。

[回应评论]

只有在玩具示例中才能在主线程上处理读数。

尝试处理:

  • 同时连接300次以上。
  • 每个连接一旦建立就会向一台服务器发送24K字节 - 即一个小网页,一个小小的.jpg。
  • 稍微降低每个连接的速度(通过拨号建立连接,或者网络具有高错误/重试率) - 因此TCP / IP ACK需要比理想时间更长的时间(超出控制操作系统级别的内容) )
  • 拥有一些测试连接,每1毫秒发送一个字节。 (这模拟了一个具有自己的高负载条件的客户端,因此以非常慢的速率生成数据。)线程必须花费几乎相同的工作量来处理单个字节,因为它需要24K字节。
  • 在没有警告的情况下切断一些连接(连接丢失问题)。

实际上,需要在尝试机器断开连接之前的500ms-1500ms内建立连接。

由于所有这些问题,在另一端的机器放弃连接尝试之前,单个线程将无法足够快地建立所有连接。读取必须在不同的线程中。周期。

[要点] 我忘记了这一点。但是执行读取的线程将拥有自己的Selector。用于建立连接的Selector不应用于侦听新数据。

添加(回应Gnarly的论点,即在java调用期间实际上没有发生I / O来读取流。

每个图层都有一个已定义的缓冲区大小。一旦缓冲区已满,IO就会暂停。例如,TCP / IP缓冲区每个连接有8K-64K缓冲区。一旦TCP / IP缓冲区填满,接收计算机就会告诉发送计算机停止。如果接收计算机没有足够快地处理缓冲的字节,则发送计算机将断开连接。

如果接收计算机正在处理缓冲的字节,发送方将继续流式传输字节,同时正在进行java io读取调用

此外,要意识到要到达的第一个字节触发选择器上的“可读取的字节”。无法保证到达了多少人。

java代码中定义的缓冲区大小与OS的缓冲区大小无关。