Simple handling of network timeouts

Learn just how easy it is to prevent stalled clients and servers

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.

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 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 Java 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.

Diagram of TimedSocket

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

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 Java 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 machines. 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.

none 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 Java since 1996, and is passionate about the Java 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, http://www.davidreilly.com/.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies