RMI allows Java objects to invoke methods on remote Java objects located in another JVM. It uses stubs and skeletons as proxies for remote objects. The client calls methods on the local stub which forwards the call to the remote skeleton which then calls methods on the actual remote object. Objects must implement the Remote interface and are registered with an RMI registry for lookup. The server implements the remote interface and registers objects with the registry, while the client looks up remote objects from the registry to invoke methods on them.
VICTOR MAESTRE RAMIREZ - Planetary Defender on NASA's Double Asteroid Redirec...
Module 3 remote method invocation-2
1. Remote Method Invocation (RMI)
Overview
1. What is RMI?
Remoting:
RMI is a lightweight Java technology that provides access to remote
methods, similar to RPC, but object-oriented. RMI basically provides remote
object access for a client and object registration for servers.
API and transport protocol:
RMI is both a Java API (java.rmi.* package) as well as a
transport protocol definition for transporting RMI calls through a
network (JRMI, see below).
Java technology:
RMI is a Java technology since it requires that client and server objects run
in a JVM. By using IIOP as transport protocol, however, it is possible to connect
RMI-clients to non-Java server objects (CORBA, see below).
2. Important RMI Components
Client:
The client looks up a remote object and calls methods on the obtained remote
object.
Server:
The server registers itself in the RMI registry and accepts method invocations
from the client.
RMI Registry:
The registry is a remote object lookup service. The registry may run on the
same host as the server or on a different host. The registry can also be a JNDI
server.
Web Server:
A plain vanilla HTTP server may hold remote object classes for downloading by
the client.
2. RMI Architecture
There are three layers that comprise the basic remote-object communication
facilities in RMI:
1. The stub/skeleton layer, which provides the interface that client and server
application objects use to interact with each other.
2. The remote reference layer, which is the middleware between the
stub/skeleton layer and the underlying transport protocol.
3. The transport protocol layer, which is the binary data protocol that sends
remote object requests over the wire.
In the figure, the server is the application that provides remotely accessible objects,
while the client is any remote application that communicates with these server
objects.
Description of the architecture:
1. The client uses the client-side stub to make a request of the remote object.
The server object receives this request from a server-side object skeleton.
2. A client initiates an RMI invocation by calling a method on a stub object.
The stub maintains an internal reference to the remote object it represents
and forwards the method invocation request through the remote reference
layer by marshaling the method arguments into serialized form and asking
the remote reference layer to forward the method request and arguments
to the appropriate remote object.
3. Marshaling involves converting local objects into portable form so that they
can be transmitted to a remote process. Each object (e.g. a String object,
an array object, or a user defined object) is checked as it is marshaled, to
determine whether it implements the java.rmi.Remote interface. If it does,
its remote reference is used as its marshaled data. If it isn‟t a Remote
object but is rather a Serializable object, the object is serialized into bytes
that are sent to the remote host and reconstructed into a copy of the local
object. If the object is neither Remote nor Serializable, the stub throws a
java.rmi.MarshalException back to the client.
4. If the marshaling of method arguments succeeds, the client-side remote
reference layer receives the remote reference and marshaled arguments
from the stub.
3. 5. The remote reference layer converts the client request into low-level RMI
transport requests, i.e., into a single network-level request and sends it
over the wire to the sole remote object (since in Java 2 the communication
style supported is the point-to-point object references) that corresponds to
the remote reference passed along with the request.
6. On the server, the server-side remote reference layer receives the
transport-level request and converts it into a request for the server
skeleton that matches the referenced object.
7. The skeleton converts the remote request into the appropriate method call
on the actual server object. This involves unmarshaling the method
arguments into the server environment and passing them to the server
object. Arguments sent as remote references are converted into local
stubs on the server, and arguments sent as serialized objects are
converted into local copies of the originals.
8. If the method calls generates a return value or an exception, the skeleton
marshals the object for transport back to the client and forwards it through
the server reference layer.
9. The result is sent back using the appropriate transport protocol (e.g.
Socket API using TCP/IP), where it passes through the client reference
layer and stub, is unmarshaled by the stub, and is finally handed back to
the client thread that invoked the remote method.
RMI Architecture: Factory Design Pattern
4. Stub and Skeleton
RMI uses the Proxy design pattern
• Stub class is the proxy
• Remote service implementation class is the RealSubject
Skeleton is a helper class
Carries on a conversation with the stub
• Reads the parameters for the method call → makes the call to the remote
service implementation object → accepts the return value → writes the return
value back to the stub.
Proxy design pattern
Provide a surrogate or placeholder for another object to control access to it.
Client stub: Proxy object on the client for accessing the remote server object. The
client stub intercepts the calls of the client and passes it to the remote reference
layer. The stub is an object, acts as a gateway for the client side. All the outgoing
requests are routed through it. It resides at the client side and represents the remote
object. When the caller invokes method on the stub object, it does the following
tasks:
1. It initiates a connection with remote Virtual Machine (JVM),
2. It writes and transmits (marshals) the parameters to the remote Virtual
Machine (JVM),
3. It waits for the result
4. It reads (unmarshals) the return value or exception, and
5. It finally, returns the value to the caller.
Server stub/skeleton: The server stub receives calls from the remote reference
layer and passes it to the server object implementing the interface (= RealSubject in
the picture above). The skeleton is an object, acts as a gateway for the server side
object. All the incoming requests are routed through it. When the skeleton receives
the incoming request, it does the following tasks:
5. 1. It reads the parameter for the remote method
2. It invokes the method on the actual remote object, and
3. It writes and transmits (marshals) the result to the caller.
RMI Remote Interface
Any system that uses RMI will use a service interface. The service interface
defines the object methods that can be invoked remotely, and specifies
parameters, return types, and exceptions that may be thrown. Stub and skeleton
objects, as well as the RMI service, will implement this interface. For this reason,
developers are urged to define all methods in advance, and to freeze changes to
the interface once development begins.
All RMI service interfaces extend the java.rmi.Remote interface, which assists in
identifying methods that may be executed remotely. To define a new interface
for an RMI system, you must declare a new interface extending the Remote
interface. Only methods defined in a java.rmi.Remote interface (or its
subclasses) may be executed remotely—other methods of an object are hidden
from RMI clients.
For example, to define an interface for a remote lightbulb system (a high-
tech version of the traditional on-off switch for a networked world), we could
define an interface such as the following:
public interface RMILightBulb extends java.rmi.Remote
{
public void on () throws java.rmi.RemoteException; public
void off() throws java.rmi.RemoteException; public boolean
isOn() throws java.rmi.RemoteException;
}
The interface is identified as remotely accessible, by extending from the Remote
interface. Each method is marked as public, and may throw a java.
rmi.RemoteException . This is important, as network errors might occur that will
prevent the request from being issued or responded to. In an RMI client, a stub
object that implements this interface will act as a proxy to the remote system—if
the system is down, the stub will throw a RemoteException error that must be
caught. If a method is defined as part of an RMI interface, it must be marked as
able to throw a RemoteException—if it is not, stub and skeleton classes cannot
be generated by the "rmic" tool (a tool that ships with the Java SDK which
automates the generation of these classes).
Methods are not limited to throwing only a RemoteException, however. They
may throw additional exceptions that are already defined as part of the Java API
(such as an IllegalArgumentException to indicate bad method parameters), or
custom exceptions created for a system. For example, the on() method could be
modified to throw a BrokenBulb exception, if it could not be successfully
activated.
6. RMI Naming Remote Objects
RMI can use different naming and directory services such as Java Naming and
Directory Interface (JNDI). RMI includes a simple service called the RMI Registry,
rmiregistry. The RMI Registry runs on each machine that hosts remote service
objects and accepts queries for services, by default on port 1099.
The RMI registry maintains a list of „advertised names‟ for remotely-available
objects on that machine. It also stores other information about each (e.g.
listening port for each). When a servant object starts, because it inherits
functionality from the java.rmi.server.UnicastRemoteObject class, it
automatically grabs a random TCP port whose number is >1023 and starts
listening for incoming calls. In order for a client to find out which port a servant
instance is listening on, the servant must notify the registry of its presence and
port number. This is done by the server (or servant) using a call to the function:
Naming.rebind(“advertised_name”, servant_variable );
This notifies the registry that a particular servant is to be advertised with a
particular name string. Any client object which knows that name string can
inquire about it to the registry using the following call to the java.rmi.* package:
Naming.lookup(“rmi://hostname:1099/advertised_name”);
This function returns a ‘remote reference’ to the remote servant object which will
allow the client to find out everything it needs to know about the servant: it‟s port, the
server program process that it is hosted within, and even the stubs that it needs to
actually call the servant instance. In fact, the stub‟s code can even be
AUTOMATICALLY downloaded to the client if the client doesn‟t have them (even if
the client is an ocean away!). It can then dynamically link to those new stubs at run
time! This capability comes from the RMIClassLoader.
The only key thing that the client programmer must have ahead of (run) time is the
source code for the remote interface definition.
In a Java RMI client, once the remote reference and stub code is in hand, the
client can then call the remote object‟s remote methods exactly as if the remote
7. object were local! Note that it cannot call any of the remote object‟s non-remote
methods, nor the remote object class‟s static methods.
For correct execution, things must thus generally be started in this order:
1. rmiregistry.exe
2. java theServerClassName
3. java theClientClassName
RMI registry function:
The RMI registry is a server registration and client lookup service.
It may run anywhere, i.e. does not need to be co-located with the RMI server object.
Default port for RMI registry: 1099 (may run on another port as well).
Server registration:
The servers register (export) objects that implement the service
interface using bind() or rebind():
Example: RemServer localObject = new RemServer();
Naming.rebind("MyServ", localObject);
Client references:
Clients obtain references to server objects (= proxy objects) through the
RMI registry using a URL scheme (with an optional port):
URL: rmi://<host_name> [:<name_service_port>] /<service_name>
Example: RemIf remObject = (RemIf)Naming.lookup("rmi://" + host + "/MyServ");
RMI registry access to server stub classes:
The RMI registry needs to have access to the server„s stub classes. This
can be done in 2 ways (strictly either-or, using both will not work!):
1. CLASSPATH:
Add the path to server stub class files to CLASSPATH when starting the
RMI registry.
2. Codebase property:
Start the server with the codebase property. This way the server registers
the remote object along with the path to the class files. The codebase property
may be a file or HTTP URL.
Implementing an RMI Service Interface
Once a service interface is defined, the next step is to implement it. This
implementation will provide the functionality for each of the methods, and may
also define additional methods. However, only those methods defined by the
RMI service interface will be accessible remotely, even if they are marked
public in scope or as being able to throw a RemoteException.
Example
public class RMILightBulbImpl
// Extends for remote object functionality
extends java.rmi.server.UnicastRemoteObject
// Implements a light bulb RMI interface
8. implements RMILightBulb
{
// A constructor must be provided for the remote object
public RMILightBulbImpl() throws java.rmi.RemoteException
{
// Default value of off
setBulb(false);
}
// Boolean flag to maintain light bulb state information
private boolean lightOn;
// Remotely accessible "on" method - turns on the light
public void on() throws java.rmi.RemoteException
{
// Turn bulb on
setBulb (true);
}
// Remotely accessible "off" method - turns off the light
public void off() throws java.rmi.RemoteException
{
// Turn bulb off
setBulb (false);
}
// Remotely accessible "isOn" method, returns state of bulb
public boolean isOn() throws java.rmi.RemoteException
{
return getBulb();
}
// Locally accessible "setBulb" method, changes state of bulb
public void setBulb (boolean value)
{
lightOn = value;
}
// Locally accessible "getBulb" method, returns state of bulb
public boolean getBulb ()
{
return lightOn;
}
}
How RMILightBulbImpl Works
An object that can be accessed remotely must—at a minimum—extend the
java.server.RemoteObject class. However, a support class exists that provides all the
necessary functionality for exporting an object remotely. When implementing an
interface, the class should extend the java.server.UnicastRemoteOb ject class—this
saves significant time and effort and provides for simpler code.
Extending from UnicastRemoteObject is the only RMI-specific code that needs to be
written for a service implementation. As can be seen from the code, there is very little
work that needs to be done to create RMI service implementations. Methods defined
in the service interface must be implemented (or the class would fail to compile until
9. implemented or marked abstract), but beyond that there is no actual networking code
required. This means that even developers not experienced with networking could
contribute to RMI systems, although writing code for a server and client requires
somewhat more effort.
RMI Server
The RMI server is responsible for creating an instance of a service implementation
and then registering it with the remote method invocation registry (rmiregistry). This
actually amounts to only a few lines of code, and is extremely easy to do. In small
systems, the server could even be combined with the service implementation by
adding a main() method for this purpose, though a separation of classes is a cleaner
design.
Example
import java.rmi.*;
import java.rmi.server.*;
public class LightBulbServer
{
public static void main(String args[])
{
System.out.println ("Loading RMI service");
try
{
// Load the service
RMILightBulbImpl bulbService = new RMILightBulbImpl();
// Examine the service, to see where it is stored
RemoteRef location = bulbService.getRef();
System.out.println (location.remoteToString());
// Check to see if a registry was specified
String registry = "localhost";
if (args.length >=1)
{
registry = args[0];
}
// Registration format //registry_hostname :port /service
// Note the :port field is optional
String registration = "rmi://" + registry + "/RMILightBulb";
// Register with service so that clients can find us
Naming.rebind( registration, bulbService );
}
catch (RemoteException re)
{
System.err.println ("Remote Error - " + re);
}
catch (Exception e)
{
System.err.println ("Error - " + e);
}
}
10. }
How LightBulbServer Works
The lightbulb server defines a single method, main, which allows it to be run as an
application. The application encloses almost all of the code in a try { .. } catch block,
since networking errors that occur at runtime must be caught. The most likely error to
occur is for a RemoteException to be thrown, if the service could not be started or if
the server is unable to register with the rmiregistry.
public static void main(String args[])
{
System.out.println ("Loading RMI service");
try
{
....
}
catch (RemoteException re)
{
System.err.println ("Remote Error - " + re);
}
catch (Exception e)
{
System.err.println ("Error - " + e);
}
}
The next step is to create an instance of the RMI lightbulb service defined by the
RMILightBulbImpl class. The constructor for this class takes no parameters and is
simple to invoke. Once the service has been created, the application obtains a
remote reference to the newly created lightbulb service and displays its contents so
that it is possible to see exactly where the service is located. Each service binds to a
local TCP port through which it is located, and the reference is composed of the
hostname and port of the service.
// Load the service
RMILightBulbImpl bulbService = new RMILightBulbImpl();
// Examine the service, to see where it is stored
RemoteRef location = bulbService.getRef();
System.out.println (location.remoteToString());
The final step is to register the service with the rmiregistry, so that other clients can
access it. This registry could be located on any computer on the local network, or on
the Internet, or it could be found on the current host. By default, the server will
attempt a registration on the local machine, but if the hostname of a registry is
specified on the command line, this setting will be overridden.
// Check to see if a registry was specified
String registry = "localhost";
if (args.length >=1)
{
11. registry = args[0];
}
// Registration format //registry_hostname:port /service
// Note the :port field is optional
String registration = "rmi://" + registry + "/RMILightBulb";
// Register with service so that clients can find us
Naming.rebind( registration, bulbService );
The registration details include the location of the registry, followed by an optional
port and then the name of the service. Using static methods of the class
java.rmi.Naming, which is responsible for retrieving and placing remote object
references in the registry, we register the service so that clients can access it. The
details, represented by our registration string, are passed to the Naming. rebind(..)
method, which accepts as a parameter a registration name and an instance of the
java.rmi.Remote interface. We pass the service created earlier, and the service
becomes bound to the registry. The binding process creates an entry in the registry
for clients to locate a particular service, so that if the service is shut down, it should
of course unbind itself. If no errors occur, the service is then available for remote
method invocation.
RMI Client
Writing a client that uses an RMI service is easy compared with writing the service
itself. The client needs only to obtain an object reference to the remote interface, and
doesn't need to be concerned with how messages are sent or received or the
location of the service. To find the service initially, a lookup in the RMI registry is
made, and after that, the client can invoke methods of the service interface just as if
it were a local object. The next example demonstrates how to turn a remote lightbulb
on and off.
Example
import java.rmi.*;
public class LightBulbClient
{
public static void main(String args[])
{
System.out.println ("Looking for light bulb service");
try
{
// Check to see if a registry was specified
String registry = "localhost";
if (args.length >=1)
{
registry = args[0];
}
// Registration format //registry_hostname (optional):port /service
String registration = "rmi://" + registry + "/RMILightBulb";
// Lookup the service in the registry, and obtain // a remote service
Remote remoteService = Naming.lookup ( registration );
// Cast to a RMILightBulb interface
RMILightBulb bulbService = (RMILightBulb) remoteService;
12. // Turn it on
System.out.println ("Invoking bulbservice.on()");
bulbService.on();
// See if bulb has changed
System.out.println ("Bulb state : " + bulbService.isOn() );
// Conserve power
System.out.println ("Invoking bulbservice.off()");
bulbService.off();
// See if bulb has changed
System.out.println ("Bulb state : " + bulbService.isOn() );
}
catch (NotBoundException nbe)
{
System.out.println ("No light bulb service available in registry!");
}
catch (RemoteException re)
{
System.out.println ("RMI Error - " + re);
}
catch (Exception e)
{
System.out.println ("Error - " + e);
}
}
}
How LightBulbClient Works
As in the lightbulb server, the client application must be careful to catch any
exceptions thrown at runtime, if the server or rmiregistry is unavailable. It also needs
to create a URL to the registry and service name, which by default will point to the
local machine, but can be overridden by a command-line parameter. The code for
this is similar to that in the server, and for this reason is not repeated here. Once a
URL to the registry entry has been created, the application attempts to look up the
location of the service and obtain an object reference to it, by using the
Naming.lookup(String) method. An explicit cast is made to the RMILightBulb
interface; if no such interface is found, a NotBoundException will be thrown and
caught.
// Lookup the service in the registry, and obtain a remote
// service
Remote remoteService = Naming.lookup ( registration );
// Cast to a RMILightBulb interface
RMILightBulb bulbService = (RMILightBulb) remoteService;
Here the networking code ends and the service code begins. Three methods were
defined in the service interface:
• public void RMILightBulb.on()
• public void RMILightBulb.off()
• public boolean RMILightBulb.isOn()
13. Each of the three methods is tested, and the changes in bulb state displayed to the
user. Once this
is done, the application will terminate, its task complete.
Client Server Application development using RMI
Steps for developing RMI applications are as follows:
1. Defining the remote interface.
2. Implementing the remote interface.
3. Writing the code for registering the object.
4. Writing the client that uses the remote objects.
5. Generating stubs (client proxies) and skeletons (server entities).
6. Running the RMI Registry, server and client.
1. Defining the Remote Interface
An interface manifests the exposed operations and the client programmer need not
be aware of the implementation (the interface in this case also serves as a marker to
the JVM). A remote Interface by definition is the set of methods that can be invoked
remotely by a client:
The remote interface must be declared public or the client will get an error
when it tries to load a remote object that implements the remote interface.
The remote interface must extend the java.rmi.Remote interface.
Each method must throw a java.rmi.RemoteException (or a superclass of
RemoteException)
If the remote methods have any remote objects as parameters or return types,
they must be interface types not the implementation classes.
Example: The example discussed in the following sections illustrates how to define
and invoke methods on a remote object. We will define our Remote Interface
(HelloInterface.java) as follows:
2. Implementing the Remote Interface
The implementing class is the actual class that provides the implementation for
methods defined in the remote interface. The java.rmi.server.RemoteObject extends
the functionality provided by the java.lang.Object class into the remote domain by
overriding the equals(), hashcode() and toString() methods. The generic
java.rmi.server.RemoteObject is an abstract class and describes the behavior of
remote objects.
14. The abstract subclass java.rmi.server.RemoteServer describes the behavior
associated with the server implementation and provides the basic semantics to
support remote references.
java.rmi.RemoteServer has two concrete sub-classes
java.rmi.server.UnicastRemoteObject
It designs a non-replicated remote object whose reference is valid only
when the server is alive.
java.rmi.activation.Activatable
It is the concrete class that defines behavior for on demand
instantiation of remote objects.
In addition to one of the above classes, the class corresponding to the remote
interface must implement one or more interfaces that define the remote methods. A
remote class can define any methods but only methods in the remote interface can
be invoked remotely.
15. 3. Registering the Remote Object
Now that we have the interface and the implementation, we need to make this object
available to clients by binding it to a registry. This will allow the clients to look the
object up on the host by a String name.
The stubs and skeletons (if any) are needed for registration. After all it is the object
stub that is going to be passed around from the registry to the clients.
16. 4. Writing the Client that uses the Remote Object
The client performs a lookup on the registry on the host and obtains a reference to
the remote object. Note that casting to the remote object is critical. In RMI, clients
always interact with the interface, never with the object implementation.
17. 5. Generating Stubs and Skeletons
Now that we have the remote interface and implementation, we can generate the
skeletons and stubs (or only stubs in java 1.2 or higher version) with the rmic tool
after we have compiled the classes. Remember to set the directory that you are
working from in your classpath; you can then use the following line in a command
window:
rmic -v1.2 HelloServer
This will generate stub class “HelloServer_Stub.class”.
Note: It is worth keeping in mind that while starting the registry all classes and stubs
must be available in the classpath or the classpath should not be set at all, to
support dynamic loading.
6. Running the RMI Registry, Client and the Server
The steps for running the RMI application are:
1. Start RMI Registry: Ensure that the following classes are present in the current
folder or the classpath:
• HelloInterface.class
• HelloServer_Stub.class
Start the RMI Registry by giving the command:
rmiregistry
2. Start Server: Ensure that the following classes are in the current folder or the
classpath:
• HelloInterface.class
• HelloServer.class
• HelloServer_Stub.class
• MyRMI.class
18. Run the MyRMI program.
D:sumit miin>java -Djava.rmi.server.codebase="file:///d:/sumit/rmi/bin/" org
.test.MyRMI
3. Run Client: Ensure that the following classes are in the current folder or the
classpath:
• HelloInterface.class
• HelloServer_Stub.class
• HelloClient.class
Run the HelloClient program :)