|
Comments
Did you read today's front page stories & breaking news?
SYS-CON.TV
|
General Java Distributed Notification with Java RMI
Server-to-client communication
By: Michael Remijan
Nov. 5, 2004 12:00 AM
Java's implementation of Remote Method Invocation (RMI) is easy to use and powerful. Java makes setting up an RMI server an almost trivial task because the JVM handles complex tasks such as networking and object serialization. Once running, connecting client applications to the RMI server is also a breeze. There are numerous examples and how-to articles for client-to-server communication (http://java.sun.com/developer/onlineTraining/rmi), but what about the other way? Is it possible for an RMI server to actively communicate with all the clients that are connected to it without the client initiating the conversation? In other words, is distributed notification possible? The purpose of this article is to demonstrate that yes, it is possible. (The source code for this article can be downloaded from www.sys-con.com/jdj/sourcec.cfm.) RMI applications are driven by the need to have centrally located business logic used by multiple clients at the same time. This presents a number of problems, such as clients not knowing what other clients are doing. A simple example that immediately demonstrates this problem is caching data on the client side for fast reuse. When one client updates information, all the other clients are now working with old data. The typical - and not the best - solution for this problem is to have each client poll the RMI server for updates on a regular basis. First, constant polling puts unnecessary strain on the RMI server because it's forced to handle additional network traffic. Second, there will be a period of time when the client is working with old data. In the J2EE world, Java Messaging Service (JMS) solves this problem the best. Assuming all data updates are done at a central location, a JMS message can be easily sent to all registered clients when an update occurs. When a client receives the message, the cache can be refreshed. An alternative solution is to keep plain RMI and follow the event notification model similar to AWT/Swing. In those models, an object implements a simple listener interface and is then added to an appropriate event notification list. When that event occurs, every object in the list becomes notified of the event. The object takes whatever action necessary in response to the event. Such a model obviously is a better solution than polling, and solves the problems that polling has - there is no unnecessary network traffic and clients are notified instantly when a change in the data has occurred. Applying this model to RMI is not trivial. Consider an RMI-based time server as an example, where client applications register with an RMI server. Every second the RMI server fires an event to inform the clients of the new time. To successfully do this, first define a remote interface named TimeServices.
import java.rmi.*;
public interface TimeServices extends Remote
{
public void addTimeMonitor(TimeMonitor tm)
throws RemoteException;
}
The TimeServices interface declares the methods used for client-to-server communication (see Figure 1). The Remote interface has one method, addTimeMonitor (TimeMonitor), which is used by RMI client applications to register themselves with the RMI server. This is analogous to the setActionListener() methods in Swing only instead of implementing the ActionListener interface, an object that implements the TimeMonitor interface is needed. Just as the ActionListener interface serves to link an event with the application code that processes the event, the TimeMonitor Interface serves as a way for the RMI Server to talk back to its clients.
import java.rmi.*;
import java.util.Date;
public interface TimeMonitor extends Remote
{
public void setTime(Date d)
throws RemoteException;
}
Because of this, TimeMonitor is a remote interface that allows server-to-client communication (see Figure 2). Implementing the TimeMonitor interface becomes a challenge, because it's not possible for every client to create its own implementation and use that implementation to register with the RMI server. When the client tries to register with the server by calling the addTimeMonitor() method, the TimeMonitor parameter serializes to a byte stream and is transmitted over the network to the server. The server then needs to know how to deserialize the stream and reconstitute the object. Even though syntactically the TimeMonitor interface is needed to compile, when the server is actually running it needs the implementation of the TimeMonitor interface in the CLASSPATH in order to deserialize the TimeMonitor objects correctly. If the implementation was not in the CLASSPATH, a ClassNotFoundException is thrown when the RMI server attempts to deserialize the stream. To get around this problem, the client must use an implementation of TimeMonitor that the RMI server provides. Assume this implementation is a class named ServerClock (see Listing 1). Since ServerClock implements the TimeMonitor interface, it's easy to have new instances of this object register itself with the RMI server. The constructor of the ServerClock class can do just this. Once registered, communication can go back and forth between the client and server (see Figure 3). The client would create an instance of this class by supplying the name and port number of the RMI server. ServerClock serverClock = new ServerClock(serverName, port); The ServerClock constructor performs two critical operations. First, it makes this method call: UnicastRemoteObject.exportObject( this ); According to the J2SE documentation, this method call dynamically "exports the remote object to make it available to receive incoming calls using an anonymous port." Making this method call is the key that allows the server to communicate with all the various clients without requiring stub classes from the client, or knowing the names and ports of client machines. Second, the constructor contacts the RMI server and registers the object by calling the addTimeMonitor() method: timeServices.addTimeMonitor( this ); This doesn't result in a ClassNotFound-Exception because the keyword "this" refers to an instance of the ServerClock object that the RMI server provided. Now the ServerClock class needs to implement the setTime(Date) method of the TimeMonitor class:
/**
* TimeMonitor interface method
*/
public void setTime(Date d)
{
System.out.println("The new time is: " + d);
}
This implementation of setTime(Date d) will technically work, but the RMI client will never know that the RMI server called this method because all the method does is print the Date object to standard out. The RMI client needs to be notified by the ServerClock whenever the setTime(Date d) method is called. A simple solution is for the RMI server to define another, nonremote interface Seconds-Listener:
import java.util.Date;
public interface SecondsListener
{
public void tick(Date d);
}
then add a method to ServerClock that stores implementations of SecondsListener in a vector.
/**
* add listener to list
*/
public void addSecondsListener(Seconds Listener sl) {
synchronized(listeners) {
if (!listeners.contains(sl)) {
listeners.add(sl);
}
listeners.notifyAll();
}
}
Finally, ServerClock's implementation of the setTime(Date d) method is changed to loop over the vector.
/**
* TimeMonitor interface method
*/
public void setTime(Date d)
{
synchronized(listeners) {
for (
Iterator itr=listeners.iterator();
itr.hasNext();
((SecondsListener)itr.next()).tick(d)
) {}
listeners.notifyAll();
}
}
With these additions in place, the client can create as many listener classes as are needed and register the classes with ServerClock: ServerClock serverClock = new ServerClock(serverName, port); serverClock.addSecondsListener(new ListenerA()); serverClock.addSecondsListener(new ListenerB()); serverClock.addSecondsListener(new ListenerC()); ... Since the SecondsListener implementations exist on the client, there is no problem with ClassCastExceptions. Summary The RMI server is the place where events are generated and fired. A remote interface (TimeServices) allows an RMI server-supplied object (ServerClock) to add itself to an event notification list on the server. Another remote interface (TimeMonitor) allows the server to communicate back with the client when the event occurs. The server-supplied object (ServerClock) allows the client to add an arbitrary number of local objects implementing a nonremote, server-supplied interface (SecondsListener). When the RMI server fires an event (TimeMonitor.setTime(Date)), the server-supplied object (ServerClock) loops over its list of registered listeners and passes the event along. Reader Feedback: Page 1 of 1
Your Feedback
Latest Cloud Developer Stories
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week
Breaking Cloud Computing News
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||