Асинхронное закрытие канала в Java NIO - PullRequest
3 голосов
/ 09 мая 2009

Предположим, у меня простой Java-сервер на основе nio. Например (упрощенный код):

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 ]

1 голос
/ 05 июня 2009

TIME_WAIT означает, что ОС получила запрос на закрытие сокета, но ожидает возможной поздней связи со стороны клиента. Клиент, очевидно, не получил RST, так как все еще думает, что он УСТАНОВЛЕН. Это не вещи Java, это ОС. RST явно задерживается ОС - по любой причине.

Почему это происходит только тогда, когда вы закрываете его в другом потоке - кто знает? Может быть, ОС считает, что закрытие в другом потоке должно ждать выхода из исходного потока, или что-то еще. Как я уже сказал, это внутренняя механика ОС.

0 голосов
/ 12 июня 2009

У вас есть серьезная проблема в вашем примере.

В Java NIO поток, выполняющий accept () , должен выполнять только accept () . Если не считать примеров, вы, вероятно, используете Java NIO из-за ожидаемого большого количества соединений. Если вы даже думаете о выполнении чтения в том же потоке, что и выборки, ожидающие неприемлемые выборки будут иметь время ожидания ожидания установления соединения. К тому моменту, когда этот один перегруженный поток доберется до принятия соединения, ОС с обеих сторон перестанут работать, и accept () завершится ошибкой.

Делайте только абсолютный минимум в цепочке выбора. Больше, и вы будете просто переписывать код, пока не сделаете только минимум.

[В ответ на комментарий]

Только в примерах игрушек чтение должно обрабатываться в главном потоке.

Попробуйте обработать:

  • 300 + одновременных попыток подключения.
  • Каждое установленное соединение отправляет 24 Кбайт на один сервер - то есть небольшую веб-страницу, крошечный .jpg.
  • Немного замедлите каждое соединение (соединение устанавливается по коммутируемому соединению, или в сети высокая частота ошибок / повторных попыток) - так что ACK TCP / IP занимает больше времени, чем идеально (вне уровня управления ОС )
  • У вас есть несколько тестовых соединений, отправляйте один байт каждые 1 миллисекунду. (это моделирует клиента, который имеет свое собственное состояние высокой нагрузки, поэтому генерирует данные с очень медленной скоростью.) Поток должен тратить почти столько же усилий на обработку одного байта, сколько и 24 КБ.
  • Некоторые соединения должны быть разорваны без предупреждения (проблемы с потерей соединения).

На практике соединение должно быть установлено в течение 500-1500 мс, прежде чем попытка машины прервет соединение.

В результате всех этих проблем один поток не сможет установить все соединения достаточно быстро, пока машина на другом конце не откажется от попытки подключения. Чтения должны быть в другом потоке. период.

[Ключевой пункт] Я забыл, чтобы действительно быть ясно об этом. Но у потоков, делающих чтение, будет свой собственный Селектор. Селектор, используемый для установления соединения, не должен использоваться для прослушивания новых данных.

Добавление (в ответ на утверждение Гнарли о том, что во время вызова Java для чтения потока фактически не происходит ввода-вывода.

Каждый слой имеет определенный размер буфера. Как только этот буфер заполнен, IO останавливается. Например, буферы TCP / IP имеют от 8К до 64К буферов на соединение. Как только буфер TCP / IP заполняется, принимающий компьютер сообщает отправляющему компьютеру остановиться. Если принимающий компьютер не обрабатывает буферизованные байты достаточно быстро, отправляющий компьютер прервет соединение.

Если принимающий компьютер обрабатывает буферизованные байты, отправитель продолжит потоковую передачу байтов, , пока выполняется вызов чтения java io .

Кроме того, осознайте, что первый поступающий байт вызывает «байты, доступные для чтения» на селекторе. Там нет никаких гарантий относительно того, сколько прибыло.

Размеры буфера, определенные в коде Java, не имеют отношения к размеру буфера ОС.

0 голосов
/ 21 мая 2009

Знаете, после более тщательного тестирования я не могу воспроизвести результаты на моем Mac.

Хотя верно, что соединение остается в TIME_WAIT в течение примерно 1 минуты после закрытия на стороне сервера, оно немедленно закрывается на стороне клиента (когда я подключаюсь к нему с помощью клиента telnet для проверки).

Это то же самое, независимо от того, в каком потоке я закрываю канал. На какой машине вы работаете и какая версия java?

0 голосов
/ 22 мая 2009

Возможно, это связано с проблемой, упомянутой здесь . Если это действительно поведение метода poll () BSD / OS X, то я думаю, что вам не повезло.

Думаю, я бы пометил этот код как непереносимый из-за - насколько я понимаю - ошибки в BSD / OS X.

0 голосов
/ 09 мая 2009

Я не понимаю, почему это будет иметь значение, если закрытие не вызывает исключение. Если бы это было так, вы бы не увидели исключения. Я предлагаю поместить закрытие в улов (Throwable t) и распечатать исключение (при условии, что оно есть)

...