How to prevent EOFException when socket is closed

When one needs some very simple java client that reads some data from socket, then it might be a bit annoying to effectively detect that there is no more data to read, aka “End of File”. Here I show an approach to solve some issues to avoid spurious IOExceptions.

Consider two Java applications that exchange data over a socket, lets call them Alice and Bob. On my example, Bob sends triples of integers, and when he does not have any more triples to send, he takes initiative to close the socket. Here I only show a simple code for Alice.

Socket socket = new Socket(host, port);
DataOutputStream os = new DataOutputStream(socket.getOutputStream());
DataInputStream is = new DataInputStream(socket.getInputStream());
while (! socket.isClosed()) {
   // read data
   int a = is.readInt();
   int b = is.readInt();
   int c = is.readInt();
   // ... do domething.
}
os.close();
is.close();
socket.close();

Since Alice is waiting on a blocking read, she will receive an EOFException whenever the socket is closed. Unfortunately, the EOFException is not very meaningful: how could she distinguish a connection closed by Bob or a connection that was dropped by a communication failure? Just wrapping the readInt into a try…catch would not help, since the EOFException is also raised if truncated data is received.

Thefore I investigated an approach where I could identify a closed socket before attempting to read the integer. If Alice’s socket gets closed before reading the first integer from the triple, then she assumes that Bob closed the socket because he does not want to send any more triples. If the socket gets closed while reading any integer, then Alice reports and error.

Just querying the method .isAvailable() was not the solution as one would expect from the javadocs. Calling Socket.isClosed() to guarantee that the socket is still open before starting to read did not help either, since it returned false even though the next read would immediately raise EOFException.

Instead, I discovered another less obvious solution that worked well. I record the current read position at the inputstream and try to read a byte. If the read fails with EOFException, then I know that the socket was closed and that this exception is not another arbitrary communication failure. Else, I begin reading the three integers. If a EOFException is raised at that point, then I know that the socket was closed due to communication failure.

Socket socket = new Socket(host, port);
DataOutputStream os = new DataOutputStream(socket.getOutputStream());
DataInputStream is = new DataInputStream(socket.getInputStream());

while (! socket.isClosed()) {
   // test stream
   is.mark(1);
   if (is.read() == -1) break;
    is.reset();
    // read data
    int a = is.readInt();
    int b = is.readInt();
    int c = is.readInt();
    // ... do domething.
}

os.close();
is.close();
socket.close();

Many of you would complain that Bob should first close the socket’s outputstream, wait for Alice to acknowledge by closing her outputstream and only then both should close the socket.  However not all applications behave on the correct way.

An even better approach would be to establish a especial value to signal that Bob does not want to send any more triples. After receiving this special value, Alice may ignore any EOFException from the socket. But even then, I prefer to empty the mark/reset approach to avoid messing the log file.

One Response to How to prevent EOFException when socket is closed

  1. Steven Arzt says:

    That’s really great, I was looking for a trick like this for quite a while.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: