Я часто использую сокеты, в основном с селекторами, и хотя я не эксперт сети OSI, насколько я понимаю, вызов shutdownOutput()
на сокете фактически отправляет что-то в сети (FIN), что вызывает мой селектор на другом сторона (такое же поведение в языке Си). Здесь у вас есть обнаружение : фактически обнаружение операции чтения, которая завершится неудачно при попытке ее выполнить.
В коде, который вы даете, закрытие сокета отключит как входной, так и выходной потоки, без возможности чтения данных, которые могут быть доступны, и, следовательно, потерю их. Метод Java Socket.close()
выполняет "изящное" разъединение (противоположное тому, что я первоначально думал), в котором данные, оставленные в выходном потоке, будут отправлены , а затем FIN , чтобы сигнализировать о его закрытии. FIN будет ACK'd другой стороной, как любой обычный пакет будет 1 .
Если вам нужно подождать, пока другая сторона закроет сокет, вам нужно дождаться его FIN. И чтобы достичь этого, вы должны обнаружить Socket.getInputStream().read() < 0
, что означает, что вы должны , а не закрыть сокет, поскольку он закроет его InputStream
.
Из того, что я сделал в C, а теперь и в Java, достижение такого синхронизированного закрытия должно быть сделано так:
- Выход сокета выключения (отправляет FIN на другом конце, это последнее, что когда-либо будет отправлено этим сокетом). Вход по-прежнему открыт, поэтому вы можете
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