RMI   (Remote Method Invocation)

Remote Method Invocation is a powerful technology for developing networked applications without having to worry about the low-level networking details. RMI transcends the client/server model of computing with a more general "remote object" model. In this model, the "server" defines objects that "clients" can use remotely. Clients invoke methods of a remote object exactly as if it were a local object running in the same virtual machine as the client. RMI hides the underlying mechanism for transporting method arguments and return values across the network.

An important limitation of RMI is that it only works when both the client and the server are Java applications. In this way, it is not as general as the CORBA architecture, which is independent of programming language.

To develop and execute an RMI application, the following steps are useful:

  1. Create the distributed object interface (comparable to CORBA IDL)
  2. Create and compile the distributed object implementation
  3. Generate the "stub" class with rmic
  4. Create and compile the server that "hosts" a distributed object
  5. Create and compile the client that "binds" to the distributed object
  6. Start the RMI naming service (rmiregistry)
  7. Start the server
  8. Start the client

1. Create the distributed object interface

A Java interface will server as the "IDL" for remote objects. The first step is to create an interface that extends the java.rmi.Remote interface (line 5). This interface defines the exported methods that the remote object implements (i.e., the methods the "server" implements and "clients" can invoke remotely). Each method in this interface must be declared to throw a java.rmi.RemoteException (the superclass of many more specific RMI exception classes) (lines 6-7). This is necessary because there are quite a few things that can go wrong during the remote method invocation process over a network. [Flanagan97b, p291]

2. Create and compile the distributed object implementation

The first step established an interface only. This second step creates an implementation of that interface. A suggested naming convention uses the same name as the interface with an "Impl" or "_i" suffix. The class extends the java.rmi.server.UnicastRemoteObject class (line 12), and, implements the "IDL" interface (line 13).

This step routinely consists of:

Notice that the added constructor must be declared to throws RemoteException (line 15).

"Other than declaring its remote methods to throw RemoteException objects, the remote object does not need to do anything special to allow its methods to be invoked remotely. The UnicastRemoteObject and the rest of the RMI infrastructure handle this automatically." [Flanagan97b, p291]

3. Generate the "stub" class

With RMI, the client and server do not communicate directly. On the client side, the client's reference to a remote object is implemented as an instance of a "stub" class. When the client invokes a remote method, it is a method of this stub object that is actually called. The stub does the necessary networking to pass that invocation to a "skeleton" class on the server. This skeleton translates the networked request into a method invocation on the server object, and passes the return value back to the stub, which passes it back to the client. This can be a complicated system, but fortunately, application programmers never have to think about stubs and skeletons; they are generated automatically by the rmic tool. Invoke rmic with the name of the distributed object class -
   C:> rmic -v1.2 RMIcountImpl 
The old 1.1 architecture creates two ".class" files with the suffixes _Stub and _Skel. If you will only ever need support for version 1.2 clients, rmic can be run with the -v1.2 option. This will generate just the _Stub class file (apparently the _Skel class file is no longer necessary).

4. Create and compile the server

The "remote object" is not capable of functioning as a stand-alone, disembodied object. It must be "hosted" by a Java application. This application is called the "server". It creates an instance of the remote object (line 33), and then "exports" that object (makes it available for use by clients) by registering the object by name with a registry service. This is usually done with bind() or rebind() from the java.rmi.Naming class (line 34) and the rmiregistry program (see step 6). The string chosen as the name can be quite arbitrary, as long as the server and all clients use the same string (lines 34 and 47).

Using rebind() is usually more convenient because it will not fail if the string chosen already exists in the binding database. This will happen if the server executable goes away for some reason, and then is relaunched while the rmiregistry executable continues to run.

A server program may also act as its own registry server by using the LocateRegistry class and Registry interface of the java.rmi.registry package.

5. Create and compile the client

The next step is to write a client program that uses the remote object exported by the server. The client must first obtain a reference to the remote object by using the Naming class to look up the object by name (line 47). The name is typically a URL with an "rmi:" protocol prefix. The remote reference that is returned is an instance of the "IDL" interface base class for the object (or more specifically, a "stub" object for the remote object). Once the client has this remote object, it can invoke methods on it exactly as it would invoke the methods of a local object (lines 52 and 54). The only thing that it must be aware of is that all remote methods can throw RemoteException objects, and that in the presence of network errors, this can happen at unexpected times. [Flanagan97b, p292]

RMI uses Java's serialization mechanism to transfer the stub object from the server to the client. Because a client may load an untrusted stub object, it should have a security manager installed to prevent a malicious (or just buggy) stub from deleting files or otherwise causing harm. The RMISecurityManager class is a suitable security manager that all RMI servers and clients should install (lines 31 and 44). [Note: RMI did not work until I opened up all permissions to all users in the file Q:\cpp\java\jdk1.2\jre\lib\security\java.policy.]

6. Start the RMI naming service (rmiregistry)

If the server uses the default registry service provided by the Naming class, you must run the registry server (if it is not already running). The CLASSPATH environment variable needs to reflect the location of the .class files generated so far. The commands below show how to append the current directory to the end of CLASSPATH's current value, and how to launch a new DOS window that is executing rmiregistry.
   C:> start rmiregistry

7. Start the server

The server can be launched in a new DOS window with the first command below. To watch the server activity, start the server with the second command. The CLASSPATH environment variable needs to include the directory containing the stub file when rmiregistry is run, or, the server needs to specify the java.rmi.server.codebase property to indicate the location of the stub file (third command).
   C:> start java RMIcountServer
   C:> start java -Djava.rmi.server.logCalls=true RMIcountServer
   C:> java -Djava.rmi.server.codebase=file:///myDir/mySubDir/ RMIcountServer
Note that the trailing '/' is required. This is so that the implementation can resolve (find) the class definition(s) properly.

If your server has hung, you can get a monitor dump and thread dump by doing a <Ctrl>-<Break> in Windows.

8. Start the client

It can take up to a minute for the server to successfully register its "remote object" with the registry service. If the client tries to lookup() the remote object before the registration has completed, the client will receive an exception. For this reason, it is a good idea to have the server print a message to standard-out when its registration request has completed (line 65). When the message appears, the client can now be started with the comand java RMIcountClient.