Remote method invocation (RMI) is the action of invoking a method of a remote interface on a remote object. Most importantly, a method invocation on a remote object has the same syntax as a method invocation on a local object.
As we have seen, Java sockets, combined with serialization makes is it easy to send objects across the network, however, sockets require the programmer to encode and decode the messages that are being exchanged. As the application facilities grow, so do the complexities in communication.
RMI is a full architecture for distributed computing. It provides a mechanism for distributing objects as services, where a remote service request looks similar to a local one. The object is not passed to and from the client/server, rather it is fixed in the one place on the server. The Virtual Machine that is responsible for the object declares it exportable and makes it available to an object server that can call on it when a request is received.
The remote client obtains a reference to the object and calls methods on it. To obtain this reference to the object, the client must know the name of the object, where it is and also what methods it has. Once this information is known, RMI takes care of the passing of information back and forth, tasks such as: object serialization, object transport, exception handling (Exceptions may occur when network connections are broken, one of the client/server pair may crash etc..) and security management.
To use a remote object, we first must have a reference to it. An application can register its
remote objects with RMI's simple naming facility, the rmiregistry
, or the
application can pass and return remote object references as part of its normal operation
The only concise way to do
this is to have lookup information available (such as the method names) to the client, generally
prior to run-time. This is facilitated through the use of a pair of classes, termed
skeletons and stubs, that are derived directly from the
remote object.
The client calls the remote object by using this lookup information, which is coded into the client. If the lookup succeeds then the RMI Server could return the remote object's stub, which acts as a stand-in for the remote object's methods. A call to any of the stub's methods, is sent from the stub to the skeleton reference, that resides on the server-side. The skeleton in effect calls the method on the server-side and routes the remote object's response back through to the stub.
The stub for a remote object acts as a client's local representative or proxy for the remote object. The caller invokes a method on the local stub which is responsible for carrying out the method call on the remote object. In RMI, a stub for a remote object implements the same set of remote interfaces that a remote object implements. When a stub's method is invoked, it:
initiates a connection with the remote JVM containing the remote object,
marshals (writes and transmits) the parameters to the remote JVM,
waits for the result of the method invocation,
unmarshals (reads) the return value or exception returned, and,
returns the value to the caller.
In the remote JVM, each remote object may have a corresponding skeleton (in Java 2 platform-only environments, skeletons are not required). The skeleton is responsible for dispatching the call to the actual remote object implementation. When a skeleton receives an incoming method invocation it does the following:
unmarshals (reads) the parameters for the remote method,
invokes the method on the actual remote object implementation, and
marshals (writes and transmits) the result (return value or exception) to the caller.
This communication takes place over the RMI remote reference layer (which relies on the TCP/IP transport layer).
Figure 13.2, “RMI Distributed Application” illustrates an RMI distributed application that uses the registry to obtain references to a remote object. The server calls the registry to associate a name with a remote object. The client looks up the remote object by its name in the server's registry and then invokes a method on it. The illustration also shows that the RMI system uses an existing web server to load bytecodes of classes written in the Java programming language, from server to client and from client to server, for objects when needed. RMI can load class bytecodes using any URL protocol (e.g., HTTP, FTP, file, etc.) that is supported by the Java platform.
Note: Non-remote arguments to, and results from, a remote method invocation are passed by copy rather than by reference. This is because references to objects are only useful within a single virtual machine.
The best way to learn is from a simple example. There are a number of steps here, but once you get used to them it is quite routine.
1 2 3 import java.rmi.*; 4 5 public interface RemoteRequest extends Remote 6 { 7 public void display(String theString) throws RemoteException; 8 } 9 10
The remote interface declaration declares the remote methods of the remote object. In this case
we have only one remote method. This is in a file called RemoteRequest.java
.
Note that it uses the java.rmi.*;
package.
Notes:
A remote interface must at least extend, either directly or indirectly, the interface
java.rmi.Remote
.
Each method declaration in a remote interface must satisfy the requirements of a remote method declaration as follows:
A remote method declaration must include the exception
java.rmi.RemoteException
(The exception
RemoteException
is thrown when a remote method invocation fails for
some reason, e.g. Communication failure (the connection is closed by the server, etc.), failure during
parameter or return value marshalling or unmarshalling, Protocol errors) in its throws clause, in
addition to any application-specific exceptions.
In a remote method declaration, a remote object declared as a parameter or return value must be declared as the remote interface, not the implementation class of that interface.
The interface java.rmi.Remote
is a marker interface that
defines no methods.
1 2 import java.rmi.*; 3 import java.rmi.server.*; 4 import java.rmi.registry.*; 5 6 public class RequestServer extends UnicastRemoteObject implements RemoteRequest 7 { 8 public RequestServer() throws RemoteException 9 { 10 super(); 11 } 12 13 public void display(String theString) throws RemoteException 14 { 15 // implementation of the display method here... 16 System.out.println("Received: "+theString); 17 } 18 19 public static void main(String args[]) 20 { 21 try { 22 LocateRegistry.createRegistry(1099); 23 RequestServer obj = new RequestServer(); 24 Naming.rebind("rmi://127.0.0.1/Request", obj); //for localhost 25 System.out.println("Request bound in registry"); 26 } 27 catch (Exception e) 28 { 29 System.out.println("Failed to bind in registry: "+e.getMessage()); 30 } 31 } 32 } 33 34
This is the server part of the system. Note that we have to import the packages
java.rmi.*
, java.rmi.server.*
and
java.rmi.registry.*
It extends the UnicastRemoteObject
object which basically allows the
Request
class to be exported and so usable outside the Virtual Machine where
it was created. The constructor calls the constructor of the UnicastRemoteObject
class and that is it. This class also provides the implementation for the
display()
method that was defined in the RemoteRequest
interface (that we just defined).
The methods needed to create remote objects and export them (make them available to remote
clients) are provided by the class UnicastRemoteObject
. The
java.rmi.server.UnicastRemoteObject
class defines a singleton
[13]
(unicast) remote object whose references are valid only while the server process is alive.
The main()
method creates a registry on port 1099 (this is the
default port for the registry services) and binds a new instance of the Request
object (obj
) to it. So, the createRegistry(int port)
method
creates and exports a registry on a local host that accepts requests on a specified port (It creates
a static Registry, i.e. The registry.). Placing an object in a registry makes it available to
clients on other virtual machines, once those clients have access to the machine, they can obtain a
reference to the remote object by specifying the machine name, port number and the name of the
exported object.
1 2 import java.rmi.*; 3 import java.net.*; 4 5 public class RMIClient 6 { 7 RemoteRequest aRequest; 8 9 public void goRequest() 10 { 11 try 12 { 13 aRequest = (RemoteRequest) 14 Naming.lookup("rmi://127.0.0.1/Request"); // for localhost 15 aRequest.display("Test"); 16 } 17 catch(MalformedURLException e) 18 { 19 System.out.println("MalformedURLException"); 20 } 21 catch(AccessException e) 22 { 23 System.out.println("AccessException"); 24 } 25 catch(RemoteException e) 26 { 27 System.out.println("RemoteException"); 28 } 29 catch(NotBoundException e) 30 { 31 System.out.println("NotBoundException"); 32 } 33 } 34 35 public static void main(String args[]) 36 { 37 RMIClient aClient = new RMIClient(); 38 aClient.goRequest(); 39 } 40 } 41
The RMIClient
object exists on client machine. Note that the
RMIClient
class is compiled with the RemoteRequest
interface.
The main()
method creates an instance of RMIClient
and calls the goRequest()
method. In this method, we call the
Naming.lookup()
method. The java.rmi.Naming
class provides
Uniform Resource Locator (URL) based methods to look up, bind, rebind, unbind, and list the name-object
pairings maintained on a particular host and port. This method retrieves a reference to the object,
specified by the URL
reference (This object is described by Remote
,
the parent of RemoteRequest
, the interface that we created) that must be
cast convereted back to a RemoteRequest
object. As no port number is specified, it is
assumed that the port number is 1099 on the machine 127.0.0.1 (you can use any IP address if
required). The next line calls the display()
method on the remote object
created on the remote system. The remainder of the code attempts to catch any exceptions that can
occur.
An argument to, or a return value from, a remote object can be any object that is
serializable. This includes primitive types, remote objects, and non-remote objects that implement
the java.io.Serializable
interface. So, when a non-remote object is passed
as an argument or return value in a remote method invocation, the content of the non-remote object is
copied before invoking the call on the remote object. When a non-remote object is returned from a remote method
invocation, a new object is created in the calling virtual machine. When passing an exported remote object as a
parameter or return value in a remote method call, the stub for that remote object is passed instead.
To compile this example do the following:
On the server side, type:
javac RequestServer.java rmic RequestServer
Running rmic creates a skeleton and a stub, called
RequestServer_Stub.class
and RequestServer_Skel.class
. Copy the stub to the
directory of the client, and run the server by typing:
java RequestServer
See Figure 13.3, “Screen Grab of the Request Server, Compilation and Execution.”
On the client side, type:
javac RMIClient.java
And run the client by typing:
java RMIClient
See Figure 13.4, “Screen Grab of the RMI Cient Application, Compilation and Execution.”
© 2006
Dr. Derek Molloy
(DCU).