ChatGPT解决这个技术问题 Extra ChatGPT

Why is it impossible, without attempting I/O, to detect that TCP socket was gracefully closed by peer?

As a follow up to a recent question, I wonder why it is impossible in Java, without attempting reading/writing on a TCP socket, to detect that the socket has been gracefully closed by the peer? This seems to be the case regardless of whether one uses the pre-NIO Socket or the NIO SocketChannel.

When a peer gracefully closes a TCP connection, the TCP stacks on both sides of the connection know about the fact. The server-side (the one that initiates the shutdown) ends up in state FIN_WAIT2, whereas the client-side (the one that does not explicitly respond to the shutdown) ends up in state CLOSE_WAIT. Why isn't there a method in Socket or SocketChannel that can query the TCP stack to see whether the underlying TCP connection has been terminated? Is it that the TCP stack doesn't provide such status information? Or is it a design decision to avoid a costly call into the kernel?

With the help of the users who have already posted some answers to this question, I think I see where the issue might be coming from. The side that doesn't explicitly close the connection ends up in TCP state CLOSE_WAIT meaning that the connection is in the process of shutting down and waits for the side to issue its own CLOSE operation. I suppose it's fair enough that isConnected returns true and isClosed returns false, but why isn't there something like isClosing?

Below are the test classes that use pre-NIO sockets. But identical results are obtained using 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);
  }
}

When the test client connects to the test server the output remains unchanged even after the server initiates the shutdown of the connection:

connected: true, closed: false
connected: true, closed: false
...
I tought I would mention: The SCTP protocol doesn't have this "problem". SCTP doesn't have a half-closed state as TCP, in other words one side can not continue sending data while the other end has closed its sending socket. That should make things easier.
We have two Mailboxes (Sockets)................................................. The Mailboxes send mail to eachother using the RoyalMail (IP) forget about TCP ................................................. All is fine and dandy, the mailboxes can send/recieve mail to eachother (loads of lag recently) sending while recieving no problem. ............. If one Mailbox was to get hit by a truck and fail.... how would the other Mailbox know? It would have to be notifyed by Royal Mail, who in turn wouldnt know untill it next tryed sending/recieving mail from that failed Mailbox.. ...... erm.....
If you're not going to read from the socket or write to the socket, why do you care? And if you are going to read from the socket or write to the socket, why do an extra check? What's the use case?
Socket.close is not a graceful close.
@immibis It most certainly is a graceful close, unless there is unread data in the socket receive buffer or you have messed with SO_LINGER.

M
Matthieu

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:

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() 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) 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


Java does perform a graceful close. It is not 'brutal'.
@EJP, "graceful disconnection" is a specific exchange taking place at TCP level, where the Client should signal its will to disconnect to the Server, which, in turn, sends remaining data before closing its side. The "send remaining data" part has to be handled by the program (though people wouldn't send anything most of the time). Calling socket.close() is "brutal" in the way it does not respect this Client/Server signaling. Server would be notified of a Client disconnection only when its own socket output buffer is full (because no data are being ack'ed by the other side, which was closed).
See MSDN for more information.
@Matthieu If your application doesn't read all available data, that may be ungraceful at the application layer, but at the TCP transport layer, the data is still received and the connection terminated gracefully. The same would be true if your application read all the data out of the input stream and merely discarded it.
@LeonidUsov That is simply incorrect. Java read() returns -1 at end of stream, and continues to do so however many times you call it. A C read() or recv() returns zero at end of stream, and continues to do so however many times you call it.
d
dty

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.


I don't think so, you can test for states in C code on both windows and linux!!! Java for some reason may not expose some of the stuff, like it would be nice to expose the getsockopt functions that are on windows and linux. In fact, answers below have some linux C code for the linux side.
I do not think that having 'isPeerClosed()' method somehow commits you to call it before every read attempt. You can simply call it only when you explicitly need it. I agree that current socket implementation is not so bad, even though it requires you to write to output stream if you want to find out whether the remote part of socket is closed or not. Because if it is not, you have to handle your written data on the other side as well, and it is simply as large amount of pleasure as sitting on the vertically oriented nail;)
It does mean 'there is no more packet in flight'. The FIN is received after any data in flight. However what it doesn't mean is that the peer has closed the socket for input. You have to **send something* and get a 'connection reset' to detect that. The FIN could have just meant a shutdown for output.
M
Mike Dimmick

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.


In my test example (maybe I should provide one here...) there's no data sent/received over the connection on purpose. So, I'm pretty sure the stack receives the FIN (graceful) or RST (in some non-graceful scenarios). This is also confirmed by netstat.
Sure - if nothing's buffered then the FIN will be sent immediately, on an otherwise empty packet (no payload). After FIN, though, no more data packets are sent by that end of the connection (it will still ACK anything sent to it).
What happens is that the sides of the connection end up in CLOSE_WAIT and FIN_WAIT_2 and it is in this state the isConcected() and isClosed() still don't see that the connection has been terminated.
Thanks for your suggestions! I think I understand the issue better now. I made the question more specific (see third paragraph): why isn't there "Socket.isClosing" to test for half-closed connections?
A
Alexander

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.


Nor can Java provide any extra methods, portably. Maybe they could make an isCloseWait() method which would return false if the platform didn't support it, but how many programmers would be bitten by that gotcha if they only tested on supported platforms?
looks like it could be portable to me...windows has this msdn.microsoft.com/en-us/library/windows/desktop/… and linux this pubs.opengroup.org/onlinepubs/009695399/functions/…
It's not that programmers are used to it; it's that the socket interface is what's useful to programmers. Keep in mind that the socket abstraction is used for more than the TCP protocol.
There's no method in Java like isCloseWait() because it isn't supported on all platforms.
The ident (RFC 1413) protocol allows the server to either keep the connection open after sending a response, or close it without sending any more data. A Java ident client might choose to keep the connection open to avoid the 3-way handshake on the next lookup, but how does it know the connection is still open? Should it just try, respond to any error by reopening the connection? Or is that a protocol design error?
U
Uncle Per

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.


also, it appears I only get and end-of-stream on read (-1 return) when a FIN is present. So this is the only way i can see to detect a shutdown on the read side.
You can detect it. You get EOS when reading. And Java doesn't send FINs. TCP does that. Java does not implement TCP/IP, it just uses the platform implementation.
u
user207421

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.


I suppose you're talking about RFC 793 (faqs.org/rfcs/rfc793.html) Section 3.5 Closing a Connection. I'm not sure it explains the issue, because both sides complete the graceful shutdown of the connection and end up in states where they shouldn't send/receive any data.
Depends. how many FIN you see on the socket? Also, could be a platform specific problem: maybe windows replies each FIN with a FIN and the connection is closed on both ends, but other operating systems may not behave this way, and this is where problem 2 arises
No, unfortunately that's not the case. isOutputShutdown and isInputShutdown is the first thing everyone tries when faced with this "discovery", but both methods return false. I've just tested it on Windows XP and Linux 2.6. The return values of all 4 methods remain the same even after a read attempt
For the record, this is not half-duplex. Half-duplex means only one side can send at s time; both sides can still send.
isInputShutdown and isOutputShutdown test the local end of the connection - they are tests to find out whether you've called shutdownInput or shutdownOutput on this Socket. They don't tell you anything about the remote end of the connection.
B
Blazor

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 */  
}  

You can do the same thing with getsocketop(... SOL_SOCKET, SO_ERROR, ...).
Error file descriptor set won't indicate closed connection. Please read select manual: 'exceptfds - This set is watched for "exceptional conditions". In practice, only one such excetional condition is common: the availability of out-of-band (OOB) data for reading from a TCP socket.' FIN is not OOB data.
You can use 'Selector' class to access 'select()' syscall. It uses NIO though.
There is nothing exceptional about a connection that has been shutdown by the other side.
Many platforms, including Java, do provide access to the select() system call.
D
Dean Hiller

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).


How does one get "notified" of the socket being closed when using SSL in java?
u
user207421

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.


REALbasic sockets (on Mac OS X and Linux) are based on BSD sockets, yet RB manages to give you a nice error 102 when the connection is dropped by the other end. So I agree with the original poster, this should be possible and it's lame that Java (and Cocoa) don't provide it.
@JoeStrout RB can only do that when you do some I/O. There is no API that will give you to status of the connection without doing I/O. Period. This is not a Java deficiency. It is in fact due to the lack of a 'dial tone' in TCP, which is a deliberate design feature.
select() tells you whether there is data or EOS available to be read without blocking. 'Signals that you can try without blocking' is meaningless. If you're in non-blocking mode, you can always try without blocking. select() is driven by data in the socket receive buffer or a pending FIN or space in the socket send buffer.
@EJP What about getsockopt(SO_ERROR)? In fact, even getpeername will tell you if the socket is still connected.
R
Ray

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.


I think an endpoint is allowed to initiate a graceful connection shutdown by sending a packet with FIN set, without writing any payload.
@Alexander Of course it does, but that isn't relevant to this answer, which is about detecting less of connection.
J
JimmyB

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


No. That only tells you what you've called, not what the peer has called.
Care to share your source for that statement?
Care to share your source for the opposite? It's your statement. If you have a proof, let's have it. I assert that you are incorrect. Do the experiment and prove me wrong.
Three years later and no experiment. QED