Java 소켓 API : 연결이 닫혔는지 확인하는 방법은 무엇입니까?


93

Java 소켓 API에 몇 가지 문제가 있습니다. 현재 내 게임에 연결된 플레이어의 수를 표시하려고합니다. 플레이어가 언제 연결되었는지 쉽게 확인할 수 있습니다. 그러나 소켓 API를 사용하여 플레이어의 연결이 끊어진시기를 확인하는 것은 불필요하게 어려워 보입니다.

isConnected()원격으로 연결이 끊어진 소켓에 대한 호출 은 항상 반환되는 것처럼 보입니다 true. 마찬가지로 isClosed()원격으로 닫힌 소켓을 호출 하면 항상 false. 소켓이 닫혔는지 여부를 실제로 확인하기 위해 데이터를 출력 스트림에 기록하고 예외를 포착해야한다는 것을 읽었습니다. 이것은이 상황을 처리하는 정말 불결한 방법 인 것 같습니다. 우리는 소켓이 언제 닫혔는지 알기 위해 네트워크를 통해 쓰레기 메시지를 지속적으로 스팸해야합니다.

다른 해결책이 있습니까?

답변:


183

현재 연결 상태를 알려주는 TCP API는 없습니다. isConnected()그리고 isClosed()당신에게 현재 상태에게 당신의 소켓을 . 같은 것이 아닙니다.

  1. isConnected()이 소켓 을 연결 했는지 여부 알려줍니다 . 그래서 그것은 사실을 반환합니다.

  2. isClosed()이 소켓 을 닫았 는지 여부 알려줍니다 . 당신이 가질 때까지 그것은 거짓을 반환합니다.

  3. 피어 가 순서대로 연결 을 닫은 경우

    • read() -1 반환
    • readLine() 보고 null
    • readXXX()EOFException다른 XXX에 대해 던졌습니다 .

    • 쓰기는 IOException결국 버퍼링 지연에 따라 '피어에 의한 연결 재설정' 을 throw합니다 .

  4. 다른 이유로 연결이 끊어진 경우 쓰기는 IOException결국 위와 같이를 던지고 읽기는 동일한 작업을 수행 할 수 있습니다.

  5. 피어가 여전히 연결되어 있지만 연결을 사용하지 않는 경우 읽기 제한 시간을 사용할 수 있습니다.

  6. 다른 곳에서 읽을 수있는 것과는 달리 ClosedChannelException이것을 말하지 마십시오. [도 안함 SocketException: socket closed.] 채널 닫은 다음 계속 사용 했음을 나타냅니다. 즉, 귀하의 프로그래밍 오류입니다. 닫힌 연결을 나타내지 않습니다 .

  7. Windows XP에서 Java 7을 사용한 몇 가지 실험 결과 다음과 같은 경우에도 나타납니다.

    • 당신은 선택하고 있습니다 OP_READ
    • select() 0보다 큰 값을 반환합니다.
    • 연결된 항목 SelectionKey이 이미 잘못되었습니다 ( key.isValid() == false).

    피어가 연결을 재설정했음을 의미합니다. 그러나 이것은 JRE 버전 또는 플랫폼에 고유 할 수 있습니다.


19
연결 지향적 인 TCP 프로토콜이 연결 상태조차 알 수 없다는 것이 믿기지 않습니다.이 프로토콜을 제시하는 사람들이 눈을 감고 차를 운전합니까?
PedroD 2014-08-11

33
@PedroD 반대로 : 그것은 의도적이었습니다. SNA와 같은 이전 프로토콜 제품군에는 '다이얼 톤'이있었습니다. TCP는 핵전쟁에서 살아남도록 설계되었으며,보다 사소한 것은 라우터 다운 및 업입니다. 따라서 발신음, 연결 상태 등과 같은 것이 전혀 없습니다. 또한 TCP keepalive가 RFC에서 논란의 여지가있는 기능으로 설명 된 이유와 기본적으로 항상 꺼져있는 이유이기도합니다. TCP는 여전히 우리와 함께합니다. SNA? IPX? ISO? 아니. 그들은 옳았습니다.
Marquis of Lorne

4
이 정보를 우리에게 숨기는 것이 좋은 변명이라고 생각하지 않습니다. 연결이 끊어 졌다는 것을 아는 것이 반드시 프로토콜의 내결함성이 낮다는 것을 의미하지는 않습니다. 항상 우리가 그 지식으로 무엇을하는지에 따라 달라집니다. 저에게있어 isBound와 isConnected from java 메소드는 순수한 모의 메소드입니다. ,하지만 연결 이벤트 리스너의 필요성을 표현합니다 ...하지만 다시 말씀 드리지만, 연결이 끊어 졌다고해서 프로토콜이 나빠지는 것은 아닙니다. 이제 당신이 말하는 프로토콜이 연결이 끊어진 것을 감지하자마자 연결을 끊었다면 그것은 다른 이야기입니다.
PedroD

23
@Pedro 이해가 안 돼요. '변명'이 아닙니다. 보류 할 정보가 없습니다. 발신음이 없습니다. TCP 사용자가 무언가를 시도 할 때까지 연결 실패 여부를 알지 못합니다 . 이것이 기본 설계 기준이었습니다.
Marquis of Lorne

2
@EJP 귀하의 답변에 감사드립니다. 소켓이 "블로킹"없이 닫혔는지 확인하는 방법을 알고 있습니까? read 또는 readLine을 사용하면 차단됩니다. 다른 소켓이 사용 가능한 메서드로 닫혔는지 알 수있는 방법이 있습니까? 0대신 -1.
insumity

9

다양한 메시징 프로토콜에서 서로 하트 비트를 유지하는 것이 일반적입니다 (핑 패킷을 계속 전송). 패킷이 매우 클 필요는 없습니다. 프로브 메커니즘을 사용하면 TCP가 일반적으로 파악하기 전에도 연결이 끊어진 클라이언트를 감지 할 수 있습니다 (TCP 시간 초과가 훨씬 더 높음). 2-3에 대한 응답이 표시되지 않으면 프로브를 보내고 응답을 5 초 동안 기다립니다. 후속 프로브, 플레이어 연결이 끊어집니다.

또한 관련 질문


6
일부 프로토콜 에서는 '일반적인 관행'입니다 . 지구상에서 가장 많이 사용되는 애플리케이션 프로토콜 인 HTTP에는 PING 작업이 없습니다.
Marquis of Lorne

2
HTTP / 1은 원샷 프로토콜이므로 @ user207421입니다. 요청을 보내면 응답을받습니다. 그리고 당신은 끝났습니다. 소켓이 닫혀 있습니다. Websocket 확장에는 PING 작업이 있으므로 HTTP / 2도 마찬가지입니다.
Michał Zabielski

나는 :) 가능하면 단일 바이트로 하트 비트 추천
스테판 제국을

@ MichałZabielski HTTP 디자이너가 지정하지 않았기 때문입니다. 당신은 왜 안되는지 모릅니다. HTTP / 1.1은 원샷 프로토콜이 아닙니다. 소켓이 닫히지 않음 : HTTP 연결은 기본적으로 지속됩니다. FTP, SMTP, POP3, IMAP, TLS, ...에는 하트 비트가 없습니다.
Marquis of Lorne

2

방금 게시 된 다른 답변을 보지만 게임을하는 클라이언트와 상호 작용한다고 생각하므로 다른 접근 방식을 취할 수 있습니다 (BufferedReader는 어떤 경우에는 확실히 유효합니다).

원한다면 ... "등록"책임을 클라이언트에게 위임 할 수 있습니다. 즉, 각각에서받은 마지막 메시지에 타임 스탬프가있는 연결된 사용자 모음이있을 것입니다. 클라이언트가 시간 초과되면 클라이언트를 강제로 다시 등록해야하지만 아래의 인용문과 아이디어로 이어집니다.

소켓이 닫혔는지 여부를 실제로 확인하기 위해 데이터를 출력 스트림에 기록해야하고 예외를 잡아야한다는 것을 읽었습니다. 이것은이 상황을 처리하는 정말 불결한 방법 인 것 같습니다.

Java 코드가 소켓을 닫거나 연결 해제하지 않은 경우 원격 호스트가 연결을 닫았 음을 어떻게 알릴 수 있습니까? 궁극적으로 try / catch는 ACTUAL 소켓에서 이벤트를 수신하는 폴러가 수행하는 것과 거의 동일한 작업을 수행합니다. 다음을 고려하세요:

  • 로컬 시스템은 알리지 않고 소켓을 닫을 수 있습니다. 이는 소켓의 구현 일뿐입니다 (즉, 상태 변경을 위해 하드웨어 / 드라이버 / 펌웨어 / 무엇이든 폴링하지 않습니다).
  • new Socket (Proxy p) ... 여러 당사자 (정말 6 개의 끝점)가 연결을 끊을 수 있습니다 ...

추상화 된 언어의 특징 중 하나는 당신이 사소한 부분에서 추상화된다는 것입니다. SqlConnection에 대한 C # (try / finally)의 using 키워드를 생각해보십시오 ... 그것은 단지 비즈니스를 수행하는 비용 일뿐입니다. try / catch / finally는 소켓 사용을위한 허용되고 필요한 패턴이라고 생각합니다.


로컬 시스템은 사용자에게 알리거나 알리지 않고 '소켓을 닫을'수 없습니다. 'Java 코드가 소켓을 닫거나 연결 해제하지 않은 경우 ...?'로 시작하는 질문 어느 쪽도 말이되지 않습니다.
론의 후작

1
시스템 (예 : Linux)이 소켓을 강제로 닫는 것을 정확히 방지하는 것은 무엇입니까? 'call close'명령을 사용하여 'gdb'에서 여전히 할 수 있습니까?
Andrey Lebedenko

@AndreyLebedenko '정확히 방지'하는 것은 없지만 그것을하지 않습니다.
Marquis of Lorne

@EJB 그럼 누가?
Andrey Lebedenko

@AndreyLebedenko 아무도 그것을하지 않습니다. 응용 프로그램 만 자체 소켓을 닫을 수 있습니다. 그렇지 않은 경우 종료하지 않으면 OS가 정리됩니다.
Marquis of Lorne

1

저는 이것이 TCP 연결의 특성이라고 생각합니다. 표준에서 송신 연결이 끊어 졌다고 결론을 내리기까지 약 6 분 동안 무음이 전송됩니다! 따라서이 문제에 대한 정확한 해결책을 찾을 수 없다고 생각합니다. 아마도 더 나은 방법은 서버가 사용자 연결이 닫혀 있다고 가정해야하는시기를 추측하는 편리한 코드를 작성하는 것입니다.


2
그보다 훨씬 더 많은 시간이 소요될 수 있으며 최대 2 시간이 소요될 수 있습니다.
론의 후작

0

@ user207421이 말했듯이 TCP / IP 프로토콜 아키텍처 모델 때문에 현재 연결 상태를 알 수있는 방법이 없습니다. 따라서 서버는 연결을 종료하기 전에 사용자를 인식해야하거나 사용자가 직접 확인해야합니다.
다음은 서버가 소켓을 닫는 방법을 보여주는 간단한 예입니다.

sockAdr = new InetSocketAddress(SERVER_HOSTNAME, SERVER_PORT);
socket = new Socket();
timeout = 5000;
socket.connect(sockAdr, timeout);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream());
while ((data = reader.readLine())!=null) 
      log.e(TAG, "received -> " + data);
log.e(TAG, "Socket closed !");

0

비슷한 문제에 직면했습니다. 제 경우에는 클라이언트가 주기적으로 데이터를 보내야합니다. 동일한 요구 사항이 있기를 바랍니다. 그런 다음 지정된 시간이 만료되면 socket.setSoTimeout(1000 * 60 * 5);throw되는 SO_TIMEOUT 을 설정합니다 java.net.SocketTimeoutException. 그러면 죽은 클라이언트를 쉽게 감지 할 수 있습니다.


-2

그게 내가 처리하는 방법

 while(true) {
        if((receiveMessage = receiveRead.readLine()) != null ) {  

        System.out.println("first message same :"+receiveMessage);
        System.out.println(receiveMessage);      

        }
        else if(receiveRead.readLine()==null)
        {

        System.out.println("Client has disconected: "+sock.isClosed()); 
        System.exit(1);
         }    } 

result.code == null 인 경우


readLine()두 번 전화 할 필요가 없습니다 . else블록 에서 null이라는 것을 이미 알고 있습니다 .
Marquis of Lorne

-4

리눅스에서, 당신이 모르는 다른 쪽이 닫혀있는 소켓에 write () ing을하면 SIGPIPE 신호 / 예외가 발생하지만 당신은 그것을 호출하고자합니다. 그러나 SIGPIPE에 잡히지 않으려면 MSG_NOSIGNAL 플래그와 함께 send ()를 사용할 수 있습니다. send () 호출은 -1로 반환되며,이 경우 errno.h에 따라 다음과 같은 값 EPIPE로 끊어진 파이프 (이 경우 소켓)를 작성하려고 시도했음을 알려주는 errno를 확인할 수 있습니다. 32. EPIPE에 대한 반응으로 두 배로 돌아가 소켓을 다시 열고 정보를 다시 보내려고 할 수 있습니다.


send()전화는 -1을 돌려줍니다 나가는 데이터가 만료 송신 타이머를위한 충분한 시간을 위해 버퍼링있어 경우에만 가능합니다. 양쪽 끝의 버퍼링과 내부의 비동기 특성으로 인해 연결 해제 후 첫 번째 전송에서는 거의 확실하게 발생하지 않습니다 send().
Marquis of Lorne
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.