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:
- Create the distributed object interface (comparable to CORBA IDL)
- Create and compile the distributed object implementation
- Generate the "stub" class with rmic
- Create and compile the server that "hosts" a distributed object
- Create and compile the client that "binds" to the distributed object
- Start the RMI naming service (rmiregistry)
- Start the server
- 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:
- Adding the private attributes necessary to support the
public interface (line 14)
- Adding constructors and other member functions not intended to be called
by distributed clients
- Defining the implementation of all member functions
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:> set CLASSPATH=%CLASSPATH%;.
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.