Remote Method Invocation

1. Basic concepts

1.1 Purpose

With sockets, the application code determines the encoding and interpretation of the bytes transmitted.  The bytes sent by the initiator of the exchange include an indication of the operation to be performed, as well as an encoding of any arguments that are passed, and are encoded by the sender and parsed by the recipient.  The bytes sent by the recipient encode a return value or an error message, which must be parsed by the initiator.  With this approach, the application is responsible for defining a protocol that encodes all interactions between the client and server.  Modifying or extending the message protocol involves rewriting the encoding and parsing methods.  Furthermore, adding new clients for a given server or adding access to new servers in a client requires documentation of the protocol (which, in too many cases, is the source code) and duplicating the encoding and parsing methods.  Such a scheme is not easily extensible or reusable, and there is no object-oriented decomposition, encapsulation, information hiding, or inheritance.

Remote method invocation (RMI) supports sending messages and arguments to an object on another virtual machine.  That is, instead of packaging the receiver, operation name, and arguments in a message, the client simply makes a method call.  The syntax for a remote invocation is the same as that for any method call, except that all remote invocations can throw remote exceptions:
   try {
result = remoteObject.method(...args...);
}
catch (RemoteException ex) {
// handle error

}
Any other clients that need the same service use the same call, which is documented in the remote interface of the server object.  Note also that the client need not know the port or even the host on which the server object resides.  (We will see how clients obtain references to remote objects below.)  Like any method call, the remote method can either return a value or signal an exception.  Remote method invocation is the object-oriented version of remote procedure calls (RPC).  Java RMI supports Java-to-Java distributed objects on any platform for which a Virtual Machine exists, in contrast to CORBA, which supports any language or system, and DCOM which works only on MS-Windows.  RMI is simpler to use than CORBA or DCOM.

The caller of the remote method is sometimes termed the "client" and the receiver the "server".  Since an object can be the client for one remote invocation and the server for another, we will use the term remote object for an object whose methods can be invoked from a different virtual machine.  Any object can invoke a remote method, but a remote object must be an instance of a descendant of RemoteServer .

1.2 Remote references and stubs

Clearly, an object reference cannot refer to an object on another virtual machine so the invoker of a remote method does not have an actual reference to the remote object.  When we define a remote server in Java, the definition must be written as a remote interface and a separate implementation class.  A stub object on the client’s machine implements the remote interface and acts as a proxy for the remote object (the term “stub” comes from CORBA).  Clients use the remote interface (not the implementation class) as the type of remote object references, and the client’s remote interface reference refers to the stub.  When the client sends a message in the remote interface, the receiver is actually the stub, which communicates with the remote object via TCP/IP.

When a remote invocation is made, the stub "marshalls" the parameters (see the next section), then sends a message consisting of the remote object ID, an operation number that identifies the method, and the marshalled parameters to the remote object.  The remote object unmarshalls the parameters, invokes the desired method, marshalls the return value or exception, and sends it to the stub.  (In fact, the remote object delegates marshalling and unmarshalling to the "dispatcher" described below.)  The stub unmarshalls the return value or exception and passes it to the caller.  The stub class is generated from the remote interface by a special compiler called rmic , which is included in the JDK.

A client can obtain a remote reference as the return value of a remote method call or by accessing a remote object through a naming service.  When this occurs the stub class is dynamically loaded and linked if necessary, and an instance is created.  The class file for the remote interface must be available on both the server and client, but the stub class file is only needed by clients and the class file for the server implementation class need only be available on the server.  If an applet makes remote invocations, the remote interface and the stub class must be available via the codebase.

The initial version of RMI in JDK 1.1 used a "skeleton" class which behaved as the remote object's proxy for the caller (like the CORBA architecture).  An instance performed the marshalling and method dispatching for the remote object, as the stub does for the client, and was also generated by rmic.  Beginning in JDK 1.2, server implementation classes delegate these tasks to a "dispatcher" implemented using reflection, rather than using a skeleton class.

The stub and dispatcher (or skeleton in Java 1.1) communicate via the Java RemoteMethod Protocol (JRMP) embedded within HTTP, which is built on top of TCP/IP.  Sun and IBM have designed an invocation protocol called RMI-IIOP (IIOP is the Internet Inter-ORB Protocol used by CORBA), which allows RMI to interoperate with CORBA distributed systems, possibly written in other langauges.  Using RMI-IIOP requires some changes to the details of how remote objects and clients are coded (see RMI-IIOP Programmers Guide).  We will not discuss RMI-IIOP.

1.3 Argument passing

On a local virtual machine, argument passing is simple: primitive types and object references are copied to the method's stack frame.  As we stated above, an object reference cannot refer to an object on another virtual machine.  With RMI, arguments and return values of remote methods whose types are remote objects are translated to references to the corresponding stub objects.  That is, all such arguments refer to the same remote object (directly on its own machine, or via its stub on other machines).  Therefore, the stub class must be available in any virtual machine that uses that remote object as the receiver, parameter, or return vlaue of a remote invocation.

Non-remote arguments and return values of remote method invocations are serialized and passed by value, whether they are primitive or class type.  (We will see that this differs from CORBA paramter passing.)  That is, the remote object receives copies of the argument objects, including those that are class instances.  Collection elements are also copied by the serialization process, i.e. the process performs a "deep copy".  The arguments are serialized in the same stream, so if two argument objects both have references to a single object, there will be one copy of that object accessed via the method's parameters within the remote method.  However, the arguments for each remote invocation are serialized separately.  Since the remote method receives copies, if it invokes methods on its argument objects that change their state, the original argument objects in the client are unaffected.  That is, a remote method can only communicate with the caller through its return value (or exceptions), and cannot do so by modifying arguments.  Like any local variable, the server copies of the argument objects are garbage collected after the method returns.  Similarly, if the return type of a remote invocation is not a remote class, the method's return value is serialized and the caller receives a copy of the object in the remote object.  This copying also means that the RMI implementation does not need to keep the connection active while the remote method is executing.  If a parameter contains an object that is not serializable (e.g., as a collection element), an exception is signaled.

The term marshalling refers to the process of translating arguments and return values into a form that can be transmitted over a network.  For RMI, this means serializing non-remote objects, or determining a remote object identifier from the object or its stub for remote objects.  For RMI, unmarshalling refers to deserializing non-remote objects and for a remote object identifier, obtaining a reference to the object on its machine or to a stub on another machine and loading and instantiating the stub class if necessary.

The class MarshalledObject can be used as the parameter type of a remote method to allow the method to delay or avoid unmarshalling that argument.  This is useful when a complex object is passed through a chain of remote invocations, but is not needed in intermediate steps.  The MarshalledObject constructor takes an object, which it serializes to a byte stream.  The object is then accessed when necessary via get .

1.4 "At most once" semantics

RMI implements "at most once" semantics (some other distributed systems provide "at least once" semantics).  If a method returns normally, it is guaranteed to have executed once.  Hoever, if an exception such as a MarshallException occurs, the caller doesn't know whether the exception occurred when transmitting the call or returning the return value, i.e. the caller cannot determine whether the remote method executed.  In this case, the client may wish to attempt the invocation again.  To facilitate this, the implementation of a remote method should be idempotent where possible, which means that the operation can be executed multiple times with the same effect as executing once.  Even a method such as an account withdrawal can be made idempotent by passing an operation ID with the invocation: the remote object ignores invocations for operation IDs that have already been performed.

1.5 rmi packages

The package java.rmi defines the marker interface Remote , the classes Naming , RMISecurityManager , MarshalledObject , and RemoteException and several more specific subclasses of RemoteException .   Remote is a marker interface that identifies remote interfaces.  It is often necessary to download class files when using RMI, and the RMI class loader will not load class files from remote sites if no security manager has been set.   RMISecurityManager provides a basic security manager for this purpose.  MarshalledObject was discussed previously.  Clients only need to import the package java.rmi , to access Remote, RemoteException and Naming .

The package  java.rmi.server defines the classes RemoteObject, RemoteStub , RemoteServer , UnicastRemoteObject , and several interfaces and classes used by server classes and the RMI infrastructure (e.g., a server class loader, remote and server references, and a socket factory).  RemoteObject extends Object and implements Remote .  RemoteServer is a subclass of RemoteObject which is the ancestor of all remote classes, and has two subclasses, UnicastRemoteObject and Activatable (discussed in the next paragraph).  UnicastRemoteObject is the ancestor for classes that define a singleton remote object and provides support for remote invocation and parameter passing using TCP streams.  RemoteStub is a subclass of RemoteObject which is the ancestor of all stub classes, and provides the framework for invoking remote methods and parameter marshalling.  Server implementation classes that are not activable extend UnicastRemoteObject and import this package.

The package  java.rmi.activation defines the class Activatable and several classes and exceptions used in RMI Object Activation.  This mechanism supports defining a remote object that can be activated by the system if it is not running when a remote client invokes one of its methods.  The package java.rmi.registry defines interfaces and classes for the RMI registry service, which keeps a database of remote objects so that objects on other hosts can access them by name.   java.rmi.dgc defines interfaces and classes for distributed garbage collection.

2. Writing distributed application

2.1 Programming and deployment

As always, the programmers must write code, build the application, and deploy it.  Each of these steps has additional substeps in the case of a distributed application.  The code consists of one or more remote classes and client classes that access them.  (The separation of application logic into server and client is usually straightforward, but reorganizing which objects and operations are on a server and which are on a client, e.g. to improve performance, can be tedious.)  Each remote object is defined by both a remote interface and a separate server implementation class.

In addition to compiling the server remote interfaces and implementation classes and the client classes, you must generate the stub for each remote interface with the JDK program rmic (the RMI stub compiler).  The command line argument is the qualified server implementation class file name, i.e. you must compile the server class before using rmic.  For a server implementation class ServerImpl, rmic generates a class file called ServerImpl_Stub.class , which is in the same package as ServerImpl.  The stub class implements only the remote interfaces of the corresponding server class.  If you forget to run rmic or the necessary classes are not in the class path, a remote invocation generates StubNotFoundException .

To deploy the application, the class files for the remote interfaces and the stub classes to clients must be distributed to clients. (We will discuss using the "codebase" facility to avoid manually distributing classes below.)  To execute the application, you must run three programs:
As we will discuss below, the RMI registry is a look-up service that registers remote objects and allows clients to locate them by name.  To run the RMI registry, execute rmiregistry & on Unix or start rmiregistry on MS-Windows.  rmiregistry uses port 1099 by default, but you can assign another port on the command line.

2.2 The interface Remote

The marker interface Remote identifies an interface implemented by a remote class and a stub proxy class, but does not define any methods.  A remote interface extends the interface Remote , and each method must list RemoteException in its exception specification (this requirement is not enforced by the Java compiler, but by rmic ).  All parameter and return types for the methods must be serializable or remote.  Remote parameters must use the remote interface type, not the server implementation class type (because that remote parameter refers to a stub, not to an instance of the server implementation class), and this is true of any remote reference within the object graphs being transmitted.  Remote interfaces must have public, not package-level access.  Clients use the interface as the type of remote object references (not the class that implements it).  The following is an example remote interface:
   package edu.uic.cs.cs441;
import java.rmi.*;
public interface CoolService extends Remote {
boolean isCool(URL) throws RemoteException;
URL[] getCoolSites(String[] keywords) throws RemoteException;
}

2.3 Naming services

A client must obtain its initial remote reference from a naming service , which provides access to remote objects by name.  Server programs register remote objects and their names with the naming service and clients access them there by their names.  The class Naming represents the RMI registry, which provides a "flat", non-hierarchical name structure.  (Naming services that implement the JNDI interface provide a heirarchical name structure, and can also be used with RMI.)  With the class Naming , remote objects are identified by a URL of the form rmi://host:port/objectName , where host and port are the host and port on which the desired RMI registry is running.  The rmi: , //host , and :port/ are optional: the host defaults to the local host and the default port number is 1099.  To avoid name collisions, it is recommended that the objectName for a remote object be the qualified name (i.e., including the package) of the remote interface, since that name is certainly know to client code.

All the methods in Naming are class methods:
   public static void bind(String name, Remote object)
throws AlreadyBoundException, MalformedURLException, AccessException, RemoteException
public static void rebind(String name, Remote object)
throws MalformedURLException, AccessException, RemoteException
public static void unbind(String name, Remote object)
throws NotBoundException, MalformedURLException, AccessException, RemoteException
public static Remote lookup(String name)
throws NotBoundException, MalformedURLException, AccessException, RemoteException
public static String[] list(String name)
throws MalformedURLException, AccessException, RemoteException
Server classes use the methods bind and rebind (which replaces any existing binding for the given name) to register remote objects (typically, in the main method), and use unbind to remove a binding from the registry.  Since the default values for the host and port are correct, it is better practice to pass the name alone (i.e., the qualified remote interface name) to (re)bind.  The binding methods can only be called on the same host given in the URL (if one is given), and throw AccessException if this is violated (which is another reason not to give a host in the name URL).  However, this does not mean that a remote object can only be listed with an RMI registry on the host on which it is executing: a remote reference to the object can be passed to the host on which the RMI registry is running and then bound.  It is possible to register the same remote object with more than one name, or to register the same object with RMI registries on multiple hosts, if desired.  Clients use the method lookup to access remote objects by name, and must specify the host in the name URL (and the port if it is not the default).  The method list returns an array of strings giving the URLs of all the registered objects.

An alternative to using the class Naming is to use the remote interface Registry.  This interface defines the same methods as Naming.  However because the host and port are given when accessing the registry initially, the remote object name only is used with bind and lookup , rather than a complete URL.  Similarly, Registry.list returns the remote object names, while Naming.list returns full URLs.  For this reason, the Registry methods do not throw MalformedURLException .  You obtain a reference to an implementor of Registry (i.e., a stub) with the class method LocateRegistry.getRegistry , which takes an optional host name and port (again, the host defaults to the local host and the default port number is 1099).  In fact, the implementations of the Naming methods use the host and port from the argument to call LocateRegistry.getRegistry to obtain the specified registry, and then call the corresponding Registry methods.

An alternative to running the RMI registry program from the JDK is to use the class method LocateRegistry.createRegistry , which creates and exports a registery remote object that will exit when the program terminates.  This method allows specifying a different port number and socket factory for the registry.

Naming services that support the Java Naming and Directory Interface (JNDI) or the Jini Discovery and Lookup service can also be used for RMI naming services.  These have the advantages over the RMI registry that they provide a hierarchical name space for locating remote objects and implement persistent access to services.  (When a RMI registry exits and is restarted, it no longer has references to the remote objects that had been registered.)  Examples of services that support JNDI include the RMI registry, the CORBA COS Naming service, the Lightweight Directory Access Protocol (LDAP), and Novell's Network Directory Service (NDS).  We will discuss the CORBA COS Naming service later in the course.

2.4 Defining a server class

The simplest type of remote object is a transient, non-replicated, "unicast" (point-to-point, as opposed to broadcast or multicast) server.  Such a server class implements one or more remote interfaces and extends UnicastRemoteObject.  For example, a server class could implement a remote user interface and a remote administration interface.  The server class can implement non-remote interfaces and define methods that are not in its remote interfaces, but they are only available on its virtual machine.  If methods invoked by clients can change the state of the server object, those methods should be declared as synchronized because several clients can use the service simultaneously.

UnicastRemoteObject supports creating a single instance and registering it with the remote naming service.  Stubs that refer to that object are only valid for the life of the process that creates the object.  The UnicastRemoteObject constructor (which is invoked by the super call in the server constructor) "exports" the object, i.e. registers it with the RMI system.  This process creates a ServerSocket bound to an anonymous port known to the registry which is used to communicate with client stubs, and invokes accept to start a thread that listens for connections.  (In fact, the RMI specification states that a new listening port is created only when necessary and ports can be shared among remote objects.)  The UnicastRemoteObject constructor can signal RemoteException , so the remote server’s constructor must also include that exception in its exception specification.  That is, even if the only constructor for the server implementation class is a no-argument constructor that simply calls super, it must be explicitly coded so that the exception specification is given.  There are also UnicastRemoteObject constructors that support specifying a port and server and client socket factories.  A server class can also be defined as a subclass of RemoteServer or an implementor of Remote, in which case it uses the class method UnicastRemoteObject.exportObject to export itself.  UnicastRemoteObject’s superclass RemoteServer defines the methods getLog and setLog, which access a stream for logging RMI calls.

A remote object is automatically "unexported", i.e. de-registered from the RMI system, when it is garbage collected locally, which can only occur when all its clients' stubs are garbage collected.  This notification is handled by the RMI Distributed Garbage Collection subsystem.  If the desginer of a remote object wishes to perform some special processing when the object has no clients, the server class can implement the interface Unreferenced, which defines a single method unreferenced, a callback which is invoked by the DCG when the lists of clients is empty.  However, there is no direct callback when a client is added.

The server class main method installs a security manager, creates the single instance, and registers that object with the RMI registry.  Any program that accesses class files via an RMI codebase must set a security manager to check the classes that will be loaded.  The following example illustrates a skeletal server class:
   package edu.uic.cs.cs441;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import java.io.IOException;

public class CoolImpl extends UnicastRemoteObject implements CoolService {
private static CoolImpl coolObj;

public static void main(String[] args) {
System.setSecurityManager(new RMISecurityManager());
try {
coolObj = new CoolImpl(...);
Naming.rebind(“edu.uic.cs.cs441.CoolService”, coolObj);
}
catch (IOException ex) {
System.out.println(“Could not register coolService”);
System.exit(1);
}
}

private CoolImpl() throws RemoteException {
// exports the object
super();
// ... build the table of cool sites ...
}

public boolean isCool(URL url) throws RemoteException {
// ... lookup url in the table ...
}

public URL[] getCoolSites(String[] keywords) throws RemoteException {
// ... get the cool sites for the given keywords ...
}
}
The class CoolImpl may define additional methods that are not in the CoolService interface (e.g., for adding and removing URLs to its list of cool sites), but these are not available to clients.  Even though main returns after registering the remote object, the thread that accepts connections which is started by the super call in the CoolImpl constructor continues execution.  Like a server socket, we must use the operating system’s “kill process” command to stop the server.

2.5 Accessing remote objects in clients

Clients find the remote object from the Java RMI registry via Naming.lookup, or can obtain a remote reference as the return value of a remote invocation.  For example:
   package edu.uic.cs.cs441;
import java.rmi.Naming;

public class Client {
public static void main(String[] args) {
System.setSecurityManager(new RMISecurityManager());
try {
CoolService cool = (CoolService) Naming.lookup(“rmi://cool.org/edu.uic.cs.cs441.coolService”);
boolean test = cool.isCool(new URL(“www.hotwired.com”));
}
catch(IOException ex) {
System.out.println(“Not so cool!\n” + ex.getMessage());
}
}
}
The method Naming.lookup returns the type Remote, so we must cast the result.  The type of the variable cool must be the remote interface CoolService, not the server class that implements it, because it refers to an instance of the stub class that acts as a proxy for the remote CoolImpl object.  Note that the syntax for the remote call is the same as for any message.  Naming.lookup can signal both RemoteException and MalformedURLException, which are subclasses of IOException.

For applet clients, the security manager only lets an applet communicate with and load classes from the host directory that delivered the page, so the remote interface and stub class files must be there, and the remote object and the RMI registry must be running there.  Note that Internet Explorer does not (and, apparently, will not) support RMI.

In a sense, remote method invocation is deceptively simple for clients.  In practice, it is a major design decision as to what action the client should take when a remote invocation fails: Should the operation be retried?  If so, how many times?  Should the client make an attempt to determine whether the method actually executed?  A robust application must handle these issues.

2.6 Code mobility

Having the necessary class files on each machine in an RMI system can be complex, particularly if clients are widely distributed or are not known in advance.  In addition to distributing stub and parameter classes to clients, other class loading issues can occur.  For example, the actual type of an argument may be a subclass of the parameter type, and that class might not be loaded on the virtual machine to which the object is being passed.  (Recall that serializing an object does not include the object's class.)  Similarly, a collection passed as an argument might contain objects whose classes are not loaded.

To simplify these issues, RMI supports loading class files from FTP or HTTP servers via a codebase.  The value of the system property java.rmi.server.codebase specifies a list of file: , ftp: , or http: URLs that supply classes for invocation of remote objects on this virtual machine.  Via a codebase, an application can set up a location or a set of locations from which clients can load the classes necessary to perform remote method invocations.  System properties are typically set with the -D command line option.  Here are two examples for an HTTP server called codeServer.org running on the default HTTP port, 80, the first for a directory of class files, the second for a jar file:
   -Djava.rmi.server.codebase=http://codeServer.org/edu/
-Djava.rmi.server.codebase=http://codeServer.org/coolServices.jar
These properties would typically be set on the command line that starts the server program.  When a client first accesses a remote object, a remote object identifier is returned, which includes the server codebase location.  If the client virtual machine can find the stub class in its classpath, it loads the class locally.  If not, it uses the RMIClassLoader to access the class from the HTTP server given by the codebase property.  Similary, when RMI serializes an object, it annotates the serialized object with the codebase URL, which is used during deserialization if the object's class is not on the classpath.  This process is similar to the virtual machine within a browser loading an applet class and the classes it requires from a codebase.  As stated above, a security manager must be running to enable downloading code from a codebase.  If the codebase is not set or set incorrectly, a ClassNotFoundException wrapped in an UnmarshalException is signaled when the virtual machine attempts to load the class.

To use this facility to distribute classes from the server, you must be careful when running rmiregistry.   If the RMI registry program can find your the server classes in its classpath, it will load them from the classpath when it needs them and ignore its java.rmi.server.codebase property.  This will result in clients not being able to download those classes because they will not be identified as having come from that codebase in the stream sent to the client.  Therefore, before running rmiregistry, set the java.rmi.server.codebase property to the URL of a FTP or HTTP server that provides the class files, and ensure that the application's classes are not on the classpath.

It is also possible that a remote object may need to load classes from a client, e.g. if the client passes an argument that is a subclass of a method parameter type.  In this case, the client must run a FTP or HTTP server and specify the URL in its java.rmi.server.codebase property.