#java #сокет
Клиент какое-то время работает с сервером на сокетах. Клиент принимает решение завершить работу(например, аварийное завершение повисшего приложения). Как узнать со стороны сервера, что сокет больше не валидный и можно его отбросить? socket.isClosed() на стороне сервера всегда возвращает false, даже если клиент уже вызвал socket.close() ?
Ответы
Ответ 1
Метод isConnected поможет решить проблему. Возвращает TRUE, если клиент закрыл соединение. public class MyServer { public static final int PORT = 12345; public static void main(String[] args) throws IOException, InterruptedException { ServerSocket ss = ServerSocketFactory.getDefault().createServerSocket(PORT); Socket s = ss.accept(); Thread.sleep(5000); ss.close(); s.close(); } } public class MyClient { public static void main(String[] args) throws IOException, InterruptedException { Socket s = SocketFactory.getDefault().createSocket("localhost", MyServer.PORT); System.out.println(" connected: " + s.isConnected()); Thread.sleep(10000); int read = s.getInputStream().read(); if(read == -1) System.out.println("Socket disconnected"); else System.out.println(read); } } ps. если не ошибаюсь, если клиент отключился, то Input/OutputStream-ы вернут IOException UPD isConnected() -правда, если сокет был успешно подключен к серверу. Закрытие сокета не очищает состояние соединения, что означает этот метод возвращает истину для закрытого сокета (см IsClosed ()), если он был успешно подключен еще до закрытия. isClosed() Возвращает закрытое состояние сокета. т.е. говорит вам закрыли ли вы этот сокет. Пока у вас есть, он возвращает ложь. isConnected() - если соединение закрыто, то read() вернет -1, если нет данных с потока, например, недоступен или дошли до конца потока. Этот метод блокирует поток до тех пор, пока не будет прочитан весь поток, либо не будет достигнуn конц потока, либо выбросит исключение. Замечание от @Regent: Если не указать timeout, то все зависнет на неизвестно сколько времени. readLine() вернет null тогда нужно проверить состояние след. образом: int read = s.getInputStream().read(); if(read == -1) System.out.println("disconnected"); else System.out.println(read); p.p.s isConnected() не скажет отвалился ли клиент.Ответ 2
Есть только один надежный метод определить, подключен клиент или нет - это что то ему отправить и получить ответ. Если в соединение никто не пишет, то оно может висеть долго. Да, в TCP есть KeepAlive, который как раз это и делает, но он настроен на очень большие интервалы (от нескольких часов). Поэтому, если Вам нужно гарантированно определять, что пользователь уже отвалился, вводите так называемые "пинги". Они могут быть как серверные, так и клиентские (то есть, кто инициатор). Одна сторона гарантирует, что раз в минуту (час/секунду) отправляет пинг запрос. Вторая должна максимально быстро на него ответить понгом (это условные названия. На уровне протокола это может быть байты FF и FE к примеру). Если к моменту отправки следующего пинга не пришел понг - значит вторая сторона либо слегла, либо может быть и рабочей, но не успевает (да, если пинги шлете раз в секунду, а сервер и клиент на разных точках земного шара и все это работает через 56к модем, то это плохое решение:) Выбирайте периодичность разумно. Я думаю, минуты обычно будет предостаточно). После того, как это все реализуете, даже isConnected в примере от @Senior Automator заработает как нужно. Но, протестировав решение @Senior Automator, Вы можете заявить, что оно работает и так. И да, оно скорее всего у Вас будет работать - так как тестить скорее всего будете на localhost. Даже в обычной локалке оно будет работать обычно хорошо. Но достаточно выйти за пределы обычной локалки - все... Пьяный тракторист переедет кабель, его спаяют назад и если в этот момент Ваш клиент-сервер ничего не слали - они могут и не узнать и продолжать работать:)Ответ 3
Простой проверкой свойств эта проблема не решается. Дело в том, что в пассивном состоянии протокол TCP ничего не передает - а потому нет никакого способа узнать за разумное время, держит ли клиент еще соединение или с ним что-то случилось. Определить состояние клиента можно только у активного соединения - и тут есть два подхода. Подход первый - пинги. Смысл данного подхода - в том, чтобы позволить драйверу TCP диагностировать разрыв связи. Драйвер TCP, в свою очередь, может это сделать при выполнении одного из трех условий: Пришел пакет RST Пришел пакет ICMP с сообщением об ошибке (лично я не знаю, используются ли эти пакеты распространенными драйверами TCP, привожу только для полноты картины) Превышен лимит попыток отправки данных Первые два способа обнаружения разрыва соединения - ненадежны, поэтому остается думать о третьем. Чтобы оказался превышен лимит попыток отправки данных - TCP должен эти самые данные передавать. А значит, надо периодически записывать что-нибудь в сокет. Пока данные пишутся в сокет - разрыв соединения будет обнаружен. А вот если соединение простаивает - имеет смысл начать туда периодически записывать сообщения, которые другой стороной будут игнорироваться. Это и называется, условно, сообщением типа PING. Этот способ самый простой - просто по таймеру периодически пишем в сокет, дальше все произойдет само. Подход второй - keepalive сообщения Смысл данного подхода - в том, чтобы определить разрыв соединения раньше, чем это сможет сделать драйвер TCP. Для этого вводится таймер, отсчитывающий время от приема последнего байта через сокет. После каждого выполнения чтения из сокета таймер начинает отсчет заново. В такой схеме клиент будет отключен спустя определенное время после того как он "замолчал". Если это является для клиента нежелательным - он должен периодически отправлять "пустые" сообщения, которые будут игнорироваться сервером, но сбрасывать таймер разрыва соединения. Такие сообщения называются keepalive-сообщениями. Этот способ сложнее прошлого, поскольку требует таймеров на обоих сторонах соединения. Про комбинацию подходов. Если необходимо проверять статус соединения с обоих сторон, то возможен вариант, когда сервер отправляет пустые сообщения - а клиент их только принимает. В таком случае на сервере достаточно периодически писать в сокет - а на клиенте нужно будет делать таймер разрыва соединения. Или наоборот. В таком случае одно и то же сообщение будет себя вести и как ping-сообщение, и как keepalive-сообщение. При использовании одного и того же подхода пустые пакеты будут неизбежно ходить в обоих направлениях. Про ответы на "пинги" ("понги"). Сами по себе "понги" для проверки на разрыв соединения не нужны - и без них разрыв соединения прекрасно детектируется. Но "понги" защищают от другой проблемы - некоторые клиенты могут (в результате ошибки) перевести соединение в полуоткрытое состояние, когда они могут только читать - но больше не посылают пакетов. При неправильной реализации сервера соединение может надолго застрять в таком состоянии. Про ответы на keepalive-сообщения. Сами по себе keepalive-сообщения, если используются в обе стороны, прекрасно определяют как разрыв соединения, так и застревание его в "полуоткрытом" состоянии. Но введение обязательных ответов на keepalive-сообщения позволяет клиенту и серверу иметь разные настройки интервалов таймера.
Комментариев нет:
Отправить комментарий