Distributed Objects Programming
Using Java RMI
Qusay H. Mahmoud
Introduction
Developing Client/Server applications using sockets involves the design of a protocol that consists of a
language agreed upon by the client and server. The design of protocols is hard and error-prone. One issue,
for example, is deadlock. In a deadlock, processes never finish executing; these processes may be holding
system resources, preventing other processes from accessing these resources.
Instead of working directly with Sockets, Client/Server applications can be developed
using Java's Remote Method Invocation. Java RMI is a package that can be used to build
distributed systems. It allows you to invoke methods on other Java Virtual Machines
(possibly on different hosts). The RMI system is very similar to (but more general and
easier to use) than the Remote Procedure Call (RPC) mechanisms found on other
systems, in that the programmer has the illusion of calling a local method from a local
class, where in fact all the arguments are shipped to the remote target, interpreted, and
results are sent back to the callers.
One distinguishing aspect of RMI is its simplicity. The set of features supported by RMI
are those that are most valuable for building distributed applications, namely: transparent
invocations, distributed garbage collection, convenient access to streams. Remote
invocations are transparent since they are identical to local ones, so their method
signature is identical. Distributed garbage collection is an interesting feature of the
JavaRMI system!
The goals of the system as described in the Java Remote Method Invocation
Specification, Prebeta Draft, Revision 1.1

Support seamless remote invocations on objects in different Java Virtual Machines

Support callbacks from servers to clients

Integrate the distributed object model into the Java language in a natural way while retaining most
of the Java language's object semantics

Make differences between the distributed object model and the local Java object model apparent.
Passing objects by reference vs. passing objects by copying

make writing reliable distributed applications as simple as possible

Preserve the safety provided by the Java runtime system
RMI and the OSI Reference Model
The OSI Reference Model defines a framework that consists of seven layers of network communication.
The figure below shows how RMI can be described by this model.
The User's application is at the top layer, it uses a data representation scheme to transparently communicate
with remote objects, possibly on other Java Virtual Machine hosts.
The RMI system itself consists of three layers:

The Stubs/Skeletons Layer

The Remote Reference Layer

The Transport Layer
The Stubs/Skeletons Layer serves as an interface between an application and the rest of the RMI system. It
is purpose is to transfer data to the Remote Reference Layer via marshal streams. This is where Object
Serialization comes in to play by enabling Java objects to be transmitted between address spaces. The
Remote Reference Layer is responsible for carrying out the semantics of the invocation, it transmits data to
the Transport Layer using connection-oriented streams - i.e. TCP (rather than UDP). The Transport Layer
in the current RMI implementation is TCP-based, but a UDP-based tarnsport layer could be substituted.
Finally, the Transport Layer is responsible for setting up connections, and managing those connections.
Developing Client-Server applications using JavaRMI
Writing Client-Server applications using JavaRMI involves six simple steps:
1.
Defining a remote interface
2.
Implementing the remote interface
3.
Writing an application (or an applet) that uses the remote objects
4.
Generating stubs (client proxies) and skeletons (server entities)
5.
Starting the registry
6.
Running the server and client
Let's look at each step by developing a simple application as an example to help us in understanding each
step. The sample application is an arithmetic server. The arithmetic server will perform four mathematical
operations on arrays of integers: addition, subtraction, multiplication, and division. Each of these operations
takes two arrays of 10 integers as arguments and returns an array after performing the operation.
The first step is to define a remote interface for the remote objects. Why do we need an
interface? The programmer of a client should be able to tell what operations the
arithmetic server provides and how to use these operations just by looking at the interface
we define.
Defining the remote interface
First, the remote interface for the arithmetic server is:
// File: Arith.java
public interface Arith extends java.rmi.Remote {
int[] add_arrays(int a[], int b[]) throws java.rmi.RemoteException;
int[] sub_arrays(int a[], int b[]) throws java.rmi.RemoteException;
/* ...
* and the same goes for mul_arrays and div_array
*/
}
Note: the remote interface must be declared public, otherwise clients won't be able to
load remote objects that implement that remote interface. The remote interface extends
Remote. This is to fulfill the requirement for making the class a remote object. Also, note
that each method must throws java.rmi.RemoteException.
Now we can compile our interface:
% javac Arith.java
Implementing the remote interface
The second step in the development life cycle is implementing the remote interface.
// File: ArithImpl.java
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
//We are extending UnicastRemoteObject to indicate that ArithImpl is
//being used to create a single remote object that uses RMI's default
//communication transport.
public class ArithImpl extends UnicastRemoteObject implements Arith {
private String name;
// constructor for the remote object
// it must throw java.rmi.RemoteException
public ArithImpl(String s) throws RemoteException {
// Java constructs the super class before the class
super();
name = s;
}
// implementation for the remote object add_array
// Note that remote objects are passed by reference
public int[] add_array(int a[], int b[]) throws RemoteException {
int c[] = new int[10];
for (int i=0; i<10; i++) {
c[i] = a[i] + b[i];
}
return c;
}
Note that the main() method needs to create and install a security manager. You can
either use the default - RMISecurityManager, or one that you define yourself. The role of
the security manager is to protect the host from malicious code from the client.
public static void main(String argv[]) {
System.setSecurityManager(new RMISecurityManager());
try {
// create an instance of the remote object
// once it is created it is ready to listen for client's requests
ArithImpl obj = new ArithImpl("ArithServer");
// register the remote object
// replace MachineName with the machine you're going to run the server
and rmiregistry on.
Naming.rebind("//MachineName/ArithServer", obj);
System.out.println("ArithServer bound in registry");
} catch (Exception e) {
System.out.println("ArithImpl err: " + e.getMessage());
e.printStackTrace();
}
}
}
We can now compile our ArithImpl.java file:
% javac ArithImpl.java
Writing a client application that uses the above interface
This program remotely invokes any of the ArithServer's methods, in this case we have only implemented
add_array. Once the client invokes that operation, the client should receive some output - the sum of two
arrays. The code for the client looks as follows:
// File: ArithApp.java
import java.rmi.*;
import java.net.*;
public class ArithApp {
public static String localHost() throws Exception {
InetAddress host = null;
host = InetAddress.getLocalHost();
return host.getHostName();
}
public static void main(String argv[]) {
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 9};
int b[] = {2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
int result[] = new int[10];
try {
// we need to get a refernece to the "ArithServer" from the server's
// registry.
Arith obj = (Arith)Naming.lookup("//" + localHost() +
"/ArithServer");
// next, we invoke the remote object add_array and save the return value
result = obj.add_array(a, b);
} catch (Exception e) {
System.out.println("ArithApp exception:"+e.getMessage());
e.printStackTrace();
}
// print the saved results on the client's screen
System.out.println("The sum of the arrays is: ");
for (int j=0; j<10; j++) {
System.out.print(result[j]+"
");
}
}
}
Now, we can compile our client:
% javac ArithApp.java
Generate Stubs and Skeletons
Now we are ready to generate the stubs and skeletons. RMI stubs and skeletons are
generated by the rmic compiler. A stub is a client-side proxy and a skeleton is a serverside entity that disptaches calls to the actual remote object implementation. Stubs and
skeletons are determined and dynamically loaded as needed at run time. To generate the
needed stubs and skeletons for our applications we type:
% rmic ArithImpl
This will generate the files: ArithImpl_Skel.class and ArithImpl_Stub.class. Note that
your CLASSPATH should include a pointer to where all these classes we are compiling
are stored.
Before running our client and server application, we need to run the RMI registry, which
is a name server that allows clients to obtain a reference to a remote object.
Running the rmiregistry
In a UNIX environment, he RMI registry can be started as follows:
% rmiregistry &
The registry, by default, runs on port 1099. If you would like to start the RMI registry on
a different port number, specify the port on the command line, i.e. rmiregistry port &,
where port is the port number you want to start the registry on. Note that if you start the
registry on a port number other than the default, you will then have to specify the port
number when you bind it - i.e., instead of saying:
Naming.rebind("//MachineName/ArithServer", obj); you will need to say:
Naming.rebind("//MachineName/ArithServer:port", obj); where port is the port
number on which you are running the rmiregistry.
Running the client and server
Now we are ready to start our server and client. The server process can be started by
typing:
% java ArithImpl &
Finally, we can run our client, by typing:
% java ArithApp
and this should result in the following output:
The sum of the arrays is:
3
4
5
6
7
8
9
10
11
11
If you look at the client's code you will note that our two arrays were:
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 9};
int b[] = {2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
Now, if you look at the results we got when we ran the client, you will note that the operation was
performed correctly. We are getting correct results. Test it for yourself! :-)
Conclusion:
Developing Client/Server applications using sockets involves the design of a protocol
that consists of a language agreed upon by the client and server. The design of protocols
is hard and error-prone. Using Java Remote Method Invocation or JavaRMI, developing
client-server applications is straight forward because remote invocations in RMI are
identical to local ones, so their method signature is identical.
About the author:
Qusay H. Mahmoud is a graduate student in Computer Science at the University of New
Brunswick, Saint John, Canada. This term he is teaching a course on Multimedia and the
Information Highway at the university. As part of his thesis, he is developing a Webbased distributed computing system using Java. You can reach him at:
qusay@scs.carleton.ca
Copyright (c)1997 Qusay H. Mahmoud. All rights reserved.