Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Remote Method Invocation: Creating Distributed Java-to-Java Applications

 
 

Articles Index

Remote Method Invocation (RMI) provides a means of communicating between Java applications using normal method calls, and offers the capability for the applications to run on separate computers--located perhaps as far apart as on opposite sides of the world.

One important feature of RMI is that it presents a programmatic interface for networking rather than relying on the sockets and streams approach. The method's major advantage is that it offers you a higher-level, method-based interface in which a remote object is treated as though it were local. RMI is also convenient to use and more natural in many ways than using sockets. A requirement, however, is that you must run Java on both ends of the network connection--rather than simply having both systems implement the same networking protocol.

Here are the steps for building a working RMI application:

1. Develop the remote object, server, and client code
2. Compile the code
3. Run the RMI compiler (rmic)
4. Move the .class files to an appropriate location
5. Start the registry
6. Start the server
7. Start the client

The example presented here is designed so the server and client run on the same machine. The principles are the same for designing applications with the server and client running on different computers. The program looks up names and phone numbers in a central database residing on a server. To use the example, you'll need to work with JDK 1.1 or a later version. The example was developed on Windows NT 4.0 and can be adapted as necessary.

How RMI Works

With RMI, client and server programs are running, with both using Java. You specify a Java interface to describe each object to be shared remotely, and enumerate the public methods to be invoked on the object. A server will use the interface and create objects that implement it and that are also registered with a name registry service using a URL-based naming scheme. That is, the shared objects to be accessed remotely will be registered with a name such as rmi://localhost/LookupServer for future access.

A client will contact the registry, attempt to retrieve an object by name, and obtain a remote reference to it. You handle method invocation and data passing via skeleton and stub classes that are generated automatically through an RMI compiler from user code, and through communication using object serialization and TCP/IP. This process is covered further below.

Develop Remote Object Code

RMI supports Java objects communicating via their methods, regardless of where the objects are located.

The first step in creating a class to be accessed remotely is to define an interface describing the methods that are available remotely. The interface that will be made available remotely is:

   // Lookup.java

   import java.rmi.*;

   public interface Lookup extends Remote {
      public String findInfo(
        String info) throws RemoteException;
   }

java.rmi.Remote is an empty marker interface that indicates that an object is remote, so class objects implementing Lookup are marked as being remote references.

All the methods in a remote interface must declare that they can throw an exception of type java.rmi.RemoteException--which gets thrown whenever a remote method invocation fails.

Develop the Server Code

Once you specify the remote object definition via an interface, the next step is to develop the server code. The server implements the object interface and creates instances of the object to be shared remotely.

For this application, the server code looks like this:

 // LookupServer.java

 import java.io.*;
 import java.util.*;
 import java.rmi.*;
 import java.rmi.server.*;

 public class LookupServer extends UnicastRemoteObject
                                     plements Lookup {
private Vector save = new Vector();

 public LookupServer(String db) throws RemoteException
    {
    try {
      FileReader fr = new FileReader(db);
         BufferedReader br = new BufferedReader(fr);
          String s = null;
         while ((s = br.readLine()) != null)
          save.addElement(s);
          fr.close();        
        }
      catch (Throwable e) {
         System.err.println("exception");
         System.exit(1);
         }
    }

   public String findInfo(String info)
    {
         if (info == null)
         return null;

         info = info.toLowerCase();
         int n = save.size();
         for (int i = 0; i < n; i++) {
           String dbs = (String)save.elementAt(i);
           if (dbs.toLowerCase().indexOf(info) != -1)
           return dbs;
    }

         return null;
    }

    public static void main(String args[])
    {
       try {
          RMISecurityManager security =
                         new RMISecurityManager();
          System.setSecurityManager(security);
          String db = args[0];
          LookupServer server = new LookupServer(db);
          Naming.rebind("LookupServer", server);
          System.err.println("LookupServer ready...");
         }
         catch (Throwable e) {
          System.err.println("exception: " + e);
          System.exit(1);
         }
      }
   }

The server reads in a text database of phone numbers and names and stores them internally. findInfo then does a caseless lookup of a given name or number in the database.

A simple example of the database is:

 Smith, Joan 204-9987

 Jones, Milton 898-9749

 Brown, Gertrude 598-1265

Note that LookupServer extends java.rmi.server.UnicastRemoteObject and implements Lookup. The first of these classes supplies some of the basic properties needed for the remote objects, and the second defines the methods to be made available remotely.

Setting up a security manager

The tricky part of this code is what occurs in main. The first thing is to establish a security manager. RMI involves loading remote .class files, something like a web browser does with an applet, but this operation entails some security risk. If you don't establish a security manager, the default is to load only local files, and RMI by definition cannot work with this restriction. So you have to set a security manager to make explicitly clear that loading remote .class files is acceptable.

An instance of LookupServer then gets created and registered with the name registry service using Naming.rebind. This process associates an object reference with a specific name so that the client can look up the object by name through the registry.

You might wonder how a remote method actually gets invoked, since the server contains no socket code or network programming of any kind. What happens behind the scenes is that the server and client use skeleton and stub entities to communicate. These .class files are generated from the server .class file via the RMI compiler described below.

Conceptually, the stub class is:

public class LookupServer_Stub
  extends java.rmi.server.RemoteStub 
   implements Lookup, java.rmi.Remote { ...  }
and the skeleton class is:
public class LookupServer_Skel implements 
             java.rmi.server.Skeleton { ...  }
Usage like:
   javap -c LookupServer_Stub
displays the bytecodes and illustrates what goes on behind the scenes.

The stub is a surrogate for the remote object, and the skeleton is a server-side entity that dispatches calls to the remote object implementation. The stub takes care of collecting (or marshalling) the data and transmitting/receiving it on the client side. The skeleton performs similarly on the server side. Object serialization changes the data into a stream of bytes, which are transmitted via TCP/IP.

Develop the Client Code

// LookupClient.java

import java.rmi.*;
import java.rmi.server.*;

 public class LookupClient {
   public static void main(String args[])
    {
     try {
       RMISecurityManager security =
                         new RMISecurityManager();
         System.setSecurityManager(security);
         String host = "localhost";
         String server = "LookupServer";
         String name = "rmi://" + host + "/" + server;
         Lookup look_obj = (Lookup)Naming.lookup(name);
           String results = look_obj.findInfo(args[0]);
            if (results == null)
             System.err.println("** not found **");
            else
               System.out.println(results);
         }
        catch (Throwable e) {
          System.err.println("exception: " + e);
          System.exit(1);
         }
      }
   }

Once you implement the server, implementing the client is straightforward. The security considerations for the client are the same as for the server. A URL is formed to contact the name registry, and Naming.lookup gets called with the server object name. The URL in this example is:

   rmi://localhost/LookupServer

where localhost is the loopback host name (IP address = 127.0.0.1) used when a server and client reside on the same machine. You can also use a remote host. Finally, the call to retrieve the information gets made, and the results are displayed.

Compile the Code

The three files Lookup.java, LookupServer.java, and LookupClient.java, compile as usual in Java:

   javac Lookup.java

   javac LookupServer.java

   javac LookupClient.java

Run the RMI Compiler

After you compile the files, run the RMI Compiler (rmic):

   rmic LookupServer
to produce the LookupServer_Skel.class and LookupServer_Stub.class files.

Move the .class Files to an Appropriate Location

Next, you move the client files (Lookup.class, LookupClient.class, and LookupServer_Stub.class) to the location you want to run the client from. You should place the server files (Lookup.class, LookupServer.class, LookupServer_Skel.class, and LookupServer_Stub.class) in a location the server designates for public access. For this example, one location that works is C:\, that is, the root directory of the C: drive on a PC running Windows.

Start the Registry

An object being accessed remotely needs to be entered in a registry of objects. JDK 1.1 provides a registry tool that you can start with the following:

   rmiregistry
rmiregistry can run either in a separate window or as a background job or process. It runs independently from the particular server and client but is required by them.

Start the Server

You start the server with:

   java LookupServer database_name
For example, if you had a set of names and phone numbers in C:\PHONE.TXT, you would use:
   java LookupServer C:\PHONE.TXT

Start the Client

You start the client with:

   java LookupClient smith
"smith" is a name or number to be looked up in the central database.

In Conclusion

As the example presented here shows, RMI enables you to create distributed Java-to-Java applications in which the methods of remote Java objects are invoked from other Java1 virtual machines, typically from different hosts. The requirement of RMI is that Java run on both ends of the network connection. And the advantages are support for a natural, convenient programming style and provision for a higher-level, method-based interface in which a remote object is treated as though it were local.

Glen McCluskey has 15 years of experience, and has focused on programming languages since 1988. He consults in the areas of Java and C++ performance, testing, and technical documentation.

_______

1 As used on this web site, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.