This is a brief introduction to Java Remote Method Invocation (RMI). Java RMI is a mechanism that allows one to invoke a method on an object that exists in another address space. The other address space could be on the same machine or a different one. The RMI mechanism is basically an object-oriented RPC mechanism. CORBA is another object-oriented RPC mechanism. CORBA differs from Java RMI in a number of ways:
This tutorial attempts to show the essence of RMI, without discussing any extraneous features. Sun has provided a Guide to RMI, but it includes a lot of material that is not relevant to RMI itself. For example, it discusses how to incorporate RMI into an Applet, how to use packages and how to place compiled classes in a different directory than the source code. All of these are interesting in themselves, but they have nothing at all to do with RMI. As a result, Sun's guide is unnecessarily confusing. Moreover, Sun's guide and examples omit a number of details that are important for RMI.
There are three processes that participate in supporting remote method invocation.
In this tutorial, we will give an example of a Client and a Server that solve the classical "Hello, world!" problem. You should try extracting the code that is presented and running it on your own computer.
There are two kinds of classes that can be used in Java RMI.
If a serializable object is passed as a parameter (or return value) of a remote method invocation, then the value of the object will be copied from one address space to the other. By contrast if a remote object is passed as a parameter (or return value), then the object handle will be copied from one address space to the other.
Serializable objects are something like the concrete objects of C++, but only with respect to remote method invocations. With respect to ordinary method invocations, serializable objects are ordinary objects, not concrete objects. This is confusing, and so in Java one must carefully distinguish remote method invocations from ordinary method invocations.
We now consider how to design Remote and Serializable classes.
The easier of the two is a Serializable class.
A class is Serializable if it implements the java.io.Serializable
interface. This interface has no methods; it serves only to mark the class
as serializable to the compiler.
Subclasses of a Serializable class are also Serializable.
Many of the standard classes are Serializable, so a subclass of one
of these is automatically also Serializable.
Normally, any data within a Serializable class should also be Serializable.
Although there are ways to include non-serializable objects within
a serializable object, it is awkward to do so. See the documentation
of java.io.Serializable for more information about this.
Using a serializable object in a remote method invocation is straightforward. One simply passes the object using a parameter or as the return value. The type of the parameter or return value is the Serializable class. Note that both the Client and Server programs must have access to the definition of any Serializable class that is being used. The only Serializable class that will be used in the "Hello, world!" example is the String class.
Next consider how to define a Remote class. This is more difficult than defining a Serializable class. A Remote class has two parts: the interface and the class itself. The Remote interface must have the following properties:
java.rmi.Remote.
The Remote interface has not methods; it serves only to mark the interface
so that classes implementing the interface can be used to create remote
objects.
java.rmi.RemoteException.
Other exceptions may also be thrown.
java.rmi.server.UnicastRemoteObject
class. Objects of such a class exist in the address space of the server
and can be invoked remotely.
While there are other ways to define a Remote class,
this is the simplest way to ensure that objects of a class can be
used as remote objects. See the documentation of the
java.rmi.server package for more information.
In the example program, we need a Remote class and its corresponding
Remote interface. We call these Hello
and HelloInterface, respectively.
Here is the the file HelloInterface.java:
import java.rmi.*;
/**
* Remote Interface for the "Hello, world!" example.
*/
public interface HelloInterface extends Remote {
/**
* Remotely invocable method.
* @return the message of the remote object, such as "Hello, world!".
* @exception RemoteException if the remote invocation fails.
*/
public String say() throws RemoteException;
}
Here is the the file Hello.java:
import java.rmi.*;
import java.rmi.server.*;
/**
* Remote Class for the "Hello, world!" example.
*/
public class Hello extends UnicastRemoteObject implements HelloInterface {
private String message;
/**
* Construct a remote object
* @param msg the message of the remote object, such as "Hello, world!".
* @exception RemoteException if the remote method fails.
*/
public Hello(String msg) throws RemoteException {
message = msg;
}
/**
* Implementation of the remotely invocable method.
* @return the message of the remote object, such as "Hello, world!".
* @exception RemoteException if the remote invocation fails.
*/
public String say() throws RemoteException {
return message;
}
}
All of the Remote interfaces and classes should be compiled using
javac.
Once this has been completed, the stubs and skeletons for the Remote
interfaces should be compiled by using the rmic stub compiler.
The stub and skeleton of the example Remote interface are compiled
with the command:
rmic Hello
The only problem one might encounter with this command is that
rmic might not be able to find the files
Hello.java and HelloInterface.java
even though they are in the same directory where rmic is
being executed.
If this happens to you, then try setting the CLASSPATH environment variable
to the current directory, as in the following command:
setenv CLASSPATH .If your CLASSPATH variable already has some directories in it, then you might want to add the current directory to the others.
Having described how to define Remote and Serializable classes, we now discuss how to program the Client and Server. The Client itself is just a Java program. It need not be part of a Remote or Serializable class, although it will use Remote and Serializable classes.
A remote method invocation can return a remote object as its return value, but one must have a remote object in order to perform a remote method invocation. So to obtain a remote object one must already have one. Accordingly, there must be a separate mechanism for obtaining the first remote object. The Object Registry fulfills this requirement. It allows one to obtain a remote object using only the name of the remote object.
The name of a remote object includes the following information:
/**
* Client program for the "Hello, world!" example.
* @param argv The command line arguments which are ignored.
*/
public static void main(String[] argv) {
try {
HelloInterface hello =
(HelloInterface) Naming.lookup("rmi://maasim.ccs.neu.edu/Hello");
System.out.println(hello.say());
} catch (Exception e) {
System.out.println("HelloClient exception: " + e);
}
}
The Naming.lookup method obtains an object handle from the
Object Registry running on maasim.ccs.neu.edu and listening
to the default port. Note that the result of Naming.lookup
must be cast to the type of the Remote interface.
The remote method invocation in the example Client is
hello.say(). It returns a String which is then printed.
A remote method invocation can return a String object because
String is a Serializable class.
The code for the Client can be placed in any convenient class.
In the example Client, it was placed in a class HelloClient
that contains only the program above.
The Server itself is just a Java program. It need not be a Remote or Serializable class, although it will use them. The Server does have some responsibilities:
Naming.rebind(objectName, object);
where object is the remote object being registered,
and objectName is the String that names the remote object.
/**
* Server program for the "Hello, world!" example.
* @param argv The command line arguments which are ignored.
*/
public static void main(String[] argv) {
try {
Naming.rebind("Hello", new Hello ("Hello, world!"));
System.out.println("Hello Server is ready.");
} catch (Exception e) {
System.out.println("Hello Server failed: " + e);
}
}
Java RMI limits binding and unbinding requests to Object
Registries running on the same machine, so it is never necessary to
specify the name of the machine when one is registering an object.
The code for the Server can be placed in any convenient class.
In the example Server, it was placed in a class HelloServer
that contains only the program above.
Before starting the Server, one should first start the Object Registry, and leave it running in the background. One performs this by using the command:
rmiregistry &It takes a second or so for the Object Registry to start running and to start listening on its socket. If one is using a script, then one should program a pause after starting the Object Registry. If one is typing at the command line, it is unlikely that one could type fast enough to get ahead of the Object Registry.
The Server should then be started; and, like the Object Registry, left running in the background. The example Server is started using the command:
java HelloServer &The Server will take a few seconds to start running and to construct and register remote objects. So one should wait a few seconds before running any Clients. Printing a suitable message, as in the example Server, is helpful for determining when the Server is ready.
Th Client is run like any other java program. The example Client is executed using:
java HelloClient
Ken Baclawski