3

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

4

5 回答 5

1

TIME_WAIT 表示操作系统已收到关闭套接字的请求,但等待来自客户端的可能延迟通信。客户显然没有得到 RST,因为它仍然认为它已经建立。这不是 Java 的东西,它是操作系统。RST 显然被操作系统延迟了——无论出于何种原因。

为什么它只发生在你在另一个线程中关闭它时——谁知道呢?可能是操作系统认为在另一个线程中关闭应该等待原始线程退出,或者什么。正如我所说,它是操作系统的内部机制。

于 2009-06-05T16:53:39.857 回答
0

我不明白为什么它会有所作为,除非关闭抛出异常。如果是,您将不会看到异常。我建议将关闭放在一个 catch(Throwable t) 中并打印出异常(假设有一个)

于 2009-05-09T14:11:30.607 回答
0

这可能与这里提到的问题有关。如果它真的BSD / OS X poll() 方法的行为,我认为你不走运。

我想我会将这段代码标记为不可移植,因为 - 据我所知 - BSD / OS X 中的一个错误。

于 2009-05-22T12:01:07.320 回答
0

You have a major problem in your example.

With Java NIO, the thread doing the accept() must only be doing the accept(). Toy examples aside you are probably using Java NIO because of anticipated high number of connections. If you even think about doing the read in the same thread as the selects, the pending unaccepted selects will time out waiting for the connection to be established. By the time this one overwrought thread gets around to accepting the connection, the OS's on either side will have given up and the accept() will fail.

Only do the absolute minimum in the selection thread. Any more and you will just being rewriting the code until you do only the minimum.

[In response to comment]

Only in toy examples should the reading be handled on the main thread.

Try to handle:

  • 300+ simultaneous connection attempts.
  • Each connection once established sends 24K bytes to a single server - i.e. a small web page, a tiny .jpg.
  • Slow down each connection slightly ( the connection is being established over a dialup, or the network is having a high-error/retry rate) - so the TCP/IP ACK takes longer than ideal (out of your control OS level thing)
  • Have some of your test connections, send a single bytes every 1 milliseconds. (this simulates a client that is having its own high load condition, so is generating the data at a very slow rate.) The thread has to spend almost the same amount of effort processing a single bytes as it does 24K bytes.
  • Have some connections be cut with no warning ( connection lost issues ).

As a practical matter, the connection needs to be established within 500ms -1500ms before the attempting machine drops the connection.

As a result of all these issues, a single thread will not be able to get all the connections set up fast enough before the machine on the other end gives up the connection attempt. The reads must be in a different thread. period.

[Key Point] I forgot to really be clear about this. But the threads doing the reading will have their own Selector. The Selector used to establish the connection should not be used to listen for new data.

Addition (in response to Gnarly's contention that no I/O actually occurs during the java call to read the stream.

Each layer has a defined buffer size. Once that buffer is full, the IO is halted. For example, TCP/IP buffers have between 8K-64K buffers per connection. Once the TCP/IP buffer fills, the receiving computer tells the sending computer to stop. If receiving computer does not process the buffered bytes fast enough the sending computer will drop the connection.

If the receiving computer is processing the buffered bytes, the sender will continue to stream the bytes, while the java io read call is being made.

Furthermore, realize that the first byte to arrive triggers the "bytes available to be read" on the selector. There is no guarantee as to how many have arrived.

The buffer sizes defined in the java code have no relationship to the buffer size of the OS.

于 2009-06-12T08:44:44.170 回答
0

你知道,经过更仔细的测试,我无法在我的 Mac 上重现你的结果。

虽然在服务器端关闭后连接确实会在 TIME_WAIT 中保持大约 1 分钟,但它会在客户端立即关闭(当我使用 telnet 客户端连接到它进行测试时)。

无论我在哪个线程上关闭通道,这都是一样的。你在什么机器上运行,什么版本的java?

于 2009-05-21T07:19:33.640 回答