В ответ на недавний вопрос , я задаюсь вопросом, почему в Java невозможно без попытки чтения / записи в сокете TCP обнаружить, что сокет был корректно закрыт партнером? Похоже, что это так, независимо от того, используете ли вы pre-NIO Socket
или NIO SocketChannel
.
Когда одноранговый узел корректно закрывает TCP-соединение, TCP-стеки на обеих сторонах соединения знают об этом факте. Сторона сервера (та, которая инициирует завершение работы) оказывается в состоянии FIN_WAIT2
, тогда как сторона клиента (та, которая явно не реагирует на отключение) оказывается в состоянии CLOSE_WAIT
. Почему не существует способ , в Socket
или SocketChannel
что может запросить стек TCP , чтобы увидеть , было ли прекращено базовое соединение TCP? Может быть, стек TCP не предоставляет такую информацию о состоянии? Или это дизайнерское решение, позволяющее избежать дорогостоящего обращения к ядру?
С помощью пользователей, которые уже опубликовали некоторые ответы на этот вопрос, я думаю, что вижу, откуда может возникнуть проблема. Сторона, которая явно не закрывает соединение, оказывается в состоянии TCP, CLOSE_WAIT
что означает, что соединение находится в процессе закрытия и ожидает, пока сторона выполнит свою собственную CLOSE
операцию. Я полагаю, это справедливо, что isConnected
возвращается true
и isClosed
возвращается false
, но почему нет ничего подобного isClosing
?
Ниже приведены тестовые классы, в которых используются сокеты до NIO. Но идентичные результаты получены при использовании NIO.
import java.net.ServerSocket;
import java.net.Socket;
public class MyServer {
public static void main(String[] args) throws Exception {
final ServerSocket ss = new ServerSocket(12345);
final Socket cs = ss.accept();
System.out.println("Accepted connection");
Thread.sleep(5000);
cs.close();
System.out.println("Closed connection");
ss.close();
Thread.sleep(100000);
}
}
import java.net.Socket;
public class MyClient {
public static void main(String[] args) throws Exception {
final Socket s = new Socket("localhost", 12345);
for (int i = 0; i < 10; i++) {
System.out.println("connected: " + s.isConnected() +
", closed: " + s.isClosed());
Thread.sleep(1000);
}
Thread.sleep(100000);
}
}
Когда тестовый клиент подключается к тест-серверу, вывод остается неизменным даже после того, как сервер инициирует отключение соединения:
connected: true, closed: false
connected: true, closed: false
...
источник
Socket.close
не изящное закрытие.Ответы:
Я часто использую сокеты, в основном с селекторами, и хотя я не являюсь экспертом по сетевой OSI, насколько я понимаю, вызов
shutdownOutput()
сокета фактически отправляет что-то в сеть (FIN), что пробуждает мой селектор на другой стороне (такое же поведение в C язык). Здесь у вас есть обнаружение : фактическое обнаружение операции чтения, которая завершится ошибкой при попытке ее выполнения.В приведенном вами коде закрытие сокета приведет к отключению как входных, так и выходных потоков без возможности чтения данных, которые могут быть доступны, что приведет к их потере. Метод Java
Socket.close()
выполняет «изящное» отключение (противоположное тому, что я изначально думал) в том смысле, что данные, оставшиеся в выходном потоке, будут отправлены с последующим FIN, чтобы сигнализировать о его закрытии. FIN будет ACK'd другой стороной, как любой обычный пакет будет 1 .Если вам нужно дождаться, пока другая сторона закроет свой сокет, вам нужно дождаться его FIN. И для этого вы должны обнаружить
Socket.getInputStream().read() < 0
, что означает, что вы не должны закрывать свой сокет, так как он закроет егоInputStream
.Из того, что я сделал на C, а теперь и на Java, достижение такого синхронизированного закрытия должно выполняться следующим образом:
read()
обнаружить удаленныйclose()
InputStream
пока мы не получим ответ-FIN с другого конца (поскольку он обнаружит FIN, он пройдет через тот же процесс постепенного установления соединения). Это важно для некоторых ОС, поскольку они фактически не закрывают сокет, пока один из его буферов все еще содержит данные. Они называются "призрачными" сокетами и используют номера дескрипторов в ОС (это может больше не быть проблемой для современных ОС).Socket.close()
или закрытия егоInputStream
илиOutputStream
)Как показано в следующем фрагменте кода Java:
public void synchronizedClose(Socket sok) { InputStream is = sok.getInputStream(); sok.shutdownOutput(); // Sends the 'FIN' on the network while (is.read() > 0) ; // "read()" returns '-1' when the 'FIN' is reached sok.close(); // or is.close(); Now we can close the Socket }
Конечно, обе стороны должны использовать один и тот же способ закрытия, иначе отправляющая часть может всегда отправлять достаточно данных, чтобы
while
цикл был занят (например, если отправляющая часть только отправляет данные и никогда не читает, чтобы обнаружить завершение соединения. Что неуклюже, но вы можете не контролировать это).Как отметил @WarrenDew в своем комментарии, отбрасывание данных в программе (прикладной уровень) приводит к необратимому отключению на прикладном уровне: хотя все данные были получены на уровне TCP (
while
цикл), они отбрасываются.1 : Из « Основы сетевых технологий в Java »: см. Рис. 3.3 с.45, и весь §3.7, стр 43-48
источник
socket.close()
"жестокий" в том смысле, что он не учитывает сигнализацию клиент / сервер. Сервер будет уведомлен об отключении клиента только тогда, когда его собственный выходной буфер сокета будет заполнен (поскольку данные не подтверждаются другой стороной, которая была закрыта).read()
возвращает -1 в конце потока и продолжает делать это, сколько бы раз вы это ни вызывали. ACread()
orrecv()
возвращает ноль в конце потока и продолжает делать это, сколько бы раз вы это ни вызывали.Я думаю, это скорее вопрос программирования сокетов. Java просто следует традиции программирования сокетов.
Из Википедии :
Как только рукопожатие выполнено, TCP не делает различий между двумя конечными точками (клиентом и сервером). Термины «клиент» и «сервер» используются в основном для удобства. Таким образом, «сервер» может отправлять данные, а «клиент» может отправлять некоторые другие данные одновременно друг другу.
Термин «Закрыть» также вводит в заблуждение. Есть только декларация FIN, что означает: «Я больше не буду вам посылать». Но это не значит, что в полете нет пакетов или другому нечего сказать. Если вы реализуете обычную почту в качестве уровня канала передачи данных, или если ваш пакет путешествовал по разным маршрутам, возможно, получатель получает пакеты в неправильном порядке. TCP знает, как это исправить.
Также у вас, как у программы, может не быть времени постоянно проверять, что находится в буфере. Итак, когда вам будет удобно, вы можете проверить, что находится в буфере. В общем, текущая реализация сокета не так уж и плоха. Если бы на самом деле был isPeerClosed (), этот дополнительный вызов вам нужно было бы делать каждый раз, когда вы хотите вызвать read.
источник
Базовый API сокетов не имеет такого уведомления.
Отправляющий TCP-стек в любом случае не будет отправлять бит FIN до последнего пакета, поэтому может быть много данных, буферизованных с момента, когда отправляющее приложение логически закрыло свой сокет, прежде чем эти данные даже будут отправлены. Точно так же данные, которые буферизуются, потому что сеть быстрее, чем принимающее приложение (я не знаю, возможно, вы ретранслируете их через более медленное соединение), могут быть важны для получателя, и вы не хотите, чтобы принимающее приложение их отбрасывало. просто потому, что стек получил бит FIN.
источник
Поскольку ни один из ответов до сих пор полностью не отвечает на вопрос, я резюмирую свое текущее понимание проблемы.
Когда TCP-соединение установлено и один партнер вызывает
close()
или используетshutdownOutput()
его сокет, сокет на другой стороне соединения переходит вCLOSE_WAIT
состояние. В принципе, можно узнать из стека TCP, находится ли сокет вCLOSE_WAIT
состоянии без вызоваread/recv
(например,getsockopt()
в Linux: http://www.developerweb.net/forum/showthread.php?t=4395 ), но это не так. портативный.Socket
Кажется, что класс Java разработан для обеспечения абстракции, сравнимой с сокетом BSD TCP, вероятно, потому, что это тот уровень абстракции, к которому люди привыкли при программировании приложений TCP / IP. Сокеты BSD - это обобщение, поддерживающее сокеты, отличные от INET (например, TCP), поэтому они не предоставляют переносимого способа определения состояния TCP сокета.Нет такого метода, как
isCloseWait()
потому, что люди, привыкшие программировать TCP-приложения на уровне абстракции, предлагаемой сокетами BSD, не ожидают, что Java предоставит какие-либо дополнительные методы.источник
isCloseWait()
потому что он поддерживается не на всех платформах.Определить, закрыта ли удаленная сторона сокета (TCP), можно с помощью метода java.net.Socket.sendUrgentData (int) и перехватить исключение IOException, которое он генерирует, если удаленная сторона не работает. Это было проверено между Java-Java и Java-C.
Это позволяет избежать проблемы разработки протокола связи для использования какого-либо механизма проверки связи. При отключении OOBInline на сокете (setOOBInline (false) любые полученные данные OOB автоматически отбрасываются, но данные OOB по-прежнему могут быть отправлены. Если удаленная сторона закрыта, выполняется попытка сброса соединения, сбой и вызывает некоторое исключение IOException .
Если вы действительно используете данные OOB в своем протоколе, ваш опыт может отличаться.
источник
стек ввода-вывода Java определенно отправляет FIN, когда он разрушается при внезапном разрыве. Нет никакого смысла в том, что вы не можете этого обнаружить, т.к. большинство клиентов отправляют FIN только в том случае, если они закрывают соединение.
... еще одна причина, по которой я действительно начинаю ненавидеть Java-классы NIO. Вроде все ползучие.
источник
Это интересная тема. Я только что покопался в коде Java, чтобы проверить. По моему мнению, есть две отдельные проблемы: первая - это сам TCP RFC, который позволяет удаленно закрытому сокету передавать данные в полудуплексном режиме, поэтому удаленно закрытый сокет остается полуоткрытым. Согласно RFC, RST не закрывает соединение, вам нужно отправить явную команду ABORT; поэтому Java позволяет отправлять данные через полузакрытый сокет
(Существует два метода чтения состояния закрытия на обеих конечных точках.)
Другая проблема в том, что реализация говорит, что это поведение необязательно. Поскольку Java стремится быть портативной, они реализовали лучшую общую функцию. Я полагаю, что сохранение карты (ОС, реализация полудуплекса) было бы проблемой.
источник
Это недостаток классов OO-сокетов Java (и всех других, на которые я смотрел) - нет доступа к системному вызову select.
Правильный ответ на C:
struct timeval tp; fd_set in; fd_set out; fd_set err; FD_ZERO (in); FD_ZERO (out); FD_ZERO (err); FD_SET(socket_handle, err); tp.tv_sec = 0; /* or however long you want to wait */ tp.tv_usec = 0; select(socket_handle + 1, in, out, err, &tp); if (FD_ISSET(socket_handle, err) { /* handle closed socket */ }
источник
getsocketop(... SOL_SOCKET, SO_ERROR, ...)
.Вот неудачный обходной путь. Используйте SSL;), и SSL выполняет тесное рукопожатие при разрыве, чтобы вы были уведомлены о закрытии сокета (большинство реализаций, похоже, выполняют разрыв надлежащего рукопожатия).
источник
Причина такого поведения (которое не относится к Java) заключается в том, что вы не получаете никакой информации о состоянии из стека TCP. В конце концов, сокет - это просто еще один дескриптор файла, и вы не можете узнать, есть ли реальные данные для чтения из него, не пытаясь на самом деле (здесь
select(2)
не поможет, он только сигнализирует, что вы можете попробовать без блокировки).Для получения дополнительной информации см. FAQ по сокетам Unix .
источник
select()
сообщает вам, есть ли данные или EOS, доступные для чтения без блокировки. «Сигналы, которые можно попробовать без блокировки» бессмысленны. Если вы находитесь в неблокирующем режиме, вы всегда можете попробовать без блокировки.select()
управляется данными в буфере приема сокета, ожидающим FIN или пробелом в буфере отправки сокета.getsockopt(SO_ERROR)
? Фактически, дажеgetpeername
скажет вам, подключен ли еще сокет.Только записи требуют обмена пакетами, что позволяет определить потерю соединения. Обычный обходной путь - использовать опцию KEEP ALIVE.
источник
Когда дело доходит до полуоткрытых сокетов Java, возможно, стоит взглянуть на isInputShutdown () и isOutputShutdown () .
источник