116

I am running into some issues with the Java socket API. I am trying to display the number of players currently connected to my game. It is easy to determine when a player has connected. However, it seems unnecessarily difficult to determine when a player has disconnected using the socket API.

Calling isConnected() on a socket that has been disconnected remotely always seems to return true. Similarly, calling isClosed() on a socket that has been closed remotely always seems to return false. I have read that to actually determine whether or not a socket has been closed, data must be written to the output stream and an exception must be caught. This seems like a really unclean way to handle this situation. We would just constantly have to spam a garbage message over the network to ever know when a socket had closed.

Is there any other solution?

Nayuki
  • 17,911
  • 6
  • 53
  • 80
Dan Brouwer
  • 1,161
  • 2
  • 9
  • 3

9 Answers9

216

There is no TCP API that will tell you the current state of the connection. isConnected() and isClosed() tell you the current state of your socket. Not the same thing.

  1. isConnected() tells you whether you have connected this socket. You have, so it returns true.

  2. isClosed() tells you whether you have closed this socket. Until you have, it returns false.

  3. If the peer has closed the connection in an orderly way

    • read() returns -1
    • readLine() returns null
    • readXXX() throws EOFException for any other XXX.

    • A write will throw an IOException: 'connection reset by peer', eventually, subject to buffering delays.

  4. If the connection has dropped for any other reason, a write will throw an IOException, eventually, as above, and a read may do the same thing.

  5. If the peer is still connected but not using the connection, a read timeout can be used.

  6. Contrary to what you may read elsewhere, ClosedChannelException doesn't tell you this. [Neither does SocketException: socket closed.] It only tells you that you closed the channel, and then continued to use it. In other words, a programming error on your part. It does not indicate a closed connection.

  7. As a result of some experiments with Java 7 on Windows XP it also appears that if:

    • you're selecting on OP_READ
    • select() returns a value of greater than zero
    • the associated SelectionKey is already invalid (key.isValid() == false)

    it means the peer has reset the connection. However this may be peculiar to either the JRE version or platform.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • 26
    It is hard to believe that the TCP protocol, which is connection oriented, can't even know the status of its connection... Do the guys that come up with this protocols drive their cars with eyes closed? – PedroD Aug 11 '14 at 12:42
  • 41
    @PedroD On the contrary: it was deliberate. Previous protocol suites such as SNA had a 'dial tone'. TCP was designed to survive a nuclear war, and, more trivially, router downs and ups: hence the complete absence of anything like a dial tone, connection status, etc.; and it is also why TCP keepalive is described in the RFCs as a controversial feature, and why it is always off by default. TCP is still with us. SNA? IPX? ISO? Not. They got it right. – user207421 Aug 11 '14 at 12:50
  • 5
    I don't believe that's a good excuse for hiding this information from us. Knowing that the connection was lost doesn't necessarily mean that the protocol is less fault resistant, it always depend on what we do with that knowledge... For me the method isBound and isConnected from java are pure mock methods, they have no use, but express the need for a connection event listener... But I reiterate: knowing that the connection was lost does not make the protocol worse. Now if those protocols you're saying killed the connection as soon as they detected it was lost, that is a different story. – PedroD Aug 11 '14 at 13:02
  • I add more: The keep alive functionality is needed, it is a requirement, but only for knowing the connection status. Now if you say that keep alive sucks because when it detects a loss of connection it closes the socket, then I agree with you. But it is not a matter of knowing the connection was lost, it is the action they are taking based on that information that sucks. – PedroD Aug 11 '14 at 13:05
  • Also, even if keep alive didnt close the socket (which I dont agree that it should do that), if you try to send or receive anything you wont be able since the connection was lost. So in the end it really doesn't matter. The only important thing here is that you should have the correct information and not a "I-wish-i-had-that-information-java-method()" like isConnected(). In a connection oriented protocol it is mandatory to know the connection status, no matter what actions we take according to the given information. – PedroD Aug 11 '14 at 13:08
  • 29
    @Pedro You don't understand. It isn't an 'excuse'. *There is no information to withhold.* There is no dial tone. TCP *doesn't know* whether the connection has failed *until you try to do something to it.* That was the fundamental design criterion. – user207421 Aug 11 '14 at 22:37
  • 2
    @EJP thanks for your answer. Do you perhaps know how to check if a socket got closed without "blocking"? Using read or readLine is going to block. Is there a way to realize if the other socket got closed with the available method, it seems to return `0` instead of `-1`. – insumity Oct 14 '14 at 14:07
  • 1
    @foobar There isn't such a way in `java.net.` And there's no reason why `available()` should return -1. Nothing about it in the Javadoc. Again this is no oversight. There is no way to know at the BSD Sockets API level either. – user207421 Mar 25 '15 at 19:13
  • 1
    @PedroD NB Keep-alive is an optional feature of TCP implementations, whatever you may think about that, and a keep-alive failure *doesn't* close the socket. It resets the connection. The socket is still open. – user207421 Mar 25 '15 at 19:15
  • @EJP but do you think a heartbeat is the way to go then? What's the solution? – El Mac Mar 27 '16 at 12:44
  • 2
    @ElMac I don't believe in heartbeats. I note that HTTP, the most widely used application protocol on the planet, doesn't have one. I believe in connection pooling, read timeouts, and correct exception and EOS handling. – user207421 May 11 '16 at 13:42
  • @EJP: Since `IsClosed` returns false even after connection is lost with peer. Should I call `close()` on socket too after peer disconnects? – user963241 Jun 12 '17 at 15:24
  • 2
    @user963241 Of course you should. It's closeable: close it. – user207421 Jun 25 '17 at 19:39
  • @user207421Gets closest to the best explanation. As much as we like to think of TCP socket connections as persistent things, nothing actually persists. They're just packets being sent between two peers over a network according to agreed rules. The socket is still "connected" as long as both participants 1) agree that it is and 2) are physically able to transmit/receive (with the second point arguably being optional; neither side is obligated to implement read/write timeouts). – aroth Aug 07 '19 at 04:40
  • @aroth Both sides are obligated to implement write timeouts and retries. – user207421 Oct 31 '19 at 00:46
  • @insumity to check if socket was closed without blocking in Java: socket.setSoTimeout(1), then `socket.read();`, if it throws SocketTimeoutException, then socket is still open, I have found this trick at https://github.com/albfernandez/commons-httpclient-3/blob/master/src/java/org/apache/commons/httpclient/HttpConnection.java#L504 – Viktor Mukhachev Apr 10 '20 at 10:59
  • @ViktorMukhachev This is not 'without blocking'. If it does anything other than throw `SocketException: socket closed` the socket is open. The *connection* may be closed, in which case the read will return -1, or it may return 0..255, which is data, or the connection may have failed, in which case the timeout will happen. – user207421 Oct 18 '20 at 05:44
  • Could you please explain Point 6: Why is SocketException a "programming error on your part"? If the server tries reading data with, e.g., readLine and blocks, this exception occurs if a client closes the connection. Seems to be a valid approach to detect, whether a client closed the connection – Anonymous Apr 04 '22 at 10:34
  • @Anonymous 'This error is thrown': no it isn't. The server's `readLine()` returns null in this case. – user207421 Apr 26 '23 at 23:48
13

It is general practice in various messaging protocols to keep heartbeating each other (keep sending ping packets) the packet does not need to be very large. The probing mechanism will allow you to detect the disconnected client even before TCP figures it out in general (TCP timeout is far higher) Send a probe and wait for say 5 seconds for a reply, if you do not see reply for say 2-3 subsequent probes, your player is disconnected.

Also, related question

Community
  • 1
  • 1
Kalpak Gadre
  • 6,285
  • 2
  • 24
  • 30
  • 6
    It is 'general practice' in *some* protocols. I note that HTTP, the most used application protocol on the planet, does not have a PING operation. – user207421 Nov 02 '16 at 09:22
  • 3
    @user207421 because HTTP/1 is one shot protocol. You send request, you receive response. And you are done. Socket is closed. The Websocket extension does have PING operation, so does HTTP/2. – Michał Zabielski Sep 17 '18 at 12:25
  • I recommended heartbeating with a single byte if possible :) – Stefan Reich Dec 08 '18 at 13:48
  • @MichałZabielski It is because the designers of HTTP didn't specify it. You don't know why not. HTTP/1.1 is not a one shot protocol. The socket is not closed: HTTP connections are persistent by default. FTP, SMTP, POP3, IMAP, TLS, ... don't have heartbeats. – user207421 Jan 28 '19 at 15:58
  • 2
    Aren't heartbeat and ping two different things? When you send a ping you expect a pong, but with heartbeats you don't expect anything. – DFSFOT Dec 30 '20 at 15:35
2

I see the other answer just posted, but I think you are interactive with clients playing your game, so I may pose another approach (while BufferedReader is definitely valid in some cases).

If you wanted to... you could delegate the "registration" responsibility to the client. I.e. you would have a collection of connected users with a timestamp on the last message received from each... if a client times out, you would force a re-registration of the client, but that leads to the quote and idea below.

I have read that to actually determine whether or not a socket has been closed data must be written to the output stream and an exception must be caught. This seems like a really unclean way to handle this situation.

If your Java code did not close/disconnect the Socket, then how else would you be notified that the remote host closed your connection? Ultimately, your try/catch is doing roughly the same thing that a poller listening for events on the ACTUAL socket would be doing. Consider the following:

  • your local system could close your socket without notifying you... that is just the implementation of Socket (i.e. it doesn't poll the hardware/driver/firmware/whatever for state change).
  • new Socket(Proxy p)... there are multiple parties (6 endpoints really) that could be closing the connection on you...

I think one of the features of the abstracted languages is that you are abstracted from the minutia. Think of the using keyword in C# (try/finally) for SqlConnection s or whatever... it's just the cost of doing business... I think that try/catch/finally is the accepted and necesary pattern for Socket use.

Scottley
  • 91
  • 5
2

I faced similar problem. In my case client must send data periodically. I hope you have same requirement. Then I set SO_TIMEOUT socket.setSoTimeout(1000 * 60 * 5); which is throw java.net.SocketTimeoutException when specified time is expired. Then I can detect dead client easily.

hurelhuyag
  • 1,691
  • 1
  • 15
  • 20
1

I think this is nature of tcp connections, in that standards it takes about 6 minutes of silence in transmission before we conclude that out connection is gone! So I don`t think you can find an exact solution for this problem. Maybe the better way is to write some handy code to guess when server should suppose a user connection is closed.

Ehsan Khodarahmi
  • 4,772
  • 10
  • 60
  • 87
1

As @user207421 say there is no way to know the current state of the connection because of the TCP/IP Protocol Architecture Model. So the server has to notice you before closing the connection or you check it by yourself.
This is a simple example that shows how to know the socket is closed by the server:

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 !");
ucMedia
  • 4,105
  • 4
  • 38
  • 46
1

Here you are another general solution for any data type.

int offset = 0;
byte[] buffer = new byte[8192];

try {
    do {
        int b = inputStream.read();

        if (b == -1)
           break;

        buffer[offset++] = (byte) b;

        //check offset with buffer length and reallocate array if needed
    } while (inputStream.available() > 0);
} catch (SocketException e) {
    //connection was lost
}

//process buffer
Thomas.
  • 91
  • 2
  • 6
-2

Thats how I handle it

 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);
         }    } 

if the result.code == null

Petar Ceho
  • 114
  • 2
  • 7
-3

On Linux when write()ing into a socket which the other side, unknown to you, closed will provoke a SIGPIPE signal/exception however you want to call it. However if you don't want to be caught out by the SIGPIPE you can use send() with the flag MSG_NOSIGNAL. The send() call will return with -1 and in this case you can check errno which will tell you that you tried to write a broken pipe (in this case a socket) with the value EPIPE which according to errno.h is equivalent to 32. As a reaction to the EPIPE you could double back and try to reopen the socket and try to send your information again.

MikeK
  • 13
  • The `send()` call will return -1 only if the outgoing data got buffered for long enough for the send timers to expire. It almost certainly won't happen on the first send after the disconnect, due to buffering at both ends and the asynchronous nature of `send()` under the hood. – user207421 Apr 19 '19 at 12:58