source

피어에 의해 TCP 소켓이 정상적으로 닫힌 것을 I/O를 시도하지 않고 검출할 수 없는 이유는 무엇입니까?

manycodes 2023. 1. 15. 17:18
반응형

피어에 의해 TCP 소켓이 정상적으로 닫힌 것을 I/O를 시도하지 않고 검출할 수 없는 이유는 무엇입니까?

최근 질문의 후속으로, Java에서는 TCP 소켓에서 읽기/쓰기를 시도하지 않고서는 왜 소켓이 피어에 의해 정상적으로 닫혔는지 알 수 없는 것입니까?이것은 프리 NIO를 사용하든 말든 마찬가지인 것 같습니다.Socket 는 NIOSocketChannel.

피어가 TCP 접속을 정상적으로 닫으면 접속 양쪽에 있는TCP 스택은 그 사실을 인식합니다.은, 「」( 「셧다운 상태가 .FIN_WAIT2 측)은, 가 됩니다.CLOSE_WAIT에 방법이 없는 이유는 무엇입니까?Socket ★★★★★★★★★★★★★★★★★」SocketChannelTCP 스택에 문의하여 기본 TCP 연결이 종료되었는지 여부를 확인할 수 있습니다.TCP 스택은 이러한 상태 정보를 제공하지 않습니까?아니면 비용이 많이 드는 커널 호출을 피하기 위한 설계 결정입니까?

이 질문에 대한 답변을 이미 게시한 사용자의 도움을 받아 문제의 원인을 알 수 있을 것 같습니다.닫지 .CLOSE_WAIT, 이고, 할 때까지 기다립니다.CLOSE작동.하는 것은 당연하다고 생각한다isConnectedtrue ★★★★★★★★★★★★★★★★★」isClosedfalse 왜 안 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
...

은 주로 셀렉터와 만, , 론 소켓을 자주 사용하고 .대부분 실렉터와 함께 사용하고 있습니다.네트워크 OSI 전문가는 아니지만, 제가 알기론, 전화하고 있습니다.shutdownOutput()Socket 상에서 실제로 네트워크(FIN)를 송신하면, 다른 쪽의 셀렉터가 기동합니다(C언어에서도 같은 동작).여기 검출이 있습니다.실제로, 판독 조작을 시행하면 실패하는 것을 검출합니다.

지정한 코드에서 소켓을 닫으면 입력 스트림과 출력 스트림이 모두 종료되고 사용 가능한 데이터를 읽을 수 없으므로 손실됩니다.Socket.close()method는 출력 스트림에 남겨진 데이터가 FIN에 의해 전송되고 닫힘 신호가 전달된다는 점에서 (처음 생각했던 것과 반대로) "그레이스풀" 절단을 수행합니다.FIN은, 통상의1 패킷과 같이, 상대측의 ACK가 됩니다.

다른 쪽의 소켓이 닫힐 때까지 기다려야 할 경우 FIN을 기다려야 합니다.그리고 그러기 위해서는, 당신은 이 모든 것들을Socket.getInputStream().read() < 0즉, 소켓이 닫힐 수 있으므로 소켓을 닫지 마십시오.

C에서, 그리고 현재 Java에서 이렇게 동기화된 닫힘을 달성하려면 다음과 같이 해야 합니다.

  1. FIN을 사용하다이것이 이 소켓에 의해 송신되는 마지막 것입니다).은 아직 있기 에, 「」를 사용할 수 .read()합니다.close()
  2. 을 읽다InputStream상대측으로부터 응답 FIN을 수신할 때까지(PIN을 검출하기 때문에, 같은 그레이스 풀 접속 프로세스를 거칩니다).일부 OS에서는 버퍼에 데이터가 포함되어 있는 한 소켓을 실제로 닫지 않기 때문에 이 기능이 중요합니다. 번호를 OS에서는 않을 수 ).
  3. 합니다).Socket.close() 는 its를 .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를 사용합니다.whileloop됩니다.

1: "Java의 기본 네트워킹"에서 : 그림 3.3 페이지 45 및 전체 § 3.7, 페이지 43-48 참조

내 생각에 이건 소켓 프로그래밍 질문인 것 같아.Java는 소켓 프로그래밍의 전통을 따르고 있을 뿐입니다.

Wikipedia에서:

TCP는 한 컴퓨터의 프로그램에서 다른 컴퓨터의 다른 프로그램으로 바이트 스트림을 신뢰할 수 있는 순서대로 전달합니다.

핸드쉐이크가 완료되면 TCP는 2개의 엔드 포인트(클라이언트와 서버)를 구별하지 않습니다."클라이언트"와 "서버"라는 용어는 대부분 편의상 사용됩니다.따라서 "서버"는 데이터를 전송하고 "클라이언트"는 서로 다른 데이터를 동시에 전송할 수 있습니다.

"닫기"라는 용어도 오해를 불러일으킬 수 있습니다.FIN 선언밖에 없는데, 이는 "더 이상 물건을 보내지 않겠다"는 뜻이다.그러나 이는 전송 중인 패킷이 없거나 다른 패킷이 더 이상 말할 필요가 없음을 의미하는 것은 아닙니다.스네일 메일을 데이터 링크 레이어로 실장하거나 패킷이 다른 루트로 이동했을 경우 수신자가 패킷을 잘못된 순서로 수신할 수 있습니다.TCP는 이 문제를 해결하는 방법을 알고 있습니다.

또한 프로그램으로서 버퍼에 무엇이 있는지 계속 확인할 시간이 없을 수도 있습니다.따라서 언제든지 버퍼에 무엇이 있는지 확인할 수 있습니다.대체로 현재의 소켓 실장은 나쁘지 않습니다.실제로 isPeerClosed()가 있는 경우, 읽기 호출을 할 때마다 추가 호출을 해야 합니다.

기본 소켓 API에는 이러한 알림이 없습니다.

송신측 TCP 스택은, 어쨌든 마지막 패킷까지 FIN 비트를 송신하지 않기 때문에, 송신측 애플리케이션이 논리적으로 소켓을 닫았을 때부터, 데이터가 송신되기 전에 버퍼링 되는 데이터가 많을 가능성이 있습니다.마찬가지로 네트워크가 수신 어플리케이션보다 빠르기 때문에 버퍼링되는 데이터(모르겠습니다만, 느린 접속으로 릴레이 하고 있는 경우도 있습니다)는 수신측 어플리케이션에 있어서 중요한 데이터일 수 있습니다.또, FIN 비트가 스택에 의해서 수신되었다고 해서, 수신측 어플리케이션이 그것을 폐기하는 것은 바람직하지 않습니다.

지금까지의 답변 중 충분한 답변이 없었기 때문에, 저는 이 문제에 대한 저의 현재 이해를 정리하고 있습니다.

, 의 피어가 TCP 를 호출하고 있는 .close() ★★★★★★★★★★★★★★★★★」shutdownOutput()의 소켓이 '접속'으로 합니다.CLOSE_WAIT 스택에 할 수 CLOSE_WAITread/recv (예:getsockopt()Linux: http://www.developerweb.net/forum/showthread.php?t=4395),. 단, 그것은 포터블이 아닙니다.

의 ★★★Socket클래스는 BSD TCP 소켓에 버금가는 추상화를 제공하도록 설계되어 있는 것 같습니다./IP 의 TCP/IP 의 경우 범용 소켓이기 TCP 의 상태를 할 수 .BSD 소켓은 INET(예를 들어 TCP) 소켓 이외의 소켓을 지원하는 범용이므로 소켓의 TCP 상태를 검출하는 휴대용 방법은 제공되지 않습니다.

isCloseWait()왜냐하면 BSD 소켓이 제공하는 추상화 수준에서 TCP 애플리케이션을 프로그래밍하는 데 익숙한 사람들은 Java가 추가 메서드를 제공할 것으로 기대하지 않기 때문입니다.

java.net 에서는, 소켓 접속의 리모트측이 닫혔는지 아닌지를 검출할 수 있습니다.Socket.sendUrgentData(int) 메서드 및 리모트측이 다운되었을 경우에 느려지는 IOException을 포착합니다.이는 Java-Java와 Java-C 사이에서 테스트되었습니다.

이를 통해 일종의 ping 메커니즘을 사용하도록 통신 프로토콜을 설계하는 문제를 피할 수 있습니다.소켓에서 OOBnline을 디세블로 하면(setOOBnline(false)), 수신한 OOB 데이터는 사일런트 폐기되지만 OOB 데이터는 송신할 수 있습니다.리모트측이 닫혀 있는 경우는, 접속 리셋이 시행되어 실패해, IOException이 느려집니다.

프로토콜에서 OOB 데이터를 실제로 사용하는 경우 주행 거리가 달라질 수 있습니다.

Java IO 스택은 갑작스러운 해체 시 반드시 FIN을 전송합니다.이것을 검출할 수 없다는 것은 의미가 없습니다.대부분의 클라이언트는 접속을 셧다운 하고 있는 경우에만 FIN을 송신합니다.

내가 NIO Java 수업을 정말 싫어하게 된 또 다른 이유.모든 게 좀 어중간한 것 같아

흥미로운 주제입니다.방금 자바 코드를 뒤져서 확인했어.제가 알아낸 바로는 두 가지 분명한 문제가 있습니다.첫 번째는 TCP RFC 자체입니다.이것에 의해 리모트 클로즈드 소켓이 반이중으로 데이터를 송신할 수 있기 때문에 리모트 클로즈드 소켓은 아직 반이중으로 열려 있습니다.RFC에 따르면 RST는 접속을 닫지 않습니다.따라서 Java는 하프 클로즈드소켓 경유로 데이터를 전송할 수 있습니다.

(양쪽 엔드포인트에서 닫힘 상태를 읽는 방법은 두 가지가 있습니다).

또 다른 문제는 이 동작이 옵션이라고 실장되어 있다는 것입니다.Java는 휴대성을 추구하기 위해 노력하고 있기 때문에, 최고의 공통 기능을 실장했습니다.(OS, 반이중 구현)의 맵을 유지하는 것은 문제가 있다고 생각합니다.

이는 Java(및 지금까지 살펴본 다른 모든) OO 소켓 클래스의 결함으로, 선택한 시스템 호출에 액세스할 수 없습니다.

정답은 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 */  
}  

여기 어설픈 회피책이 있습니다.SSL 을 사용합니다).또한 SSL은 해체 시 클로즈 핸드쉐이크를 실행하여 소켓이 닫혔음을 통지합니다(대부분의 실장에서는 적절한 핸드쉐이크 해체가 이루어지는 것 같습니다).

이 동작의 이유(Java 고유하지 않음)는 TCP 스택에서 상태 정보를 얻을 수 없기 때문입니다.일 뿐이므로 읽어보려고 실제로 알 수 select(2)도움이 되지 않습니다.차단하지 않고 시도할 수 있다는 신호뿐입니다).

상세한 것에 대하여는, UNIX 소켓의 FAQ 를 참조해 주세요.

패킷 교환이 필요한 것은 쓰기뿐입니다.이를 통해 접속 상실을 판단할 수 있습니다.일반적인 회피책은 KEEP ALIVE 옵션을 사용하는 것입니다.

하프 오픈 Java 소켓에 대해서는 isInputShutdown()과 isOutputShutdown()을 참조해 주십시오.

언급URL : https://stackoverflow.com/questions/155243/why-is-it-impossible-without-attempting-i-o-to-detect-that-tcp-socket-was-grac

반응형