|
Articles Index
By David Reilly; Reprinted from JavaWorld
(October 1999)
Many programmers dread the thought of handling network timeouts. A common fear
is that a simple, single-threaded network client without timeout support will
balloon into a complex multithreaded nightmare, with separate threads needed to
detect network timeouts, and some form of notification process at work between
the blocked thread and the main application. While this is one option for
developers, it is not the only one. Dealing with network timeouts need not be a
difficult task, and in many cases you can completely avoid writing code for
additional threads. This article shows how esy it is to write code that handles network timeouts gracefully.
When working with network connections, or any type of I/O device, there are two
classifications of operations:
- Blocking
operations: Read or write stalls, operation waits until I/O device is
ready.
- Nonblocking
operations: Read or write attempt is made, operation aborts if I/O
device is not ready.
Java networking is, by default, a form of
blocking I/O. Thus, when a Java networking application reads from a socket
connection, it will generally wait indefinitely if there is no immediate
response. If no data is available, the program will keep waiting, and no further
work can be done. One solution, which solves the problem but introduces a little
extra complexity, is to have a second thread perform the operation; this way, if
the second thread becomes blocked the application can still respond to user
commands, or even terminate the stalled thread if necessary.
This solution is often employed, but there is a much simpler alternative. Java
technology also supports nonblocking network I/O, which can be activated on any
Socket, ServerSocket, or DatagramSocket.
It is possible to specify the maximum length of time that a read or write
operation will stall before returning control back to the application. For
network clients, this is the easiest solution and offers simpler, more
manageable code.
The only drawback to nonblocking network I/O under the Java platform is that it
requires an existing socket. Thus, while this method is perfect for normal read
or write operations, a connect operation can stall for a much longer period,
since there is no method for specifying a timeout period for connect operations.
Many applications require this ability; you can, however, easily avoid the extra
work of writing additional code. I've written a small class that allows you to
specify a timeout value for a connection. It uses a second thread, but the
internal details are abstracted away. This approach works well, as it provides a
nonblocking I/O interface, and the details of the second thread are hidden from
view.
Nonblocking Network I/O
The simplest way of doing something often turns out to be the best way. While it
is sometimes necessary to use threads and blocking I/O, in the majority of cases
nonblocking I/O lends itself to a far clearer and more elegant solution. With
only a few lines of code, you can incorporate timeout supports for any socket
application. Don't believe me? Read on.
When Java 1.1 was released, it included API changes to the java.net
package that allowed programmers to specify socket options. These options give
programmers greater control over socket communication. One option in particular,
SO_TIMEOUT, is extremely useful, because it allows programmers to
specify the amount of time that a read operation will block. We can specify a
short delay, or none at all, and make our networking code nonblocking.
Let's take a look at how this works. A new method, setSoTimeout ( int
) has been added to the following socket classes:
java.net.Socket
java.net.DatagramSocket
java.net.ServerSocket
This method allows us to specify a maximum timeout length, in
milliseconds, that the following network operations will block:
ServerSocket.accept()
SocketInputStream.read()
DatagramSocket.receive()
Whenever one of these methods is called, the clock starts ticking. If the
operation is not blocked, it will reset and only restart once one of these
methods is called again; as a result, no timeout can ever occur unless you
perform a network I/O operation. The following example shows just how easy it
can be to handle timeouts, without resorting to multiple threads of execution:
// Create a datagram socket on port
//2000 to listen for incoming UDP packets
DatagramSocket dgramSocket =
new DatagramSocket ( 2000 );
// Disable blocking I/O operations,
//by specifying a five second timeout
dgramSocket.setSoTimeout ( 5000 );
|
Assigning a timeout value prevents our network operations from blocking
indefinitely. At this point, you're probably wondering what will happen when a
network operation times out. Rather than returning an error code, which might
not always be checked by developers, a
java.io.InterruptedIOException is thrown. Exception handling is an
excellent way of dealing with error conditions, and allows us to separate our
normal code from our error-handling code. Besides, who religiously checks every
return value for a null reference? By throwing an exception, developers are
forced to provide a catch handler for timeouts.
The following code snippet shows how to handle a timeout operation when reading
from a TCP socket:
// Set the socket timeout for ten seconds
connection.setSoTimeout (10000);
try
{
// Create a DataInputStream for
//reading from socket
DataInputStream din = new DataInputStream (
connection.getInputStream());
// Read data until end of data
for (;;)
{
String line = din.readLine();
if (line != null)
System.out.println (line);
else
break;
}
}
// Exception thrown when network timeout occurs
catch (InterruptedIOException iioe)
{
System.err.println (
"Remote host timed out during read operation");
}
// Exception thrown when general
//network I/O error occurs
catch (IOException ioe)
{
System.err.println ("Network I/O error -
" + ioe);
}
|
With only a few extra lines of code for a try {} catch block, it's
extremely easy to catch network timeouts. An application can then respond to the
situation without stalling itself. For example, it could start by notifying the
user, or by attempting to establish a new connection. When using datagram
sockets, which send packets of information without guaranteeing delivery, an
application could respond to a network timeout by resending a packet that had
been lost in transit. Implementing this timeout support takes very little time
and leads to a very clean solution. Indeed, the only time that nonblocking I/O
is not the optimal solution is when you also need to detect timeouts on connect
operations, or when your target environment does not support Java 1.1.
Timeout Handling on Connect Operations
If your goal is to achieve complete timeout detection and handling, then you'll
need to consider connect operations. When creating an instance of
java.net.Socket, an attempt to establish a connection is made. If
the host machine is active, but no service is running on the port that is
specified in the java.net.Socket constructor, a
ConnectionException will be thrown and control will return to the
application. However, if the machine is down, or if there is no route to that
host, the socket connection will eventually time out on its own much later. In
the meantime, your application remains frozen, and there is no way to change the
timeout value.
Though the socket constructor call will eventually return, it introduces a
significant delay. One way of dealing with this problem is to employ a second
thread, which will perform the potentially blocking connect, and to continually
poll that thread to see if a connection has been established.
This does not, however, always lead to an elegant solution. Yes, you could
convert your network clients into multithreaded applications, but often the
amount of extra work required to do this is prohibitive. It makes the code more
complex, and when writing only a simple network application, the amount of
effort required is difficult to justify. If you write a lot of network
applications, you'd find yourself reinventing the wheel frequently. There is,
however, a simpler solution.
I've written a simple, reusable class that you can use in your own applications.
The class generates a TCP socket connection without stalling for long time
periods. You simply call a getSocket method, specifying the
hostname, port, and timeout delay, and receive a socket. The following example
shows a connection request:
// Connect to a remote server by hostname,
//with a four second timeout
Socket connection = TimedSocket.getSocket(
"server.my-network.net", 23, 4000);
If all goes well, a socket will be returned, just like the standard
java.net.Socket constructors. If the connection cannot be
established before your specified timeout occurs, the method will stop, and will
throw an java.io.InterruptedIOException,
just as other socket-read operations would when a timeout has been specified
using a setSoTimeout method. Pretty easy, huh?
Encapsulating multithreaded network code into a single class
While the TimedSocket class is a useful component in itself, it's
also a very good learning aid for understanding how to deal with blocking I/O.
When a blocking operation is performed, a single-threaded application will
become blocked indefinitely. If multiple threads of execution are used, however,
only one thread need stall; the other thread can continue to execute. Let's take
a look at how the TimedSocket class works.
When an application needs to connect to a remote server, it invokes the
TimedSocket.getSocket() method and passes details of the remote
host and port. The getSocket() method is overloaded, allowing both
a String hostname and an InetAddress to be specified.
This range of parameters should be sufficient for the majority of socket
operations, though custom overloading could be added for special
implementations. Inside the getSocket() method, a second thread is
created.
The imaginatively named SocketThread will create an instance of
java.net.Socket, which can potentially block for a considerable
amount of time. It provides accessor methods to determine if a connection has
been established or if an error has occurred (for example, if
java.net.SocketException was thrown during the connect).
While the connection is being established, the primary thread waits until a
connection is established, for an error to occur, or for a network timeout.
Every hundred milliseconds, a check is made to see if the second thread has
achieved a connection. If this check fails, a second check must be made to
determine whether an error occurred in the connection. If not, and
the connection attempt is still continuing, a timer is incremented
and, after a small sleep, the connection will be polled again.
This method makes heavy use of exception handling. If an error occurs, then this
exception will be read from the SocketThread instance, and it will
be thrown again. If a network timeout occurs, the method will throw a
java.io.InterruptedIOException.
The following code snippet shows the polling mechanism and error-handling code.
for (;;)
{
// Check to see if a connection is established
if (st.isConnected())
{
// Yes ... assign to sock variable,
//and break out of loop
sock = st.getSocket();
break;
}
else
{
// Check to see if an error occurred
if (st.isError())
{
// No connection could be established
throw (st.getException());
}
try
{
// Sleep for a short period of time
Thread.sleep ( POLL_DELAY );
}
catch (InterruptedException ie) {}
// Increment timer
timer += POLL_DELAY;
// Check to see if time limit exceeded
if (timer > delay)
{
// Can't connect to server
throw new InterruptedIOException
("Could not connect for " + delay +
" milliseconds");
}
}
}
|
Inside the Blocked Thread
While the connection is regularly polled, the second thread attempts to create a
new instance of java.net.Socket. Accessor methods are provided to
determine the state of the connection, as well as to get the final socket
connection. The SocketThread.isConnected() method returns a boolean
value to indicate whether a connection has been established, and the
SocketThread.getSocket() method returns a Socket.
Similar methods are provided to determine if an error has occurred, and to
access the exception that was caught.
All these methods provide a controlled interface to the
SocketThread instance, without allowing external modification of
private member variables. The following code example shows the thread's
run() method. When, and if, the socket constructor returns a
Socket, it will be assigned to a private member variable, to which
the accessor methods provide access. The next time a connection state is
queried, using the SocketThread.isConnected() method, the socket
will be available for use. The same technique is used to detect errors; if a
java.io.IOException is caught, it will be stored in a
private member, which can be accessed via the isError()
and getException() accessor methods.
public void run()
{
// Socket used for establishing a connection
Socket sock = null;
try
{
// Was a string or an inet specified
if (m_host != null)
{
// Connect to a remote host - BLOCKING I/O
sock = new Socket (m_host, m_port);
}
else
{
// Connect to a remote host - BLOCKING I/O
sock = new Socket (m_inet, m_port);
}
}
catch (IOException ioe)
{
// Assign to our exception member variable
m_exception = ioe;
return;
}
// If socket constructor returned without error,
// then connection finished
m_connection = sock;
}
|
Using TimedSocket in applications
The TimedSocket class was designed to make timeout handling simpler
and to conform with the timeout handling mechanism offered for nonblocking I/O.
Both TimedSocket and the read operations controlled by the
setSoTimeout method throw the same exception, so handling one
timeout mechanism involves the same amount of code as handling the other. To
demonstrate the use of TimedSocket in action, I've written a simple
socket-based application that uses both timeout handling techniques to fetch a
Web page. Though support for HTTP is supported by the URL and
URLConnection classes, these classes do not allow a timeout value
to be specified. Thus, implementing HTTP using sockets is a task that developers
may face and a valuable learning exercise. Support for timeouts is particularly
important when creating systems that must establish connections frequently, such
as browsers and Web crawlers.
Let's examine the HTTP application. The first thing you should notice is that
all of the code is enclosed within a try {} catch block, and that
several different exceptions are caught. The most important of these, for our
purposes, is the java.io.InterruptedIOException.
TimedSocket is used to create the socket connection, and a timeout
for read operations is also specified. There could be several causes for this
being thrown -- a timeout during the connect operation, for instance, or a
timeout during a read operation. If you require separate handler code for these
two types of timeouts, there is also the option of using two different try
{} catch blocks. Also, although it is always good practice to catch
specific exceptions, one could combine all the catch blocks into a single
java.io.IOException handler, for brevity.
try
{
// Parse URL, to extract details
dataURL = new URL(args[0]);
remote_address = dataURL.getHost();
remote_path = dataURL.getFile();
remote_port = dataURL.getPort();
// Check port, default is 80
if (remote_port == -1) remote_port = 80;
// Connect to remote service
Socket connection = TimedSocket.getSocket (
remote_address,
remote_port, 4000);
// Set the socket timeout for ten seconds
connection.setSoTimeout (10000);
......
}
catch (InterruptedIOException iioe)
{
System.err.println ("
Remote host timed out");
}
catch (MalformedURLException mue)
{
System.err.println ("Invalid URL");
}
catch (IOException ioe)
{
System.err.println ("
Network I/O error - " + ioe);
}
|
SUBHEAD: Running the sample application
Running the sample application is quite straightforward. Simply pass it the URL
of a Web page, and it will fetch it for you (though no support for proxies or
firewalls is included). However, the real test will be to pass it the URL of a
machine that will cause a timeout. For example, on my local area network, I can
pass the application the URL of a machine that is turned off. To do this on a
local machine, I might type:
java HTTPDemo http://192.168.0.5/
or
jview HTTPDemo http://192.168.0.5/
The application will attempt to connect, and will terminate after a ten-second
connection timeout. You can also try a host machine that does not have a server
running. Rather than waiting for a full ten seconds, the
TimedSocket.getSocket() method will return almost immediately and
throw the appropriate exception. Once the connection has been established, it
will also detect network timeouts when reading data from the remote server, by
specifying a timeout value with java.net.Socket.setSoTimeout(int)
and using nonblocking I/O.
Conclusion
Under ideal testing conditions, network timeouts don't occur, and the amount of
effort that has to be expended to handle timeouts makes it tempting to ignore
them. Users running applications over networks of varying degrees of quality
quickly notice a lack of timeout support, however. For this reason, it is
critical that attention is given to the problem of network timeouts during
application development, for both network clients and network servers.
The introduction of support for socket options in the Java platform has made
timeout handling far simpler, now that nonblocking I/O is supported. However,
nonblocking I/O is not always available for every network operation and is not
supported on earlier Java Virtual Machines1. The
solution, in this case, is to make use of multiple threads of execution. If done
incorrectly, however, threads can make the code messy and more complex to
maintain. But in most cases, the details of such threads can be carefully
encapsulated within a class (as has been done in the case of
TimedSocket) to provide a nonblocking I/O interface. The added
bonus is that this class can be reused in future projects, which can result in
significant savings of time. With the techniques discussed in this article, and
the sample class for connection timeouts, writing code to handle network
timeouts can become a simple part of your everyday programming.
Resources
David
Reilly's Java Network Programming FAQanswers commonly asked questions
about network programming in Java.
Socket
options in Java (JDK 1.1 documentation)offers an overview of the
socket option support in Java 1.1.
Download
the complete source for this article as a zip file.

Reprinted with permission from the September 1999 edition of JavaWorld magazine. Copyright Web
Publishing Inc., an IDG Communications company. Register for editorial e-mailalerts
About the Author
David Reilly is a Sun Certified Java Programmer and
freelance technical writer. He completed a bachelor's degree in Software
Engineering at Bond University, Australia, where he is currently completing an
honors degree in the same subject. David has been programming in the Java
programming language since 1996, and is passionate about the language and the opportunities it offers for network programming and distributed
systems development. His writings appear in numerous online and print
publications, as well as at his Web site, David Reilly.
_______
1 As used on this web site, the terms "Java
Virtual Machine" or "JVM" mean a virtual machine for the Java
platform.
|
|