Remote Method Invocation

Remote Method Invocation

By Jeff Brown, OCI Senior Software Engineer

March 2000


Overview

Remote Method Invocation (RMI) is a technology that is used to implement pure Java distributed systems. 

This article will present a mid- to high-level overview of what RMI is and how it relates to some other similar technologies. It will also touch on some of the technical details involved in implementing a system built using RMI. While some technical coding details will be discussed, they will be discussed for the most part at a high level, and some working sample code will be covered.

RMI allows Java applications to access "remote" objects across a distributed system. 

Clients and servers in an RMI application can be distributed over a network, or they can reside in virtual machines running on the same physical machine. For the most part, the clients and servers are not necessarily concerned with exactly where their distributed counterparts reside. 

Typically a client needs some bootstrap mechanism to connect to a distributed server. Once connected, the client can gain references to remote objects and doesn't really need to be concerned with the details about exactly where the remote objects actually exist. That is, a server may give a client a reference to an object that actually exists on a third machine somewhere on the network, and the client doesn't know or care about this detail. The client need only be aware that it has a reference to a remote object.

Once a client has a reference to a remote object, the object can be manipulated in much the same way that a local object may be manipulated. There are some additional error conditions that the client needs to consider, but outside of that, the client may not need to make a distinction between local objects and remote objects.

Comparing RMI With Some Alternatives

RMI is certainly not the only technology that can be used to implement a distributed system in Java. There are a number of other ways to accomplish some of the same goals.

RMI vs. TCP Sockets and RPCs

A system could be implemented using TCP sockets to allow distributed components of a system to communicate with each other. While the Java networking API greatly simplifies this type of socket communication compared to writing BSD type socket code in a language like C, the application developer is still left to implement custom application protocols for the components to use, and this method does not really provide an object-oriented way to access objects distributed across the network.

Another approach might be some sort of remote procedure call (RPC) library. RPCs may greatly simplify the burden of marshaling parameters back and forth compared to writing lower level socket communication. However, RPC is not well suited for a truly object-oriented distributed system. RPC leads to a more procedural approach.

Both TCP sockets and RPC allow distributed components to communicate with each other, but there is no notion of a reference to a remote object.

RMI vs. CORBA

For a truly object-oriented distributed system, one technology that might be considered is the Common Object Request Broker Architecture (CORBA).

In many ways, CORBA is similar to RMI, but there are some critical differences.

For example, RMI is limited to Java-to-Java systems. If a requirement is for a Java client to communicate with a C++ server, for example, RMI doesn't meet the need.

If, however, the system is going to be all Java, there may be compelling reasons that RMI is the better choice.

One issue is simplicity. Writing a distributed system using RMI does not require the use of any special interface language; everything is written in Java.

Writing a distributed system using CORBA requires the use of at least one other language, known as the Interface Definition Language (IDL).  IDL is used to define the distributed API. In RMI, this is written in Java like any other Java interface.

Another issue to consider may be cost. Everything that is needed to implement an RMI system is available free as part of the standard Java Development Kit. While there are freely available CORBA implementations, many implementations can be very expensive.

Implementing RMI

The Registry

The registry is a simple server that acts as a place from which distributed objects are dispatched.

In order for an object to be available for remote access, the object must be registered with a registry. You can write your own registry and integrate registry services directly into your own application, or you can use the RMI registry that comes with the Java Development Kit rmiregistry.

The Server

A server that is going to export an object is responsible for registering the remote object with the registry. The registry must be running on the same physical machine where the server is running.

In order for an object to be registered with the registry, the object must either extend UnicastRemoteObject or it must be exported with UnicastRemoteObject.exportObject().

A server may export as many objects as it likes, and there is no need to have a separate server for each remote object.

An object might be registered with the registry like this:

Naming.rebind("MyRemoteObject", ro);

"MyRemoteObject" is an arbitrary name that will be associated with the object in the registry. Clients will access the object via this name.

The reference ro is a reference to the object that should be registered.

In addition to the rebind method, there is also a bind method in the Naming class. Bind will throw an exception if there is already an object registered with the specified name.

Before a server binds an object to the registry, it must setup the proper security manager. For simplicity, our example is going to use the standard RMISecurityManager and a very liberal policy file. This is probably not appropriate for a production environment but will simplify the exercise.

To use the standard RMISecurityManager, the code would look something like this:

System.setSecurityManager(new RMISecurityManager());

The Client

A client may get a reference to a remote object from a registry anywhere on the network. The registry may be on the same machine as the client, but it does not need to be.

Once a client gets a reference to a remote object, the client can manipulate that object as if it were a local object.

There are ways for a client to query the registry to find out what objects are registered there (Naming.list()). Once the client knows the name of the remote object (either by the fact that the object has a well known name, or the client has queried the registry), the client may request a reference to the object like this:

RemoteClock clock = (RemoteClock)Naming.lookup("rmi://hostname:1099/MyRemoteClock");

The parameter being passed to the lookup method is a URL defining where to find the remote object.

If the client wants to access a remote object with the name "MyRemoteClock," and that object is registered with a registry on the same machine that the client is on, and the registry is running on the default port, then ...

RemoteClock clock = (RemoteClock)Naming.lookup("rmi://hostname:1099/MyRemoteClock");

... is effectively the same as ...

RemoteClock clock = (RemoteClock)Naming.lookup("MyRemoteClock");

Note that the object returned by the lookup method is of type Remote. To do anything useful with it, the reference will have to be cast to a specific interface that extends Remote; in this case the object is cast to RemoteClock (see the example to follow).

The Remote Object

In order for an object to be accessed via RMI, the object must be registered with the registry.

In order for an object to be registered with the registry, the object must either extend UnicastRemoteObject, or it must be exported with a call to UnicastRemoteObject.exportObject().

In either case, the object needs to implement a Remote interface that defines the methods that will be exposed remotely. Every remote method must declare that it throws RemoteException.

The RMI Compiler

The RMI compiler (rmic) is used to generate stubs and skeletons for remote objects.

The stub is the client-side proxy to the remote object, and the skeleton is the server-side proxy to the remote object. These two objects handle all of the data marshaling.

rmic will generate RemoteObjectName_Stub and RemoteObjectName_Skel where RemoteObjectName is the name of the remote object.

Your code never references either the skeleton or the stub classes.

Parameters and Return Types

Once a client has a reference to a remote object, it can call any method on the object defined in its Remote interface.

If the method returns an Object, that Object must be Serializable.

If the method takes any Object parameters, those parameters must also be Serializable.

Note that as Objects are passed back and forth like this, they are all being passed by value, not by reference. A remote method can return a reference to another remote object, in which case it is essentially passed by reference.

A Simple Example

For an example, we will implement a trivial RMI application that consists of a single remote object which reports the current date and time on the server that is hosting the remote object. Our example will require one interface and three classes to be written.

RemoteClock

A remote interface that defines a single method.

  1. // RemoteClock.java
  2.  
  3. import java.rmi.*;
  4.  
  5. public interface RemoteClock extends Remote {
  6. public String getCurrentTime() throws RemoteException;
  7. }

RemoteClockImpl

A class to implement RemoteClock.

This is the remote object.

  1. // RemoteClockImpl.java
  2.  
  3. import java.rmi.*;
  4. import java.rmi.server.*;
  5. import java.util.*;
  6.  
  7. public class RemoteClockImpl extends UnicastRemoteObject
  8. implements RemoteClock {
  9.  
  10. public RemoteClockImpl() throws RemoteException {
  11. }
  12.  
  13. public String getCurrentTime() throws RemoteException {
  14. return new Date().toString();
  15. }
  16. }

MyServer

A simple server to provide an instance of RemoteClockImpl.

  1. // MyServer.java
  2.  
  3. import java.rmi.*;
  4.  
  5. public class MyServer {
  6.  
  7. public static void main(String[] args) {
  8. System.setSecurityManager(new RMISecurityManager());
  9. try {
  10. RemoteClockImpl clock = new RemoteClockImpl();
  11. Naming.rebind("MyRemoteClock", clock);
  12. } catch (Exception exc) {
  13. exc.printStackTrace();
  14. System.exit(1);
  15. }
  16. }
  17. }

MyClient

A client to access the RemoteClock contained in MyServer.

  1. // MyClient.java
  2.  
  3. import java.rmi.*;
  4.  
  5. public class MyClient {
  6. public static void main(String[] args) {
  7. try {
  8. RemoteClock clock = (RemoteClock)Naming.lookup("MyRemoteClock");
  9. if(clock == null) {
  10. System.err.println("There was a problem locating the remote clock.");
  11. } else {
  12. String time = clock.getCurrentTime();
  13. System.out.println("current time on server is: " + time);
  14. }
  15. } catch (Exception exc) {
  16. exc.printStackTrace();
  17. System.exit(1);
  18. }
  19. }
  20. }

The Policy File

For the sake of simplifying the example, we will use a very liberal security policy. This is probably not appropriate for a production environment.

Our policy file looks like this:

  1. grant {
  2. // Allow everything for now
  3. permission java.security.AllPermission;
  4. };
  5.  

Trying Out The Example

Compiling the files

Each of the .java files will need to be compiled.

javac MyClient.java MyServer.java RemoteClock.java RemoteClockImpl.java

This will generate MyClient.class, MyServer.class, RemoteClock.class, and RemoteClockImpl.class.

Once the source files have been compiled, the RMI compiler will have to be run to generate the stubs and skeletons.

rmic RemoteClockImpl

This should generate RemoteClockImpl_Stub.class and RemoteClockImpl_Skel.class.

Note that if the remote class is in a package, the fully qualified class name must be passed to rmic.

To run the example, three things have to be done:

  1. The registry must be started
  2. The server must be run to export the remote object
  3. A client must be run to access the remote object

To start the rmiregistry on Unix:

rmiregistry &

1. To start the rmiregistry on Windows

start rmiregistry

2. Once the registry is running, the server can be run.

java -Djava.security.policy=/devel/testing/policy MyServer
Note that the property java.security.policy should be set to point to your security policy file.

3. Once the server is running, a client may be run to access the server.

java MyClient
Note that the registry and the server have to be on the same physical machine, but the client may be run on the same machine or a different machine.

When the client successfully connects to the RemoteClock, the output should look something like this:

current time on server is: Sun Mar 19 14:37:46 CST 2000


 

Software Engineering Tech Trends (SETT) is a regular publication featuring emerging trends in software engineering.