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