2009-05-09 3 views
3

Предположим, у меня есть простой java-сервер на основе nio. Например (упрощенный код):Закрытие асинхронного канала в 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. Таким образом, соединение не закрывается грациозно. Какие-нибудь идеи, что не так? Что я пропустил?

+1

Что такое ОС хоста? Это может быть функция реализации/ошибка, поскольку спецификации пакета утверждают, что это все поточно-безопасные конструкции. – alphazero

+0

Хост-компьютер под Mac OS X 10.5.6. Сегодня я повторяю тесты под Windows 7, и все работает отлично. Похоже, вы правы. –

+0

Что произойдет, если вы закроете channel.socket() перед закрытием канала? – Nuoji

ответ

0

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

+0

Я пробовал это. Все ключевые моменты моего кода, завернутые в try-catch с исключением журнала. На данный момент никаких исключений не возникает. Перед закрытием канала он активен, после закрытия он находится в состоянии ЗАКРЫТО (но сокет в состоянии TIME_WAIT). –

0

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

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

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

0

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

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

1

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

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

0

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

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

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

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

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

Попробуйте справиться:

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

Как правило, соединение должно устанавливаться в пределах 500 мс -1500 мс до того, как попытка машины отключит соединение.

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

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

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

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

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

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

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

+0

Насколько я помню, основной поток должен читать данные из сокета. Это чтение не блокирует (данные уже готовы для чтения). Если вы делегируете чтение другому потоку, вы оказываетесь в ситуации, когда селекторный поток будет пробуждаться несколько раз в одном и том же событии. Это происходит из-за параллельного потока, вероятно, не успеет закончить чтение, прежде чем селекторный поток сделает следующий выбор. –

+0

Добавлен «Ключевой пункт» в конце. – Pat

+0

Я не согласен. Если сервер настроен в неблокирующем асинхронном режиме, вам не нужно будет нить выполнять операции ввода-вывода, так как это ** неблокирование **, что означает, что вызов самого метода делает не что иное, как простое вызов ОС. Он фактически не выполняет операцию ввода-вывода в потоке, который вы вызывали. Кроме того, несколько 'Selector' - самая бессмысленная идея. «Селектор» может работать так же быстро, как позволяет ОС. Добавление в мультипликаторы только замедлит выбор готовности другого 'Selector' **, если что-нибудь **. – 2011-03-22 02:41:07

Смежные вопросы