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.

1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more