This article covers the following topics:
To ensure orderly or graceful release of TCP connections is a challenge with
TCP networking applications. Abortive or ungraceful release may result if special
care is not taken. In Java, an unexpected abortive release can manifest itself
by the application receiving a java.net.SocketException
when reading
or writing to the socket. read()
and write()
normally
return a numeric value indicating, respectively, the number of bytes received
or sent. If an exception is received instead, this indicates the connection
has been aborted and also that data may have been lost or discarded. This article
explains what causes socket connections to be aborted and provides tips for
avoiding the situation, except for the case where an application intends to
abort the connection.
First, we need to distinguish the differences between an abortive and an orderly
connection release. To understand this distinction we need to look at what happens
at the TCP protocol level. It is helpful to imagine an established TCP connection
as actually two separate, semi-independent streams of data. If the two peers
are A and B, then one stream delivers data from A to B, and the other stream
from B to A. An orderly release occurs in two stages. First one side (say A)
decides to stop sending data and sends a FIN
message across to
B. When the TCP stack at B's side receives the FIN
it knows that
no more data is coming from A, and whenever B reads all preceding data off the
socket, further reads will return the value -1
to indicate end-of-file.
This procedure is known as the TCP half-close, because only one half of the
connection is closed. Not surprisingly, the procedure for the other half is
exactly the same. B sends a FIN
message to A, who eventually receives
a -1
after reading all preceding data sent by A off the socket.
By contrast, an abortive close uses the RST (Reset) message. If either side issues an RST, this means the entire connection is aborted and the TCP stack can throw away any queued data which has not been sent or received by either application.
So, how do Java applications perform orderly and abortive releases? Let's consider
abortive releases first. A convention that has existed since the days of the
original BSD sockets is that the "linger" socket option can be used
to force an abortive connection release. Either application can call Socket.setLinger
(true, 0) to tell the TCP stack that when this socket is closed, the abortive
(RST) procedure is to be used. Setting the linger option has no immediate effect,
except that when Socket.close()
is called subsequently, the connection
is aborted with an RST message. As we will see shortly, there are other ways
that may cause a connection to be aborted, but this is the simplest way to make
it happen.
The close()
method is also used to perform orderly release, as
well as abortive. So, at its simplest, the difference between an orderly release
and an abortive release could be as little as not setting the linger(0) option,
described above, prior to calling Socket.close()
. Take the example
of two connected peers A and B: If A calls Socket.close()
, a FIN
message is sent from A to B; and when B calls Socket.close()
, a
FIN
is sent from B to A. In fact, the default setting for the linger
option is to not use abortive close; so if two applications terminate their
connection just by using Socket.close()
, then the outcome should
be an orderly release. So what, then, is the problem?
The problem is a slight mismatch between the semantics of Socket.close()
and the TCP FIN
message. Sending a TCP FIN
message
means "I am finished sending", whereas Socket.close()
means "I am finished sending and receiving." When you call Socket.close()
,
clearly it is no longer possible to send data; nor, however, is it possible
to receive data. So what happens, for example, when A attempts an orderly release
by closing the socket, but B continues to send data? This is perfectly allowable
in the TCP specification, because as far as TCP is concerned only one half of
the connection has been closed. But since A's socket is closed there is nobody
to read data if B should continue sending. In this situation A's TCP stack must
send an RST to forcibly terminate the connection.
Another common scenario, which may result in an unintended SocketException
,
is the following: Say A has sent data to B, but B closes the socket without
reading all the data. In this situation, B's TCP stack knows that some data
is effectively lost and it will forcibly terminate with RST rather than use
the orderly FIN
procedure. A will get a SocketException
if it then tries to send or receive data from the socket.
There are a number of simple ways to avoid being surprised by this problem.
shutdownOutput()
. This method has the same effect as close()
in that a FIN
is sent to indicate that this peer has finished
sending, but it is still possible to read from the socket until such time
as the remote peer sends a FIN
and end-of-file is read from the
stream. Then the socket can be closed with Socket.close()
.