Обнаружение разъединения разъемов?
Я расстроен тем, что это невозможно обработать элегантным способом, попробовав разные решения (this, this и несколько других), упомянутых в ответах на несколько вопросов SO, я до сих пор не смог обнаружить разъединение разъемов (отсоединив кабель).
Я использую неблокирующий сокет NIO, все работает отлично, за исключением того, что я не нашел способа обнаружить отключение сервера.
У меня есть следующий код:
while (true) {
handlePendingChanges();
int selectedNum = selector.select(3000);
if (selectedNum > 0) {
SelectionKey key = null;
try {
Iterator<SelectionKey> keyIterator = selector.selelctedKeys().iterator();
while (keyIterator.hasNext()) {
key = keyIterator.next();
if (!key.isValid())
continue;
System.out.println("key state: " + key.isReadable() + ", " + key.isWritable());
if (key.isConnectable()) {
finishConnection(key);
} else if (key.isReadable()) {
onRead(key);
} else if (key.isWritable()) {
onWrite(key);
}
}
} catch (Exception e) {
e.printStackTrace();
System.err.println("I am happy that I can catch some errors.");
} finally {
selector.selectedKeys().clear();
}
}
}
Пока читаются SocketChannels, я отсоединяю кабель, а Selector.select()
начинает вращаться и возвращается 0, теперь у меня нет возможности читать или писать каналы, потому что основной код чтения и записи защищен if (selectedNum > 0)
, теперь это первая путаница, исходящая из моей головы, от этого ответа, говорится, что когда канал будет разбит, select() вернется, а клавиша выбора канала будет отображаться как читаемая/записываемая, но, по-видимому, это не так, ключи не выбраны, select()
все еще возвращает 0.
Кроме того, из ответа EJP на аналогичный вопрос:
Если сверстник закрывает сокет:
- read() возвращает -1
- readLine() возвращает null
- readXXX() выдает исключение EOFException для любого другого X.
В этом случае я не пробовал комментировать if (selectedNum > 0)
и использовать selector.keys().iterator()
, чтобы получить все ключи независимо от того, выбраны они или нет, чтение из этих ключей не возвращает -1 (вместо этого возвращено 0) и запись на эти клавиши не получает EOFException
. Я заметил только одно: даже клавиши не выбраны, key.isReadable()
возвращает true, а key.isWritable()
возвращает false (думаю, это может быть потому, что я не зарегистрировал ключи для OP_WRITE).
Мой вопрос: почему Java-сокет ведет себя так или есть что-то, что я сделал неправильно?
Ответы
Ответ 1
Вы обнаружили, что вам нужны таймеры и сердцебиение по TCP-соединению.
Если вы отключите сетевой кабель, TCP-соединение не может быть нарушено. Если вам нечего послать, стек TCP/IP нечего отправлять, он не знает, что кабель куда-то ушел, или что одноранговый ПК внезапно вспыхнул. Это TCP-соединение можно считать открытым до перезагрузки сервера через несколько лет.
Подумайте об этом так; как TCP-соединение знает, что другой конец отключен от сети - он отключен от сети, поэтому он не может сказать вам этого факта.
Некоторые системы могут обнаружить это, если вы отсоедините кабель, идущий на ваш сервер, а некоторые - нет. Если вы отсоедините кабель на другом конце, например, сетевой коммутатор, который не будет обнаружен.
Для чего всегда нужны таймеры супервизора (например, отправляйте одноранговое сообщение одноранговому узлу или закрывайте TCP-соединение, основанное на отсутствии активности за определенный промежуток времени) для TCP-соединения,
Один очень дешевый способ, по крайней мере, избегать TCP-соединений, которые вы только читали, никогда не записывать, чтобы не ложиться спать много лет подряд, заключается в том, чтобы включить TCP keepalive в сокет TCP - имейте в виду, что тайм-ауты по умолчанию для TCP keepalive часто занимает 2 часа.
Ответ 2
Эти ответы не применяются. Первый относится к случаю, когда соединение нарушено, а второе (мое) относится к случаю, когда партнер завершает соединение.
В TCP-соединении, если данные не отправляются или не принимаются, в принципе нет ничего о том, чтобы потянуть кабель, который должен разорвать соединение, поскольку TCP намеренно разработан таким образом, чтобы быть надежным в этом отношении, и, безусловно, нет ничего об этом, который должен смотреть на локальное приложение, подобное закрытию сверстников.
Единственный способ обнаружить сломанное соединение в TCP - попытка отправить данные через него или интерпретировать тайм-аут чтения как потерянное соединение после подходящего интервала, который является решением приложения.
Вы также можете настроить TCP keep-alive для включения обнаружения неработающих подключений, а в некоторых системах вы можете даже контролировать таймаут для каждого сокета. Однако не через Java, поэтому вы застряли с системным значением по умолчанию, которое должно составлять два часа, если оно не было изменено.
Ваш код должен вызывать keyIterator.remove() после вызова keyIterator.next().