programing

I / O를 시도하지 않고 TCP 소켓이 피어에 의해 정상적으로 닫 혔음을 감지하는 것이 왜 불가능합니까?

nasanasas 2020. 9. 8. 08:02
반응형

I / O를 시도하지 않고 TCP 소켓이 피어에 의해 정상적으로 닫 혔음을 감지하는 것이 왜 불가능합니까?


최근 질문에 대한 후속 으로, 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
...

I have been using Sockets often, mostly with Selectors, and though not a Network OSI expert, from my understanding, calling shutdownOutput() on a Socket actually sends something on the network (FIN) that wakes up my Selector on the other side (same behaviour in C language). Here you have detection: actually detecting a read operation that will fail when you try it.

In the code you give, closing the socket will shutdown both input and output streams, without possibilities of reading the data that might be available, therefore loosing them. The Java Socket.close() method performs a "graceful" disconnection (opposite as what I initially thought) in that the data left in the output stream will be sent followed by a FIN to signal its close. The FIN will be ACK'd by the other side, as any regular packet would1.

If you need to wait for the other side to close its socket, you need to wait for its FIN. And to achieve that, you have to detect Socket.getInputStream().read() < 0, which means you should not close your socket, as it would close its InputStream.

From what I did in C, and now in Java, achieving such a synchronized close should be done like this:

  1. Shutdown socket output (sends FIN on the other end, this is the last thing that will ever be sent by this socket). Input is still open so you can read() and detect the remote close()
  2. Read the socket InputStream until we receive the reply-FIN from the other end (as it will detect the FIN, it will go through the same graceful diconnection process). This is important on some OS as they don't actually close the socket as long as one of its buffer still contains data. They're called "ghost" socket and use up descriptor numbers in the OS (that might not be an issue anymore with modern OS)
  3. Close the socket (by either calling Socket.close() or closing its InputStream or OutputStream)

As shown in the following Java snippet:

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
}

Of course both sides have to use the same way of closing, or the sending part might always be sending enough data to keep the while loop busy (e.g. if the sending part is only sending data and never reading to detect connection termination. Which is clumsy, but you might not have control on that).

As @WarrenDew pointed out in his comment, discarding the data in the program (application layer) induces a non-graceful disconnection at application layer: though all data were received at TCP layer (the while loop), they are discarded.

1: From "Fundamental Networking in Java": see fig. 3.3 p.45, and the whole §3.7, pp 43-48


I think this is more of a socket programming question. Java is just following the socket programming tradition.

From Wikipedia:

TCP provides reliable, ordered delivery of a stream of bytes from one program on one computer to another program on another computer.

Once the handshake is done, TCP does not make any distinction between two end points (client and server). The term "client" and "server" is mostly for convenience. So, the "server" could be sending data and "client" could be sending some other data simultaneously to each other.

The term "Close" is also misleading. There's only FIN declaration, which means "I am not going to send you any more stuff." But this does not mean that there are no packets in flight, or the other has no more to say. If you implement snail mail as the data link layer, or if your packet traveled different routes, it's possible that the receiver receives packets in wrong order. TCP knows how to fix this for you.

Also you, as a program, may not have time to keep checking what's in the buffer. So, at your convenience you can check what's in the buffer. All in all, current socket implementation is not so bad. If there actually were isPeerClosed(), that's extra call you have to make every time you want to call read.


The underlying sockets API doesn't have such a notification.

The sending TCP stack won't send the FIN bit until the last packet anyway, so there could be a lot of data buffered from when the sending application logically closed its socket before that data is even sent. Likewise, data that's buffered because the network is quicker than the receiving application (I don't know, maybe you're relaying it over a slower connection) could be significant to the receiver and you wouldn't want the receiving application to discard it just because the FIN bit has been received by the stack.


Since none of the answers so far fully answer the question, I'm summarizing my current understanding of the issue.

When a TCP connection is established and one peer calls close() or shutdownOutput() on its socket, the socket on the other side of the connection transitions into CLOSE_WAIT state. In principle, it's possible to find out from the TCP stack whether a socket is in CLOSE_WAIT state without calling read/recv (e.g., getsockopt() on Linux: http://www.developerweb.net/forum/showthread.php?t=4395), but that's not portable.

Java's Socket class seems to be designed to provide an abstraction comparable to a BSD TCP socket, probably because this is the level of abstraction to which people are used to when programming TCP/IP applications. BSD sockets are a generalization supporting sockets other than just INET (e.g., TCP) ones, so they don't provide a portable way of finding out the TCP state of a socket.

There's no method like isCloseWait() because people used to programming TCP applications at the level of abstraction offered by BSD sockets don't expect Java to provide any extra methods.


Detecting whether the remote side of a (TCP) socket connection has closed can be done with the java.net.Socket.sendUrgentData(int) method, and catching the IOException it throws if the remote side is down. This has been tested between Java-Java, and Java-C.

This avoids the problem of designing the communication protocol to use some sort of pinging mechanism. By disabling OOBInline on a socket (setOOBInline(false), any OOB data received is silently discarded, but OOB data can still be sent. If the remote side is closed, a connection reset is attempted, fails, and causes some IOException to be thrown.

If you actually use OOB data in your protocol, then your mileage may vary.


the Java IO stack definitely sends FIN when it gets destructed on an abrupt teardown. It just makes no sense that you can't detect this, b/c most clients only send the FIN if they are shutting down the connection.

...another reason i am really beginning to hate the NIO Java classes. It seems like everything is a little half-ass.


It's an interesting topic. I've dug through the java code just now to check. From my finding, there are two distinct problems: the first is the TCP RFC itself, which allows for remotely closed socket to transmit data in half-duplex, so a remotely closed socket is still half open. As per the RFC, RST doesn't close the connection, you need to send an explicit ABORT command; so Java allow for sending data through half closed socket

(There are two methods for reading the close status at both of the endpoint.)

The other problem is that the implementation say that this behavior is optional. As Java strives to be portable, they implemented the best common feature. Maintaining a map of (OS, implementation of half duplex) would have been a problem, I guess.


This is a flaw of Java's (and all others' that I've looked at) OO socket classes -- no access to the select system call.

Correct answer in 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 */  
}  

Here is a lame workaround. Use SSL ;) and SSL does a close handshake on teardown so you are notified of the socket being closed (most implementations seem to do a propert handshake teardown that is).


The reason for this behaviour (which is not Java specific) is the fact that you don't get any status information from the TCP stack. After all, a socket is just another file handle and you can't find out if there's actual data to read from it without actually trying to (select(2) won't help there, it only signals that you can try without blocking).

For more information see the Unix socket FAQ.


Only writes require that packets be exchanged which allows for the loss of connection to be determined. A common work around is to use the KEEP ALIVE option.


When it comes to dealing with half-open Java sockets, one might want to have a look at isInputShutdown() and isOutputShutdown().

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

반응형