views:

3248

answers:

8

As a follow up to a recent question (http://stackoverflow.com/questions/151590/java-how-do-detect-a-remote-side-socket-close), 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
...
+4  A: 

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.

Mike Dimmick
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.
Alexander
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).
Mike Dimmick
What happens is that the sides of the connection end up in <code>CLOSE_WAIT</code> and <code>FIN_WAIT_2</code> and it is in this state the <code>isConcected()</code> and <code>isClosed()</code> still don't see that the connection has been terminated.
Alexander
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?
Alexander
+2  A: 

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

Lorenzo Boccaccia
I suppose you're talking about RFC 793 (http://www.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.
Alexander
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
Lorenzo Boccaccia
I only see one FIN sent by the server-side (the one that initiates the shutdown). I've updated the question with the TCP states. The behavior seems to be indentical between Windows XP and Linux 2.6.
Alexander
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?
Alexander
because there is isOutputShutdown() and isInputShutdown(); don't know if those are fully working, however. (as usual they depends of the actual implementation of the standard inside the OS, and inside the JVM)
Lorenzo Boccaccia
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
Alexander
question (this problem bite me time ago, and I normally work around it, but now that we are there...): did you tried out other jvm (kaffe, gcj, etc)?
Lorenzo Boccaccia
Ran a quick test: same story in Blackdown 1.4.1 on Linux 2.6
Alexander
+5  A: 

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

w:

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

eed3si9n
+2  A: 

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.

WMR
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.
Joe Strout
+1  A: 

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.

Alexander
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?
ephemient
A: 

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.

Ray
I think an endpoint is allowed to initiate a graceful connection shutdown by sending a packet with FIN set, without writing any payload.
Alexander
+1  A: 
Joshua
A: 

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.