Должен ли я закрыть розетки с обоих концов? - PullRequest
5 голосов
/ 16 марта 2010

У меня следующая проблема. Моя клиентская программа следит за наличием сервера в локальной сети (используя Bonjour, но это не помогает). Как только сервер «замечен» клиентским приложением, клиент пытается создать сокет: Socket(serverIP,serverPort);.

В какой-то момент клиент может потерять сервер (Bonjour говорит, что сервер больше не виден в сети). Итак, клиент решает close сокет, потому что он больше не действителен.

В какой-то момент сервер снова появляется. Итак, клиент пытается создать новый сокет, связанный с этим сервером. Но! Сервер может отказаться от создания этого сокета, так как он (сервер) уже имеет сокет, связанный с IP-адресом клиента и клиентским портом. Это происходит потому, что сокет был закрыт клиентом, а не сервером. Это может случиться? И если это так, то как решить эту проблему?

Ну, я понимаю, что маловероятно, что клиент попытается подключиться к серверу с того же порта (клиентского порта), поскольку клиент выбирает свои порты случайным образом. Но это все еще может случиться (просто случайно). Правильно?

Ответы [ 6 ]

2 голосов
/ 16 марта 2010

Быстрая / грязная иллюстрация того, почему это не может произойти (обратите внимание, что клиент принудительно использует тот же локальный порт в своем соединении):

public class Server{

public static void main(String[] args) throws Exception {
    new Thread(){
        java.net.ServerSocket server = new java.net.ServerSocket(12345);
        java.util.ArrayList<java.net.Socket> l = new java.util.ArrayList<java.net.Socket>();
        public void run() {
            try{
            while(true){
                 java.net.Socket client = server.accept();
                System.out.println("Connection Accepted: S: "+client.getLocalPort()+", C: "+client.getPort());
                l.add(client);
            }
            }catch(Exception e){e.printStackTrace();}
        }
    }.start();
}

и клиент (замените адрес сервера на что-то действительное):

import java.net.InetAddress;
import java.net.Socket;


public class SocketTest {
    public static void main(String[] args) throws Exception {
        InetAddress server = InetAddress.getByName("192.168.0.256");
        InetAddress localhost = InetAddress.getLocalHost();
        Socket s = new Socket(server, 12345, localhost, 54321);
        System.out.println("Client created socket");
        s.close();
        s = null;
        System.gc();
        System.gc();
        Thread.sleep(1000);
        s = new Socket(server, 12345, localhost, 54321);
        System.out.println("Client created second socket");
        s.close();
        System.exit(55);
    }
}

Если вы запустите сервер, а затем попытаетесь запустить клиент, первое соединение будет установлено успешно, но второе завершится неудачно с «java.net.BindException: адрес уже используется: connect»

2 голосов
/ 16 марта 2010

Да, закройте сокет, как только вы обнаружите сбой.

Сокет будет "застревать" в "close_wait", если не будет закрыт должным образом. Даже если сокет закрыт, его состояние будет в time_wait на короткий период.

Однако, если вы разрабатываете приложение для использования нового локального порта для каждого нового соединения, нет необходимости ждать закрытия старого сокета.

(Так как вы создаете совершенно другой сокет, поскольку сокет идентифицируется по удаленному ip, удаленному порту, локальному ip и локальному порту.)

1 голос
/ 16 марта 2010

Похоже, ваш клиент обнаружил потерю соединения с сервером (используя Bonjour), но у вас нет соответствующей возможности в другом направлении.

Вам наверняка понадобится какой-то тайм-аут для неактивных соединений на стороне сервера, в противном случае мертвые соединения будут зависать вечно. Помимо проблемы потенциальных конфликтов IP-адресов / портов #, о которых вы упоминаете, есть еще и тот факт, что мертвые соединения потребляют ресурсы ОС и приложений (например, дескрипторы открытых файлов)

И наоборот, вы также можете подумать о том, чтобы не быть слишком агрессивным при закрытии соединения со стороны клиента, когда Bonjour говорит, что сервис больше не виден. Если вы находитесь в беспроводном сценарии, временная потеря подключения не является чем-то необычным, и возможно, что подключение TCP остается открытым и действительным после восстановления подключения (при условии, что клиент все еще имеет тот же IP-адрес). Оптимальная стратегия зависит от того, о какой связи вы говорите. Если это соединение без сохранения состояния, где стоимость отбрасывания соединения и повторных попыток невелика (например, HTTP), то имеет смысл сбросить соединение при первых признаках проблемы. Но если это долгоживущее соединение со значительным состоянием пользователя (например, сеанс входа в систему SSH), имеет смысл постараться сохранить соединение живым.

1 голос
/ 16 марта 2010

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

Я бы об этом не беспокоился.

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

1 голос
/ 16 марта 2010

Краткий ответ: да, вы должны закрыть сокет на обоих концах.

Несмотря на то, что ответ прост, в действительности может быть очень трудно обнаружить, что узел прекратил отвечать, если вы не встроите какую-либо схему ACK / NACK в свой протокол клиент-сервер.

Даже с ACK вашего протокола ваш поток обработки может зависать в ожидании ACK, которые никогда не поступят от клиента или наоборот.

Если вы используете блокировку ввода / вывода, я бы начал с установки таймаутов чтения в сокете. К сожалению, если одноранговый узел перестает отвечать на запросы, соответствующего времени ожидания для записи не существует. Один тупой инструмент, который я нашел, имеет значение в нашей среде - это создание блокирующих сокетов с помощью методов java.nio, а затем прерывание потока обработки через настраиваемые интервалы.

Прерывание потока обработки закроет сокет, но если вы выберете достаточно большое время ожидания, вы будете знать, что возникла проблема. Мы выбрали этот подход, потому что приложение изначально было написано с блокирующим вводом-выводом, а стоимость перехода на неблокирующую систему была очень высокой.

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

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

0 голосов
/ 16 апреля 2010

Если вы закрываете сокет сервера только в случае блокировки сокета, сокет клиента будет закрыт, но не наоборот.

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

Спасибо Сунил Кумар Саху

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...