This very simple example is taken from Chapter 24 of "Java 2: The Complete Reference" by P.Naughton and H.Schildt.
The example is overly simplified but it still illustrates the basic steps in creating an RMI distributed program.
This example provides step-by-step directions for building a client/server application by using RMI. The server receives a request from a client, processes it, and returns a result. In this example, the request specifies two numbers, the server adds these together and returns the sum. That is it!
(Of course, this program is intended only to illustrate the basic RMI mechanism.)
The next subsections provide main steps in writing an RMI program
All remote methods should throw a RemoteException
import java.rmi.*;
public interface AddServerIntf extends Remote {
double add(double d1, double d2) throws RemoteException;
}
import java.rmi.*; import java.rmi.server.*; public class AddServerImpl extends UnicastRemoteObject implements AddServerIntf { public AddServerImpl() throws RemoteException { } public double add(double d1, double d2) throws RemoteException { return d1 + d2; } }All remote objects must extend UnicastRemoteObject which provide the functionaly that is needed to make objects available from remote machines.
The third source file
AddServer.java contains the main program for the server
machine. Its primary function is to update the RMI registry
on that machine. This is done by using the rebind() method of the
Naming class
(it is in java.rmi API). That method associates a name with an object reference.
import java.net.*;
import java.rmi.*;
public class AddServer {
public static void main(String args[]) {
try {
AddServerImpl addServerImpl = new AddServerImpl();
Naming.rebind("AddServer", addServerImpl);
}
catch(Exception e) {
System.out.println("Exception: " + e);
}
}
}
The application forms an URL string using the rmi protocol, the first command line argument and the name "AddServer" that is used by naming registry of the server. The the program calls the method lookup() of the Naming class to find a reference to a remote object. All remote method invocations can then be directed to this object.
The client program illustrates the remote call by using
the method add(d1,d2) that will be invoked on the remote
server machine from the local client machine where the client runs.
import java.rmi.*;
public class AddClient {
public static void main(String args[]) {
try {
String addServerURL = "rmi://" + args[0] + "/AddServer";
AddServerIntf addServerIntf =
(AddServerIntf)Naming.lookup(addServerURL);
System.out.println("The first number is: " + args[1]);
double d1 = Double.valueOf(args[1]).doubleValue();
System.out.println("The second number is: " + args[2]);
double d2 = Double.valueOf(args[2]).doubleValue();
System.out.println("The sum is: " + addServerIntf.add(d1, d2));
}
catch(Exception e) {
System.out.println("Exception: " + e);
}
}
}
Before you can use the client and the server, you must generate the necessary stub. You may also need to generate a skeleton, if a server runs on a machine that does not have Java 2, but has an earlier version of JVM.
In RMI, a stub is a java class that either resides on the client machine, or downloaded to the client machine dynamically from elsewhere (e.g., from the web server). The function of stub is to present the same interfaces as the remote server. Remote method calls initiated by the client are actually directed to the stub. The stub works with the other parts of the RMI system to formulate a request that is sent to the remote machine.
A remote method may accept arguments that are simple types or objects. In the latter case, the object may have references to other objects. All of this information must be sent to the remote machine. That is, an object passed as an argument to a remote method call must be serialized and sent to the remote machine. If a response must be returned to the client, the process works in reverse. Note that the serialization and deserialization facilities are also used if objects are returned to a client.
Skaletons are not required by Java 2, but required for the Java 1.1 RMI model. For this reason, skeletons are still necessary for compatibility between Java 1.1 and Java2. A skeleton is a Java class that resides on the server machine. It works with other parts of the 1.1 RMI system to receive requests, perform deserialization, and invoke the appropriate code on the server. It is used in this example to illustrate the compatibility.
To generate stubs and skeletons, you use a tool called the RMI compiler:
This command generates two new files:
AddServerImpl_Skel.class (skeleton) and
AddServerImpl_Stub.class (stub).
If you do not need the skeleton, run rmic -v1.2
to turn on Java 2 option. (read details in the rmic manual page).
The easiest way to try a distributed architecture is to copy all required files manually to correct machines/directories:
Suppose that you started the server on jupiter.scs.ryerson.ca Then, you can start the client on your local machine as follows:
java AddClient jupiter.scs.ryerson.ca 567 999 The first number is: 567.0 The second number is: 999.0 The sum is: 1566.0If you do not have an Internet connection, you can run all programs of this example on your local machine (both server and client). In this case, you can run
This ability to load classes from the network is one of the unique features of Java. Of course, as with any time that classes are to be loaded from a potentially untrusted host, they must be checked by a SecurityManager.
In addition, if you're running the client or server with Java 2, then you'll need to specify a security policy file, to prevent SecurityExceptions being thrown. This policy file will allow your application to bind to a local port (if a service), and to connect to remote hosts (if a client). The following changes should be made when running the client/server :
java -Djava.security.policy=rmi.policy yourserverYou'll also need to create a policy file "rmi.policy" (if one does not already exist). Here's a sample policy file that will allow you to accept conections from ports higher than 1024, but connect to all ports as a client (the real file is provided later).
grant { permission java.net.SocketPermission "*:1024-65535", "connect,accept,resolve"; permission java.net.SocketPermission "*:1-1023", "connect,resolve"; };
In the previous version of the example, the client did not attempt to load stub file from the server; both the server and client also lack any security managers that prevent from loading malicious code. The next modification of the previous example provides both security managers and the ability to load files at runtime from the remote server.
import java.rmi.*; public interface AddServerIntf extends Remote { double add(double d1, double d2) throws RemoteException; }
import java.rmi.*; import java.rmi.server.*; public class AddServerImpl extends UnicastRemoteObject implements AddServerIntf { public AddServerImpl() throws RemoteException { super(); } public double add(double d1, double d2) throws RemoteException { return d1 + d2; } }
The revised version of the third source file
AddServer.java includes the security manager,
assumes that you will run the server on jupiter using the port 56789,
and uses a slightly modified name "MyAddServer" for registration purposes.
import java.net.*;
import java.rmi.*;
public class AddServer {
public static void main(String args[]) {
// Create and install a security manager
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
try {
AddServerImpl addServerImpl = new AddServerImpl();
// You want to run your AddServer on jupiter using the port 56789
// and you want to use rmi to connect to jupiter from your local machine
// Note that to accomplish this you have to start on jupiter
// rmiregistry 56789 &
// in the directory that contains NO classes related to this server !!!
Naming.rebind("rmi://jupiter.scs.ryerson.ca:56789/MyAddServer", addServerImpl);
}
catch (Exception e) {
System.out.println("Exception: " + e.getMessage());
e.printStackTrace();
}
}
}
Copy files AddServerIntf.java, AddServerImpl.java, and AddServer.java to your directory on jupiter and compile them as usual using javac.
import java.rmi.*; // USAGE: java AddClientKeep this file on your local machine together with the remote interface and the rmi.policy file that controls access to your local machine:firstNum secondNum public class AddClient { public static void main(String args[]) { // The client will try to download code, in particular, it will // be downloading stub class from the server. // Any time code is downloaded by RMI, a security manager must be present. if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } try { String addServerURL = "rmi://" + args[0] + ":" + args[1] + "/MyAddServer"; System.out.println("I will try to invoke the remote method from " + addServerURL); AddServerIntf remoteObj = (AddServerIntf) Naming.lookup(addServerURL); System.out.println("The first number is: " + args[2]); double d1 = Double.valueOf(args[2]).doubleValue(); System.out.println("The second number is: " + args[3]); double d2 = Double.valueOf(args[3]).doubleValue(); // Now we invoke from a local machine the remote method "add" System.out.println("The sum is: " + remoteObj.add(d1, d2)); } catch(Exception e) { System.out.println("Exception: " + e); } } }
grant { // The simplest (but uncarefull) policy is allow everything: // permission java.security.AllPermission; // More secure policy is the following. // // jupiter.scs.ryerson.ca This is the rmihost - RMI registry and the server // www.scs.ryerson.ca This is webhost - HTTP server for stub classes permission java.net.SocketPermission "jupiter.scs.ryerson.ca:1024-65535", "connect,accept"; permission java.net.SocketPermission "www.scs.ryerson.ca:80", "connect"; };You also have to copy this policy file to your directory on jupiter that contains all server-related files.
This command generates two new files: AddServerImpl_Skel.class (skeleton) and AddServerImpl_Stub.class (stub).
Before you proceed, copy stub and interface classes to a directory, where the web server can access them, e.g. to the world-accessible sub-directory JavaClasses of your public_html directory:
ls -t -l ~mes/public_html/JavaClasses/ total 24 -rw-r--r-- 1 mes mes 205 AddServerIntf.class -rw-r--r-- 1 mes mes 1612 AddServerImpl_Skel.class -rw-r--r-- 1 mes mes 3171 AddServerImpl_Stub.classCheck that all these files are accessible, e.g., try to download them using Netscape or IE: if you succeeded, then they are accessible.
On jupiter in the directory that does NOT contain any server related classes and assuming that those classes are NOT on you CLASSPATH, start rmiregistry:
For example, create the temporary directory tmpTEST, go to that directory and start there rmiregistry naming system. By default, rmiregistry naming system loads stub and skeleton files from directories mentioned in your CLASSPATH. But because you want to load them dynamically from your web directory JavaClasses, you want to hide these files from rmiregistry: this way you force to load required files from the codebase given below as a command-line argument.
-rw-r--r-- 1 mes mes 1075 AddServer.class -rw-r--r-- 1 mes mes 363 AddServerImpl.class -rw-r--r-- 1 mes mes 1612 AddServerImpl_Skel.class -rw-r--r-- 1 mes mes 3171 AddServerImpl_Stub.class -rw-r--r-- 1 mes mes 205 AddServerIntf.class -rw-r--r-- 1 mes mes 511 rmi.policywe can run the server:
java -Djava.security.policy=rmi.policy -Djava.rmi.server.codebase=http://www.scs.ryerson.ca/~mes/JavaClasses/ AddServer &Note that the URL given to "codebase" ends with / and use your own login name, of course.
This time, you would like to load the stub class dynamically from the server. Assume that the server side was developed by a different company, you are responsible only for the client application and you do not have access to the server-related files when you start your client. All you know is that the RMI server will be running on jupiter and you can connect to the registry at the port 56789 if you need to invoke remote methods on the server.
Finally, you can run the client application on your local machine:
java -Djava.security.policy=rmi.policy AddClient jupiter.scs.ryerson.ca 56789 456 544 I will try to invoke the remote method from rmi://jupiter.scs.ryerson.ca:56789/MyAddServer The first number is: 456 The second number is: 544 The sum is: 1000.0