Using Server-Side Java Successfully

 
Scott's Solution

Scott Oaks is a Systems Engineer for Sun Microsystems, where he focuses on practical applications of Java technology. He is the co-author, with Henry Wong, of Java Threads. He can be contacted at soaks@sigs.com

THIS MONTH'S QUESTION is about how a Java client application can receive and respond to asynchronous messages from a Java server.

The premise behind this question is something that you're probably already aware of: There is no Java API that allows asynchronous I/O. In fact, there is no asychronicity in Java at all except for by creating different threads; although each thread is completely synchronous, the interleaving of threads leads to asynchronous behavior.

Hence, the standard answer to this problem is simply to open up a socket to the server and read that socket in a new thread. The thread will block until the server sends it data, at which point it can deliver that data to other threads in the program.

This standard approach is often inconvenient to program. There are a lot of low-level threading and communications issues to deal with; and on the client, we'd usually rather deal with event callbacks than worry about the specifics of the threads and their I/O.

There's another technique in our arsenal, however, and that is the RMI callback. Using an RMI callback allows us to deal with asynchronous messages from the server in a style more suited to client-side programming (although, as we'll see, our programming will still not be strictly event-driven).

To use an RMI callback, we must define two RMI interfaces: the usual interface that our server will implement and the interface that our callback will implement. For example, if we expect the server to send us back simple strings, we might define the callback interface like this:

public interface Callback
      	                extends Remote {
	public void msg(String s)
	           throws RemoteException;
}
Among other things, our server interface must provide a mechanism for clients to register instances of this callback, e.g.:
public interface Server
				extends Remote {
	...
	public void register(Callback c)
				throws RemoteException;
}
The server interface might also provide a method by which clients can de-register a callback, or a callback can be automatically de-registered when the client it represents no longer exists. That's the approach that we'll take in our server, a skeleton implementation of which looks like this:
public class ServerImpl
	extends UnicastRemoteObject
	implements Server {
  Vector v = new Vector();
  public void
			register(Callback c) {
	v.addElement(c);
  }
  private void
			broadcast(String s) {
	Enumeration e = v.elements();
	while (e.hasMoreElements()) {
		Callback c = (Callback)
				e.nextElement();
		try { c.msg(s); }
		catch (RemoteException e) {
			v.removeElement(c);
	 	 }
	   }
     }
}
Whenever the server has a message to send, it broadcasts it to all registered clients, removing any who have disconnected. A client that is interested in such messages need only construct a class that implements the Callback interface and pass that object to the server. A skeleton implementation of such a client might look like this:
class ClientCB
		extends UnicastRemoteObject
		implements Callback {
	Client c;
	ClientCB(Client c) {
		this.c = c;
	}
	public void msg(String s)
				throws RemoteException {
		c.handleMessage(s);
	}
}
public class Client
		extends Applet {
	public void init() {
		Server s = (Server)
				Naming.lookup(...);
		ClientCB cb =
				new ClientCB(this);
		s.register(cb);
	}
	void handleMessage(String s) {
		...
	}
}
In this client, the handleMessage() method is called whenever the server has an asynchronous message to send to us. There's a strong similarity between this technique and the event callbacks that make up a typical GUI-based client. Things happen—buttons are pressed, servers send messages—and the appropriate method to handle the event is called. For most purposes, this technique is sufficient. However, we need to consider some of the details of what's going on here. To begin, although it's not readily apparent, there are a number of threads that are involved in the client callback. This isn't surprising because we know that starting a new thread is the only way to obtain asynchronous behavior in Java.

The most important ramification of this is that the callback method—the handleMessage() method in our example—is not called from the event dispatching thread of a GUI-based applet. So even though we've gone to some effort to make programming our callback look just like any other callback, we haven't quite succeeded: Because the handleMessage() method is not called from the event dispatching thread, it cannot directly manipulate any Swing component in the client.

Hence, if the callback needs to access Swing elements, we must rewrite the msg() method of the callback class as follows:
public void msg(String s)
				throws RemoteException {
	SwingUtilities.invokeLater(
		new Runnable() {
			public void run() {
				c.handleMessage(s);
			}
		}
	);
}
This change is not needed if our client does not have a GUI or if its handleMessage() method does not access any GUI components.

In Java 2, this implementation still isn't sufficient because the default Java 2 security manager will interfere with this activity. When the invokeLater() method attempts to post an event to the event queue (so that the handleMessage() method can later be run by the event-dispatching thread), the security manager will notice that an RMI stub method is on the stack. And stub methods by default do not have permission to post an event to the event queue. Hence, on the Java 2 platform, the client code must be written as follows:
public void msg(String s)
				throws RemoteException {
	AccessController.doPrivileged(
		new PrivilegedAction() {
			public Object run() {
				SwingUtilities.
						invokeLater(
					...
				);
				return null;
			}
		}
	);
}
This allows the invokeLater() method to post the necessary event to the event queue so that its target object may be run (assuming, of course, that the client code itself has the necessary permission to post the event, which is the default behavior).

Another important ramification of the fact that the callback method is called from a hidden thread lies in the possibility that the callback method may attempt to invoke a synchronized method that will result in an unusual type of deadlock. This typically happens when the other threads in the client are in the process of a call to the server when the callback is invoked. If the methods involved in the calls to the server are synchronized, and the callback method itself attempts to grab the same synchronization lock, things will deadlock: The call to the server will not complete until the callback completes, and the callback cannot complete because it cannot obtain the correct lock. This can also happen if the methods in the server are synchronized, and the client ends up calling the server from both its default thread and its callback thread.

The easy way to avoid this possibility is to use the code that we've just shown: The callback, rather than actually performing any work, simply posts an event somewhere. Then the other threads of the client can process that event when convenient. If all such processing occurs in the same thread (e.g., the event-dispatching thread), this type of deadlock will be avoided.

With a little bit of care then, the RMI callback offers a convenient way to obtain asynchronous results from a server without having to worry low-level threading or I/O interfaces.

Quantity reprints of this article can be purchased by phone: 717.560.2001, ext.39 or by email: sales@rmsreprints.com.

Featured

Most   Popular
Upcoming Events

AppTrends

Sign up for our newsletter.

Terms and Privacy Policy consent

I agree to this site's Privacy Policy.