Подтвердить что ты не робот

Обнаружение разъединения разъемов?

Я расстроен тем, что это невозможно обработать элегантным способом, попробовав разные решения (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-сокет ведет себя так или есть что-то, что я сделал неправильно?

4b9b3361

Ответ 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().