최근 질문에 대한 후속 으로, TCP 소켓에서 읽기 / 쓰기를 시도하지 않고 Java에서 소켓이 피어에 의해 정상적으로 닫 혔음을 감지하는 것이 왜 불가능한지 궁금합니다. 이 관계없이 하나가 사전 NIO 사용하는지 여부의 경우 것 같다 Socket
또는 NIO SocketChannel
.
피어가 TCP 연결을 정상적으로 닫으면 연결 양쪽의 TCP 스택이 그 사실을 알고 있습니다. 서버 측 (종료를 시작하는 서버)은 state에서 끝나는 FIN_WAIT2
반면 클라이언트 측 (종료에 명시 적으로 응답하지 않는 서버)은 state에서 끝납니다 CLOSE_WAIT
. 왜의 방법이 아닌 Socket
또는 SocketChannel
기본 TCP 연결이 종료되었는지 여부를 확인하기 위해 즉, TCP 스택을 조회 할 수는? TCP 스택이 그러한 상태 정보를 제공하지 않는 것입니까? 아니면 비용이 많이 드는 커널 호출을 피하기위한 설계 결정입니까?
이 질문에 대한 답변을 이미 게시 한 사용자의 도움으로 문제의 원인을 알 수 있다고 생각합니다. 연결을 명시 적으로 닫지 않은 쪽은 TCP 상태 CLOSE_WAIT
가되어 연결이 종료되는 과정에 있으며 쪽이 자체 CLOSE
작업 을 실행할 때까지 기다립니다 . 나는 그것이 isConnected
반환 true
하고 isClosed
반환 하는 것이 충분히 공정하다고 생각 false
하지만, 왜 그런 것이 isClosing
없는가?
다음은 pre-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
...
답변
저는 주로 Selector와 함께 소켓을 사용해 왔으며 Network OSI 전문가는 아니지만 shutdownOutput()
소켓을 호출 하면 실제로 다른 쪽에서 Selector를 깨우는 네트워크 (FIN)를 전송합니다 (C에서도 동일한 동작). 언어). 여기에 감지 기능이 있습니다 . 실제로 시도 할 때 실패 할 읽기 작업을 감지합니다.
제공하는 코드에서 소켓을 닫으면 사용 가능한 데이터를 읽을 가능성없이 입력 및 출력 스트림이 모두 종료되어 손실됩니다. Java Socket.close()
메서드는 출력 스트림에 남아있는 데이터 가 FIN 으로 전송된다는 점에서 “단계적”연결 해제 (처음에 생각했던 것과 반대)를 수행합니다. 으로 전송되어 종료를 알리는 . FIN은 일반 패킷이 1 처럼 다른 쪽에서 ACK됩니다 .
상대방이 소켓을 닫을 때까지 기다려야하는 경우 FIN을 기다려야합니다. 그리고 그것을 달성하기 위해, 당신은 할 필요가 감지 Socket.getInputStream().read() < 0
하는 당신이해야 의미 하지 로는 것, 당신의 소켓 닫기 의 닫습니다InputStream
.
내가 C에서, 그리고 지금은 자바에서했던 것에서, 이러한 동기화 된 닫기를 달성하는 것은 다음과 같이 이루어져야합니다 :
- 셧다운 소켓 출력 (다른 쪽 끝에 FIN을 보냅니다. 이것이이 소켓이 보낸 마지막 것입니다). 입력은 여전히 열려 있으므로
read()
리모컨을 감지 할 수 있습니다.close()
InputStream
다른 쪽에서 응답 -FIN을받을 때까지 소켓을 읽습니다 ( FIN을 감지하므로 동일한 단계적 연결 프로세스를 거치게됩니다). 버퍼 중 하나에 데이터가 포함되어있는 한 소켓을 실제로 닫지 않기 때문에 일부 OS에서 중요합니다. “유령”소켓이라고하며 OS에서 설명자 번호를 사용합니다 (최신 OS에서는 더 이상 문제가되지 않을 수 있음).- (어느 쪽인지를 호출하여 소켓을 닫습니다
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 : ” Fundamental Networking in Java “에서 : 그림 참조. 3.3 p. 45 및 전체 §3.7, pp 43-48
답변
나는 이것이 소켓 프로그래밍 질문에 더 가깝다고 생각합니다. Java는 소켓 프로그래밍 전통을 따르고 있습니다.
에서 위키 백과 :
TCP는 한 컴퓨터의 한 프로그램에서 다른 컴퓨터의 다른 프로그램으로 바이트 스트림을 안정적으로 순서대로 전달합니다.
핸드 셰이크가 완료되면 TCP는 두 끝점 (클라이언트와 서버)을 구분하지 않습니다. “클라이언트”와 “서버”라는 용어는 대부분 편의를위한 것입니다. 따라서 “서버”는 데이터를 보내고 “클라이언트”는 다른 데이터를 서로에게 동시에 보낼 수 있습니다.
“닫기”라는 용어도 오해의 소지가 있습니다. FIN 선언 만 있습니다. “더 이상 보내지 않겠다”는 뜻입니다. 그러나 이것은 비행 중에 패킷이 없거나 다른 패킷이 더 이상 말할 수 없다는 것을 의미하지는 않습니다. 달팽이 메일을 데이터 링크 계층으로 구현하거나 패킷이 다른 경로를 이동 한 경우 수신자가 잘못된 순서로 패킷을 수신 할 수 있습니다. TCP는이 문제를 해결하는 방법을 알고 있습니다.
또한 프로그램으로서 버퍼에있는 내용을 계속 확인할 시간이 없을 수도 있습니다. 따라서 편의에 따라 버퍼에 무엇이 있는지 확인할 수 있습니다. 대체로 현재 소켓 구현은 그렇게 나쁘지 않습니다. 실제로 isPeerClosed ()가 있었다면 read를 호출 할 때마다해야하는 추가 호출입니다.
답변
기본 소켓 API에는 이러한 알림이 없습니다.
송신 TCP 스택은 어쨌든 마지막 패킷까지 FIN 비트를 보내지 않을 것입니다. 따라서 송신 애플리케이션이 데이터가 전송되기 전에 논리적으로 소켓을 닫을 때 버퍼링 된 데이터가 많을 수 있습니다. 마찬가지로 네트워크가 수신 애플리케이션보다 빠르기 때문에 버퍼링 된 데이터 (모르겠습니다. 더 느린 연결을 통해 중계 할 수 있음)는 수신자에게 중요 할 수 있으며 수신 애플리케이션이 데이터를 버리는 것을 원하지 않을 것입니다. FIN 비트가 스택에 의해 수신 되었기 때문입니다.
답변
지금까지 질문에 대한 완전한 답변이 없기 때문에 문제에 대한 현재 이해를 요약하고 있습니다.
TCP 연결이 설정되고 하나의 피어가 close()
또는 shutdownOutput()
해당 소켓을 호출 하면 연결의 다른 쪽 소켓이 CLOSE_WAIT
상태 로 전환됩니다 . 원칙적으로 소켓이 CLOSE_WAIT
호출하지 않고 상태 인지 여부를 TCP 스택에서 확인할 수 있습니다 read/recv
(예 getsockopt()
: Linux : http://www.developerweb.net/forum/showthread.php?t=4395 ). 가지고 다닐 수 있는.
Java의 Socket
클래스는 BSD TCP 소켓에 필적하는 추상화를 제공하도록 설계된 것으로 보입니다. 아마도 이것이 TCP / IP 애플리케이션을 프로그래밍 할 때 사람들이 사용하는 추상화 수준이기 때문입니다. BSD 소켓은 INET (예 : TCP) 소켓 이외의 소켓을 지원하는 일반화이므로 소켓의 TCP 상태를 찾는 이식 가능한 방법을 제공하지 않습니다.
isCloseWait()
BSD 소켓이 제공하는 추상화 수준에서 TCP 애플리케이션을 프로그래밍하는 데 익숙한 사람들은 Java가 추가 메서드를 제공 할 것이라고 기대하지 않기 때문에 이와 같은 방법 은 없습니다.
답변
(TCP) 소켓 연결의 원격 측이 닫혔는지 여부를 감지하는 것은 java.net.Socket.sendUrgentData (int) 메소드를 사용하여 수행 할 수 있으며 원격 측이 다운 된 경우 발생하는 IOException을 포착 할 수 있습니다. 이것은 Java-Java와 Java-C 사이에서 테스트되었습니다.
이것은 일종의 핑 메커니즘을 사용하도록 통신 프로토콜을 설계하는 문제를 방지합니다. 소켓에서 OOBInline을 비활성화 (setOOBInline (false))하면 수신 된 모든 OOB 데이터가 자동으로 삭제되지만 OOB 데이터는 계속 전송 될 수 있습니다. 원격 측이 닫히면 연결 재설정이 시도되고 실패하고 일부 IOException이 발생합니다. .
실제로 프로토콜에서 OOB 데이터를 사용하는 경우 마일리지가 다를 수 있습니다.
답변
Java IO 스택은 갑작스러운 분해로 파괴 될 때 확실히 FIN을 보냅니다. 당신이 이것을 감지 할 수 없다는 것은 말이되지 않습니다. b / c 대부분의 클라이언트는 그들이 연결을 종료하는 경우에만 FIN을 보냅니다.
… 제가 NIO Java 클래스를 정말 싫어하기 시작한 또 다른 이유입니다. 모든 것이 약간의 엉덩이 인 것 같습니다.
답변
흥미로운 주제입니다. 확인하기 위해 방금 자바 코드를 파헤 쳤습니다. 내 발견에서 두 가지 뚜렷한 문제가 있습니다. 첫 번째는 TCP RFC 자체로, 원격으로 닫힌 소켓이 반이중으로 데이터를 전송할 수 있으므로 원격으로 닫힌 소켓은 여전히 절반이 열려 있습니다. RFC에 따라 RST는 연결을 닫지 않으므로 명시적인 ABORT 명령을 보내야합니다. 그래서 Java는 반 폐쇄 소켓을 통해 데이터를 보낼 수 있습니다.
(두 끝점에서 닫힘 상태를 읽는 방법에는 두 가지가 있습니다.)
다른 문제는 구현에서이 동작이 선택 사항이라고 말합니다. Java는 이식성을 위해 노력하면서 최고의 공통 기능을 구현했습니다. (OS, 반이중 구현)의지도를 유지하는 것이 문제 였을 것입니다.
