Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Java Q&A


Dr. Dobb's Journal March 1998: Java Q&A

Govind is a software developer specializing in distributed-object computing and Java/legacy system integration. He can be contacted at [email protected].


Java's Remote Method Invocation (RMI) brings distributed-object computing to Java. With RMI, Java objects are not restricted to operating solely within the confines of the host JVM. Rather, they can freely roam the Internet, seeking remote object implementations to service their needs.

Simply having remote objects whose methods can be invoked by distributed client objects is not sufficient for complex interactions, however. For instance, applications supporting any kind of collaborative functionality need to implement peer-to-peer relationships between interacting client and server objects. But how do you enable the server object to communicate with the client object in a truly asynchronous manner? While there are several approaches to implementing peer-to-peer asynchronous communication, one of the more elegant techniques involves the use of callbacks.

Event-driven programming in languages like C has traditionally used function pointers to pass references to functions, which are then asynchronously invoked in response to an "event" (timers, mouse clicks, and the like). Java uses interfaces to give you access to the same functionality in an object-oriented world. Here, the interface defines the methods that may be invoked by any object with access to the interface. The real "functionality" is present within some other object that actually implements this interface and it is "called back" by the target object.

Implementing callbacks, however, is not without challenges -- particularly in a distributed-object environment like RMI. Callbacks are implemented in RMI by setting the client itself as an exportable remote object (that is, make it implement some remote interface), then registering a reference of this with the remote-server object. This way, the server can asynchronously invoke the remote methods of any connected client in the same way the client can asynchronously invoke the methods implemented within the remote-server objects.

In this article, I'll examine the intricacies of enabling true peer-to-peer RMI interaction and present a step-by-step approach to implementing callbacks. For more information on RMI, see "How Do I Use Java Remote Method Invocation from an Applet?" by Cliff Berg (DDJ, March 1997).

Implementing Callbacks

The steps required to implement RMI callbacks have much in common with those needed to implement a simple remote-server object. The key difference is that, with RMI, you also have to set up the client applet or application as a "remote object," and register its reference with the remote-server object.

To illustrate how this works, I'll implement an application using RMI callbacks. In this example, the remote-server object (QuoteServer.java) maintains an updatable list of quotes. The RMI client applet (QuoteApplet.java) can make additions to this list in an asynchronous manner at any time. In the process, I'll demonstrate the use of RMI callbacks by having the server object periodically select a quote from its list in random fashion, and update each of the connected client objects in an asynchronous manner.

Specify the remote-server interface. The remote-server interface serves to specify the functionality implemented by the server object. This functionality is then made accessible to a client via remote methods invocations.

The remote-server interface (see Listing Onelists two methods available to client RMI objects. The addQuote method is used by remote clients to update the list of quotes, and the setQClientInterface method is used by clients to "register" themselves with the server object.

Specify the remote-client interface. Any RMI client applet/application that exports itself as a remote object needs to implement a remote-client interface. This is what enables the server object to invoke the client functionality in an asynchronous manner via callbacks.

The refreshClient method (see Listing Two) defines how each remote-client object can be "notified" or updated by the server object asynchronously.

Develop the remote-server object by implementing the remote-server interface. If you have prior experience with RMI, you'll notice that nothing really new is happening in the implementation of the remote-server object in Listing Three. The server object maintains two updatable Vector objects: one of the quotes, and the other to maintain references to all the connected remote RMI client objects. The remote-server object also demonstrates the use of callbacks in making asynchronous updates to the client object.

Develop the client application or applet by implementing the remote-client interface. The mypackage.rmiexamples package in Listing Four illustrates how you can implement the client application/applet using the remote-client interface in step 2.

Compile the Java source files for the RMI client and server. Since the RMI client applet is sent across the Internet by the web server, it is necessary that the applet and all stub files and interfaces be accessible in some location under the document root directory of the web server; for example, create a directory "rmi" somewhere within the $DOCUMENT_ROOT for your web server. Since the Java classes -- including the RMI client and server -- are part of the package mypackage.rmiexamples, you will have to create the subdirectories $DOCUMENT_ROOT/rmi/mypackage and $DOCUMENT_ROOT/ rmi/mypackage/rmiexamples.

Assuming you have all the Java source files within $DOCUMENT_ROOT/rmi, you can compile them by going to that directory and executing Example 1. This process creates the package mypackage.rmiexamples containing the classes QClientInterface.class, QServerInterface.class, QuoteServer.class, and QuoteApplet.class.

Generate the stubs and skeletons for the RMI client and server objects. From within $DOCUMENT_ROOT/rmi, invoke Example 2, which generates the server skeletons and client stubs for the client and server remote objects within $DOCUMENT_ROOT/rmi/mypackage/rmiexamples.

QuoteServer_Stub.class and QuoteServer_Skel.class are the client stubs and server skeletons for the remote-server object. QuoteApplet_Stub.class and QuoteApplet_Skel.class are the same for the client-remote object.

Start the remote-server object. Start up the remote-server object from within $DOCUMENT_ROOT/rmi using java mypackage.rmiexamples.QuoteServer &.

Run the client applet after creating an HTML file for the RMI client. Since Java applets are embedded within an HTML file, make sure that you create an HTML file (say, rmi.html) to send the QuoteApplet.class to the browser or appletviewer.

Since support for JDK 1.1 is still evolving within the major browsers, test your client applet with the JDK 1.1 appletviewer. You should be able to bring up the RMI client applet with your appletviewer using appletviewer http:// hostname/rmi/rmi.html. Figure 1 shows an RMI client applet executing within the appletviewer.

Summary

Callbacks are not just an elegant computing paradigm -- they are also a prerequisite for enabling peer-to-peer asynchronous communication. Here, I've presented one way you can easily implement callbacks within RMI. Although there may be other mechanisms for implementing RMI callbacks, they all rely upon the basic principle of setting up the RMI client as an exportable remote object, then passing its reference to the remote-server object. Finally, note that RMI applications utilizing callbacks cannot be used across firewalls because RMI makes use of HTTP tunneling; to get through firewalls, remote-client object references cannot use the stateless HTTP protocol for persistence.

DDJ

Listing One

/** Defines the methods available to the client for remote invocation */package mypackage.rmiexamples;
public interface QServerInterface extends java.rmi.Remote {
    public  void addQuote(String quote) throws java.rmi.RemoteException;
    public  void setQClientInterface(QClientInterface c) 
                                         throws java.rmi.RemoteException;
}

Back to Article

Listing Two

/** Defines methods available to server for remote invocation as callbacks */package mypackage.rmiexamples;
public interface QClientInterface extends java.rmi.Remote {
        public void refreshClient(String q) throws java.rmi.RemoteException;
}

Back to Article

Listing Three

package mypackage.rmiexamples;import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import mypackage.rmiexamples.QClientInterface;
import mypackage.rmiexamples.QServerInterface;
import java.util.*;
import java.rmi.registry.LocateRegistry;


</p>
public class QuoteServer extends UnicastRemoteObject 
                                      implements QServerInterface, Runnable {
    private Vector clientele; //tracks all connected clients
    private QClientInterface myQClientInterface; 
    private Vector quoteList; //maintains updatable list of quotes
    private QClientInterface myClientObj;
    private Thread clientThread=null;
    private static int counter=0;


</p>
    public QuoteServer ()  throws java.rmi.RemoteException {
       super();
       // create and initialize the quote list 
       quoteList = new Vector();
       quoteList.addElement("Climb mountains to see lowlands");
       quoteList.addElement("If you want the rainbow, put up with the rain");
       quoteList.addElement("Smooth seas do not make skilful sailors");
       clientele = new Vector();
    }
    public  void setQClientInterface(QClientInterface c) 
                                                 throws RemoteException {
        synchronized (clientele) {
            clientele.addElement(c);
        }
       if (clientThread == null) {
            clientThread = new Thread(this, "clientThread");
            clientThread.start();
        }
    }
    private void doIt() {
        synchronized (clientele) {
            Vector backup = (Vector) clientele.clone();
            int seed;
            while ((seed = new Random().nextInt()) <=0) ;
                seed = seed % quoteList.size();
                String data = quoteList.elementAt(seed)+ ": "+ ++counter;
                for (int i=0; i < clientele.size(); i++) {
                    myClientObj = (QClientInterface) clientele.elementAt(i);
                    try {
                        //update the client asynchronously via callback
                        myClientObj.refreshClient((String) data );
                    } catch (RemoteException e) {
                        System.out.println("client must have disconnected!");
                        //get rid of remote reference for disconnected client
                        backup.removeElement(myClientObj);
                        if (backup.size() <= 0) {
                            //no more clients- so stop server thread
                            clientele = (Vector) backup.clone();
                            Thread dummy = clientThread;
                            clientThread = null;
                            dummy.stop();
                        }
                    }
            }
            clientele = (Vector) backup.clone();
        } //end syncronization on clientele
    }
    public void run() 
    {
        while (true) {
            try {
                //sleep for a second
                Thread.currentThread().sleep(1000);
            } catch (Exception e) { 
                                    }
            doIt();
        }
    }   
    public void addQuote(String quote) throws RemoteException {
        synchronized (quoteList) {
            //update quote list
            quoteList.addElement(quote);
        }
    }
    public static void main(String[] args) {
        System.setSecurityManager(new RMISecurityManager());
        try {
            System.out.println("QuoteServer.main: creating registry");  
            LocateRegistry.createRegistry(1099);
            System.out.println("QuoteServer.main: creating server");
            QuoteServer myQuoteServer=new QuoteServer();
            System.out.println("QuoteServer.main: binding server ");
            Naming.rebind("/QuoteServer", myQuoteServer);
            System.out.println("QuoteServer bound in registry");
         } catch (Exception e) {
            System.out.println("Exception on binding QuoteServer");
            System.out.println(e.toString());
          }
    }
}

Back to Article

Listing Four

package mypackage.rmiexamples;

</p>
import mypackage.rmiexamples.QServerInterface;
import mypackage.rmiexamples.QClientInterface;
import java.rmi.*;
import java.rmi.server.*;
import java.util.Random;
import java.awt.*;
import java.applet.*;
public class QuoteApplet extends Applet implements QClientInterface {
public QServerInterface  srvQuote=null;


</p>
    //the client uses jdk1.1 event handling mechanism
    void sendQuote_Clicked(java.awt.event.ActionEvent event) {
        try {
            //update the remote server object with a new quote
            srvQuote.addQuote((String)myQuote.getText());
        } catch (Exception e) {
            System.out.println(e.toString());
        }
    }
    public void init() {
    super.init();
        try {
            //by exporting the client object, turn it into a
            //remote object 
            UnicastRemoteObject.exportObject(this);
        } catch (Exception e) {
            System.out.println("Could not export client remote object");
            System.out.println(e);
              }
    try {
        //obtain the remote server object's reference by interrogating the
       //rmi registry
            srvQuote = (QServerInterface) 
                Naming.lookup("//"+getCodeBase().getHost()+"/QuoteServer");
           srvQuote.setQClientInterface(this);
    } catch (Exception e) {
        System.out.println("Could not set client interface at server");
       System.out.println(e.toString());
    }
}
    public void addNotify() {


</p>
        //don't worry about the specifics of the
               //following code. All it does is create a GUI
              //for the rmi client
                   super.addNotify();
        setLayout(null);
        resize(1016,565);
        label1 = new java.awt.Label("Input Quote:");
        label1.reshape(21,24,72,21);
        add(label1);
        myQuote = new java.awt.TextField();
        myQuote.reshape(104,24,242,21);
        add(myQuote);
        sendQuote = new java.awt.Button("Send");
        sendQuote.reshape(357,24,48,21);
        add(sendQuote);
        quoteList = new java.awt.TextArea();
        add(quoteList);
        quoteList.reshape(36,60,371,125);
        //register listners
        Action lAction = new Action();
        sendQuote.addActionListener(lAction);
    }
    java.awt.Label label1;
    java.awt.TextField myQuote;
    java.awt.Button sendQuote;
    java.awt.TextArea quoteList;
public void refreshClient(String q) throws java.rmi.RemoteException {
    //this is the callback method implementation
        quoteList.append(q+"\n");
}
class Action implements java.awt.event.ActionListener {
        public void actionPerformed(java.awt.event.ActionEvent event) {
            Object object = event.getSource();
            if (object == sendQuote)
                sendQuote_Clicked(event);
        }
    }
}

Back to Article


Copyright © 1998, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.