9. JAVA RMI
9.1. Introduction
We have seen how to create network applications using communication tools called sockets. In a client/server application built on these tools, the link between the client and the server is the communication protocol they have adopted to communicate. The two applications can be written in different languages: Java, for example, for the client, Perl for the server, or any other combination. We do indeed have two distinct applications connected by a communication protocol known to both. Furthermore, network access via sockets is not transparent to a Java application: it must use the Socket class, a class created specifically to manage these communication tools known as sockets.
Java RMI (Remote Method Invocation) allows you to create network applications with the following characteristics:
- Client/server applications are Java applications at both ends of the communication
- The client can use objects located on the server as if they were local
- The network layer becomes transparent: applications do not have to worry about how information is transported from one point to another.
The last point is a factor in portability: if the network layer of an RMI application were to change, the application itself would not need to be rewritten. It is the RMI classes of the Java language that would need to be adapted to the new network layer.
The principle of RMI communication is as follows:
- A standard Java application is written on machine A. It will act as the server. To do this, some of its objects will be “published” on machine A, where the application is running, and will then become services.
- A classic Java application is written on machine B. It will act as the client. It will have access to the objects/services published on machine A; that is, via a remote reference, it will be able to manipulate them as if they were local. To do this, it will need to know the structure of the remote object it wants to access (methods & properties).
9.2. Let’s learn through an example
The theory behind the RMI interface is not simple. To make things clearer, we will walk through the process of writing a client/server application using Java’s RMI package step by step. We’ll use an application found in many books on RMI: the client calls a single method of a remote object, which then returns a string. Here, we present a slight variation: the server echoes back what the client sends it. We’ve already presented such an application in this book, one that relies on sockets.
9.2.1. The server application
9.2.1.1. Step 1: The object/server interface
A remote object is a class instance that must implement the Remote interface defined in the java.rmi package. The object’s methods that will be accessible remotely are those declared in an interface derived from the Remote interface:
import java.rmi.*;
// the remote interface
public interface interEcho extends Remote{
public String echo(String msg) throws java.rmi.RemoteException;
}
Here, we declare an interEcho interface that declares an echo method as remotely accessible. This method may throw an exception of the RemoteException class, which encompasses all network-related errors.
9.2.1.2. Step 2: Writing the server object
In the next step, we define the class that implements the previous remote interface. This class must be derived from the UnicastRemoteObject class, which provides methods that allow remote method invocation.
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// class implementing the remote echo
public class srvEcho extends UnicastRemoteObject implements interEcho{
// constructor
public srvEcho() throws RemoteException{
super();
}// end of constructor
// Echo method
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// end of echo
}// end of class
In the previous class, we find:
- the method that performs the echo
- a constructor that does nothing except call the parent class’s constructor. It is there to declare that it can throw a RemoteException.
We will create an instance of this class with a main method. For an object/service to be accessible from the outside, it must be created and registered in the directory of externally accessible objects. A client wishing to access a remote object proceeds as follows:
- they contact the directory service on the machine where the desired object is located. This directory service operates on a port that the client must know (1099 by default). The client requests from the directory a reference to an object/service, providing its name. If this name corresponds to an object/service in the directory, the directory returns a reference to the client, through which the client can communicate with the remote object/service.
- From that point on, the client can use this remote object as if it were local
Returning to our server, we must create an object of type srvEcho and register it in the directory of externally accessible objects. This registration is performed using the rebind method of the Naming class:
where
name: the name that will be associated with the remote object
obj: the remote object
Our srvEcho class therefore becomes the following:
import java.rmi.*;
import java.rmi.server.*;
import java.net.*;
// class implementing the remote echo
public class srvEcho extends UnicastRemoteObject implements interEcho{
// constructor
public srvEcho() throws RemoteException{
super();
}// end of constructor
// Echo method
public String echo(String msg) throws RemoteException{
return "[" + msg + "]";
}// end of echo
// service creation
public static void main(String arg[]){
try{
srvEcho echoServer = new srvEcho();
Naming.rebind("srvEcho", echoServer);
System.out.println("Echo server ready");
} catch (Exception e){
System.err.println("Error " + e + " while starting the echo server");
}
}// main
}// end of class
When reading the previous program, it seems as though it will stop immediately after creating and registering the echo service. This is not the case. Because the srvEcho class is derived from the UnicastRemoteObject class, the created object runs indefinitely: it listens for client requests on an anonymous port, i.e., one chosen by the system based on the circumstances. Service creation is asynchronous: in the example, the main method creates the service and continues executing; it will display “Echo server ready.”
9.2.1.3. Step 3: Compiling the server application
At this point, we can compile our server. We compile the interEcho.java file from the interEcho interface as well as the srvEcho.java file from the srvEcho class. We obtain the corresponding .class files: interEcho.class and srvEcho.class.
9.2.1.4. Step 4: Writing the client
We write a client that takes the echo server’s URL as a parameter and
- reads a line typed on the keyboard
- sends it to the echo server
- displays the response it sends
- loops back to step 1 and stops when the typed line is “end”.
This results in the following client:
import java.rmi.*;
import java.io.*;
public class cltEcho {
public static void main(String arg[]){
// syntax: cltEcho URLService
// Check arguments
if(arg.length!=1){
System.err.println("Syntax: pg rmi_service_url");
System.exit(1);
}
// client-server communication
String urlService = arg[0];
BufferedReader in = null;
String msg = null;
String response = null;
serverEcho = null;
try{
// Open the keyboard stream
in = new BufferedReader(new InputStreamReader(System.in));
// Locate the service
server = (interEcho) Naming.lookup(urlService);
// Loop to read messages to send to the echo server
System.out.print("Message: ");
msg = in.readLine().toLowerCase().trim();
while(! msg.equals("end")){
// send the message to the server and receive the response
response = server.echo(msg);
// tracking
System.out.println("Server response: " + response);
// next message
System.out.print("Message: ");
msg = in.readLine().toLowerCase().trim();
}// while
// Done
System.exit(0);
// error handling
} catch (Exception e){
System.err.println("Error: " + e);
System.exit(2);
}// try
}// main
}// class
There is nothing particularly special about this client except for the statement that retrieves a server reference:
Recall that our echo service was registered in the service directory of the machine where it resides using the instruction:
Naming.rebind("srvEcho", serverEcho);
The client therefore also uses a method from the Naming class to obtain a reference to the server it wants to use. The lookup method used takes the URL of the requested service as a parameter. This URL has the form of a standard URL:
where
rmi: optional - RMI protocol
machine: name or IP address of the machine on which the echo server runs - optional, default is localhost.
port: listening port of this machine’s directory service—optional, default 1099
service_name: name under which the requested service was registered (srvEcho in our example)
What is returned is an instance of the remote interface interEcho. Assuming the client and server are not on the same machine, when compiling the client cltEcho.java, the file interEcho.class—the result of compiling the remote interface interEcho—must be present in the same directory; otherwise, a compilation error will occur on the lines referencing this interface.
9.2.1.5. Step 5: Generating the .class files required for the client-server application
To clearly distinguish between the server-side and client-side components, we will place the server in the echo\server directory and the client in the echo\client directory.
The server directory contains the following source files:
E:\data\java\RMI\echo\server>dir *.java
INTERE~1 JAV 158 03/09/99 15:06 interEcho.java
SRVECH~1 JAV 759 03/09/99 15:07 srvEcho.java
After compiling these two source files, we have the following .class files:
E:\data\java\RMI\echo\server>dir *.class
SRVECH~1 CLA 1,129 03/09/99 15:58 srvEcho.class
INTERE~1 CLA 256 03/09/99 15:58 interEcho.class
In the client directory, we find the following source file:
as well as the interEcho.class file that was generated during server compilation:
After compiling the source file, we have the following .class files:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 506 03/09/99 16:08 cltEcho.class
INTERE~1 CLA 256 03/09/99 15:59 interEcho.class
If we try to run the cltEcho client, we get the following error:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho
Error: java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub
If you try to run the srvEcho server, you get the following error:
E:\data\java\RMI\echo\server>j:\jdk12\bin\java srvEcho
Error java.rmi.StubNotFoundException: Stub class not found: srvEcho_Stub; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub when launching the echo server
In both cases, the Java Virtual Machine indicates that it could not find the srvEcho_stub class. Indeed, we have never heard of this class before. In the client, the server was located using the following statement:
Here, urlservice is the string rmi://localhost/srvEcho with
RMI: RMI protocol
Localhost: the machine where the server is running—in this case, the same machine as the client. The syntax is normally machine:port. If no port is specified, port 1099 is used by default. The server’s directory service is listening on this port.
srvEcho: this is the name of the specific service being requested
No errors were reported during compilation. The interEcho.class file for the remote interface simply needed to be available.
At runtime, the virtual machine requires the presence of an srvEcho_stub.class file if the requested service is the srvEcho service; generally, an X_stub.class file is required for a service X. This file is only necessary at runtime, not during client compilation. The same applies to the server. So what exactly is this file?
On the server, there is the srvEcho.class class, which is our remote object/service. The client, even if it does not need this class, still needs a sort of image of it in order to communicate with it. In fact, the client does not send its requests directly to the remote object: it sends them to its local image srvEcho_stub.class located on the same machine as itself. This local image, srvEcho_stub.class, communicates with a similar image (srvEcho_stub.class) located on the server. This image is created from the server’s .class file using a Java tool called rmic. On Windows, the command:
will generate, from the srvEcho.class file, two other .class files:
E:\data\java\RMI\echo\server>dir *.class
SRVECH~2 CLA 3,264 03/09/99 16:57 srvEcho_Stub.class
SRVECH~3 CLA 1,736 03/09/99 16:57 srvEcho_Skel.class
Here we have the srvEcho_stub.class file that both the client and server need at runtime. There is also a srvEcho_Skel.class file, the purpose of which is currently unknown. We copy the srvEcho_stub.class file to the client and server directories and delete the srvEcho_Skel.class file. We now have the following files:
on the server side:
E:\data\java\RMI\echo\server>dir *.class
SRVECH~1 CLA 1 129 03/09/99 15:58 srvEcho.class
INTERE~1 CLA 256 03/09/99 15:58 interEcho.class
SRVECH~1 CLA 3 264 03/09/99 16:01 srvEcho_Stub.class
on the client side:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 506 03/09/99 16:08 cltEcho.class
INTERE~1 CLA 256 03/09/99 15:59 interEcho.class
SRVECH~1 CLA 3,264 03/09/99 16:01 srvEcho_Stub.class
9.2.1.6. Step 6: Running the client-server echo application
We are ready to run our client-server application. Initially, the client and server will run on the same machine. First, we need to launch our server application. Recall that this:
- creates the service
- registers it in the service directory of the machine on which the echo server is running
This last step requires a registry service. This is started with the command:
rmiregistry is the registry service. Here, it is launched in the background in a Windows Command Prompt window using the start command. With the registry active, we can create the echo service and register it in the service registry. Again, it is launched in the background using a start command:
The echo server runs in a new DOS window and displays the requested output:
All that remains is to launch and test our client:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java cltEcho rmi://localhost/srvEcho
Message: msg1
Server response: [msg1]
Message: msg2
Server response: [msg2]
Message: end
9.2.1.7. The client and server on two different machines
In the previous example, the client and server were on the same machine. Now we place them on different machines:
- the server on a Windows machine
- the client on a Linux machine
The server is launched as before on the Windows machine. On the Linux machine, the client's .class files have been transferred:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir
total 9
drwxr-xr-x 2 serge admin 1024 Mar 10 10:02 .
drwxr-xr-x 4 serge admin 1024 Mar 10 10:01 ..
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
The client is launched:
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message: msg1
Error: java.rmi.ServerException: RemoteException occurred in server thread; nested exception is:
java.rmi.UnmarshalException: error unmarshalling call header; nested exception is:
java.rmi.UnmarshalException: skeleton class not found but required for client version
So we have an error: the Java Virtual Machine apparently requires the file srvEcho_skel.class, which was generated by the rmic utility but had not been used until now. We recreate it and also transfer it to the Linux machine:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir
total 11
drwxr-xr-x 2 serge admin 1024 Mar 10 10:17 .
drwxr-xr-x 4 serge admin 1024 Mar 10 10:01 ..
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 1736 Mar 10 10:17 srvEcho_Skel.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
We get the same error as before... So we think it over and reread the RMI documentation. We eventually conclude that perhaps the server itself needs the famous srvEcho_Skel.class file. We then restart the server on the Windows machine with both the srvEcho_Stub.class and srvEcho_Skel.class files present:
E:\data\java\RMI\echo\server>dir *.class
SRVECH~1 CLA 1 129 03/09/99 15:58 srvEcho.class
INTERE~1 CLA 256 03/09/99 15:58 interEcho.class
SRVECH~2 CLA 3,264 03/10/99 9:05 srvEcho_Stub.class
SRVECH~3 CLA 1,736 03/10/99 9:05 srvEcho_Skel.class
E:\data\java\RMI\echo\server>start j:\jdk12\bin\java srvEcho
Then, on the Linux machine, we test the client again, and this time it works:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
-rw-r--r-- 1 serge admin 3264 Mar 10 10:02 srvEcho_Stub.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message: msg1
Server response: [msg1]
Message: msg2
Server response: [msg2]
Message: end
We can therefore conclude that, on the server side, both the srvEcho_Stub.class and srvEcho_Skel.class files must be present. On the client side, only the srvEcho_Stub.class file has been necessary so far. It had proven essential when the client and server were on the same Windows machine. On Linux, we’ll remove it to see what happens...
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 10:02 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:28)
Error: java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
We have an interesting error that seems to indicate the Java Virtual Machine tried to load the infamous stub class but failed due to the absence of a "security manager." We recall seeing something about this in the documentation. We dive back in... and find that the server must create and install a security manager to guarantee to clients requesting class loading that the classes are safe. Without this security manager, class loading is impossible. That seems to fit: our Linux client requested the srvEcho_stub.class it needed from the server, and the server refused, stating that no security manager had been installed. So we modify the code of the server’s main function as follows:
// create the service
public static void main (String arg[]){
// install a security manager
System.setSecurityManager(new RMISecurityManager());
// Start and register the service
try{
srvEcho serverEcho = new srvEcho();
Naming.rebind("srvEcho", echoServer);
System.out.println("Echo server ready");
} catch (Exception e){
System.err.println("Error " + e + " while starting the echo server");
}
}// main
We compile and generate the files srvEcho_stub.class and srvEcho_Skel.class using the rmic tool. We start the directory service (rmiregistry) and then the server, and we get an error that we didn’t have before!
Error java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve) while starting the echo server
The security manager seems to have been too strict. We review the documentation again... We realize that when a security manager is active, you must specify the program’s permissions when launching it. This is done with the following option:
<mark style="background-color: #ffff00">start j:\\jdk12\\bin\\java -Djava.security.policy=mypolicy srvEcho</mark>
where
java.security.policy is a keyword
mypolicy is a text file defining the program’s permissions. Here, it is as follows:
The program has full permissions here.
Let’s start over. Go to the server directory and do the following:
- start the directory service: start j:\jdk12\bin\rmiregistry
- Start the server: start j:\jdk12\bin\java -Djava.security.policy=mypolicy srvEcho
And this time, the echo server (but not the client yet) starts correctly. Now you can try the following:
- stop the echo server, then the directory service
- Restart the directory service while in a directory other than the server’s
- return to the server directory start the echo server—you will get the following error:
Error java.rmi.ServerException: RemoteException occurred in server thread; nes
ted exception is:
java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub when launching the echo server
We can conclude that the directory from which the directory service is launched matters. Here, Java could not find the srvEcho_stub.class because the directory service was not launched from the server directory. When launching the server, you can specify the directory where the classes required by the server are located:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/server/
srvEcho
The command is on a single line. The keyword <mark style="background-color: #ffff00">java.rmi.server.codebase</mark> is used to specify the URL of the directory containing the classes required by the server. In this case, the URL specifies the file protocol—which is the protocol for accessing local files—and the directory containing the server’s .class files. Therefore, if you proceed as follows:
- stop the directory service
- restart the directory service from a directory other than the server’s
- in the server directory, start the server with the command (a single line):
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/server/
srvEcho
The server is now running correctly. We can now move on to the client. Let’s test it:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 14:28 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
*** Security Exception: No security manager, stub class loader disabled ***
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
at sun.rmi.server.RMIClassLoader.getClassLoader(RMIClassLoader.java:84)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:88)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
Error: java.rmi.UnexpectedException: Unexpected exception; nested exception is:
java.rmi.RMISecurityException: security.No security manager, stub class loader disabled
We get the same error indicating the absence of a security manager. We think we might have made a mistake and that the client is the one that needs to create its own security manager. We leave the server with its security manager but create one for the client as well. The main function of the client cltEcho.java then becomes:
public static void main(String arg[]){
// syntax: cltEcho machine port
// machine: machine where the echo server is running
// port: port where the service directory operates on the echo service machine
// argument validation
if(arg.length!=1){
System.err.println("Syntax: pg rmi_service_url");
System.exit(1);
}
// Set up a security manager
System.setSecurityManager(new RMISecurityManager());
// client-server communication
String urlService = arg[0];
BufferedReader in = null;
String msg = null;
String response = null;
serverEcho = null;
try{
....
} catch (Exception e){
....
}// try
}// main
We then proceed as follows:
- Recompile cltEcho.java
- transfer the .class files to the Linux machine
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1506 Mar 10 14:28 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
- Launch the client
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
java.io.FileNotFoundException: /e:/data/java/rmi/echo/server/srvEcho_Stub.class
at java.io.FileInputStream.<init>(FileInputStream.java)
at sun.net.www.protocol.file.FileURLConnection.connect(FileURLConnection.java:150)
at sun.net.www.protocol.file.FileURLConnection.getInputStream(FileURLConnection.java:170)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:119)
at sun.applet.AppletClassLoader.findClass(AppletClassLoader.java:496)
at sun.applet.AppletClassLoader.loadClass(AppletClassLoader.java:199)
at sun.rmi.server.RMIClassLoader.loadClass(RMIClassLoader.java:159)
at sun.rmi.server.MarshalInputStream.resolveClass(MarshalInputStream.java:97)
at java.io.ObjectInputStream.inputClassDescriptor(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at java.io.ObjectInputStream.inputObject(ObjectInputStream.java)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java)
at sun.rmi.registry.RegistryImpl_Stub.lookup(RegistryImpl_Stub.java:105)
at java.rmi.Naming.lookup(Naming.java:60)
at cltEcho.main(cltEcho.java:31)
File not found when looking for: srvEcho_Stub
Error: java.rmi.UnmarshalException: Return value class not found; nested exception is:
java.lang.ClassNotFoundException: srvEcho_Stub
Despite appearances, we’re making progress: the error is no longer the same. We can see that the client was able to request the srvEcho_Stub.class from the server, but the server couldn’t find it. Therefore, the client must have a security manager if it wants to be able to request classes from the server.
If we look at the previous error, we see that the file srvEcho_Stub.class was searched for in the directory e:/data/java/rmi/echo/server/ and was not found. Yet that is exactly where it is. If we look more closely at the list of methods involved in the error, we find this one: sun.net.www.protocol.file.FileURLConnection.getInputStream. The client appears to have opened a connection using a FileURLConnection object. We suspect that all of this is related to how we started our server:
start j:\jdk12\bin\java
-Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/echo/server/
srvEcho
The error message seems to refer to the value of the java.rmi.server.codebase keyword. When we look at the documentation again, we see that the value of this keyword is, in the examples provided, always: http://.., i.e., the protocol used is HTTP. It is not clear how the client requests and obtains its classes from the server. Perhaps it requests them using the URL specified by the *java.rmi.server.codebase* keyword, which is set when the server is launched. We therefore decide to launch the server using the following new command:
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
The protocol is now HTTP. We must move the .class files to a location accessible to the HTTP server on the machine where the classes will be stored. In our example, the server is running on a Windows machine with a Microsoft PWS HTTP server. The root of this server is d:\Inetpub\wwwroot. Therefore, we proceed as follows:
- Create the directory d:\Inetpub\wwwroot\rmi\echo
- Place the server’s .class files and the mypolicy file there
- Start the web server if it is not already running
- Restart the registry service (rmiregistry)
- Restart the server with the command
start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
- On the Linux machine, launch the client:
shiva[serge]:/home/admin/serge/java/rmi/client#
$ dir *.class
-rw-r--r-- 1 serge admin 1622 Mar 10 14:37 cltEcho.class
-rw-r--r-- 1 serge admin 256 Mar 10 10:02 interEcho.class
shiva[serge]:/home/admin/serge/java/rmi/client#
$ java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message: msg1
Server response: [msg1]
Message: msg2
Server response: [msg2]
Message: end
Phew! It works. The client successfully retrieved the srvEcho_Stub.class file.
All of this has given us some ideas, and we’re wondering if the client running on the server’s Windows machine would also work without the srvEcho_Stub.class file. We navigate to the client’s directory, delete the srvEcho_Stub.class file if it’s there, and launch the client the same way as on Linux:
E:\data\java\RMI\echo\client>dir *.class
CLTECH~1 CLA 1 622 03/10/99 14:12 cltEcho.class
INTERE~1 CLA 256 03/09/99 15:59 interEcho.class
E:\data\java\RMI\echo\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
Message: new message
Server response: [new message]
Message: end
9.2.1.8. Summary
Windows server side:
- The server has a security manager
- it was launched with the following options: start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/echo/ srvEcho
Client side (Linux or Windows)
- The client has a security manager
- On Linux, it was launched by `java cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho`
- On Windows, it was launched by j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://tahe.istia.univ-angers.fr/srvEcho
9.2.1.9. Echo server on Linux, clients on Windows and Linux
We will now move the server to a Linux machine and test Linux and Windows clients. The procedure is as follows:
- Move the server’s .class files to the Linux machine
shiva[serge]:/home/admin/serge/WWW/rmi/echo/server#
$ dir
total 11
drwxr-xr-x 2 serge admin 1024 Mar 10 16:15 .
drwxr-xr-x 3 serge admin 1024 Mar 10 16:09 ..
-rw-r--r-- 1 serge admin 256 Mar 10 16:09 interEcho.class
-rw-r--r-- 1 serge admin 1245 Mar 10 16:09 srvEcho.class
-rw-r--r-- 1 serge admin 1736 Mar 10 16:09 srvEcho_Skel.class
-rw-r--r-- 1 serge admin 3264 Mar 10 16:09 srvEcho_Stub.class
- Because the srvEcho_Stub.class will be requested by clients, the directory chosen for the server classes is one accessible to the Linux machine’s HTTP server. Here, the URL for this directory is http://shiva.istia.univ-angers.fr/~serge/rmi/echo/serveur
- the registry service is launched in the background: /usr/local/bin/jdk/rmiregistry &
- The server is launched in the background: /usr/local/bin/jdk/bin/java
-Djava.rmi.server.codebase=http://shiva.istia.univ-angers.fr/~serge/rmi/echo/server/
srvEcho &
We can test the clients. The Windows client first.
- Navigate to the client directory on the Windows machine
- Launch the client with the command:
E:\data\java\RMI\echo\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltEcho rmi://shiva.istia.univ-angers.fr/srvEcho
Message: msg1
Server response: [msg1]
Message: end
Testing the Linux client:
shiva[serge]:/home/admin/serge/java/rmi/echo/client#
$ java cltEcho srvEcho
Message: msg1
Server response: [msg1]
Message: msg2
Server response: [msg2]
Message: end
Note that for the Linux client running on the same machine as the echo server, there was no need to specify a machine in the URL of the requested service.
9.3. Second example: SQL server on a Windows machine
9.3.1. The problem
In the JDBC chapter, we saw how to manage relational databases. In the examples provided, the applications and the database used were on the same Windows machine. Here, we propose writing an RMI server on a Windows machine that would allow remote clients to access the public ODBC databases on the machine hosting the server.

The RMI client could perform three operations:
- connect to the database of its choice
- send SQL queries
- close the connection
The server executes the client’s SQL queries and sends the results back to the client. This is its primary function, which is why we will refer to it as an SQL server.
We apply the different steps seen previously with the echo server.
9.3.2. Step 1: The remote interface
The remote interface is the interface that lists the RMI server methods that will be accessible to RMI clients. We will use the following interface:
import java.rmi.*;
// the remote interface
public interface interSQL extends Remote{
public String connect(String driver, String url, String id, String password)
throws java.rmi.RemoteException;
public String[] executeSQL(String query, String separator)
throws java.rmi.RemoteException;
public String close()
throws java.rmi.RemoteException;
}
The roles of the various methods are as follows:
Connect: the client connects to a remote database, providing the driver, the JDBC URL, as well as its ID and password to access the database. The server returns a string indicating the result of the connection:
executeSQL: the client requests the execution of an SQL query on the database to which it is connected. It specifies the character that should separate the fields in the results returned to it. The server returns an array of strings:
for a database update query, where n is the number of rows updated
if the query generated an error
if the query returned no results
if the query returned results. The rows returned by the server are the query results.
close: the client closes its connection to the remote database. The server returns a string indicating the result of this closure:
9.3.3. Step 2: Writing the server
The Java source code for the SQL server follows. Understanding it requires familiarity with JDBC database management and the construction of RMI servers. The program comments should facilitate understanding.
// Imported packages
import java.rmi.*;
import java.rmi.server.*;
import java.sql.*;
import java.util.*;
// srvSQL class
public class srvSQL extends UnicastRemoteObject implements interSQL{
// class-level variables
private Connection DB;
// ------------- constructor
public srvSQL() throws RemoteException{
super();
}
// --------------- connect
public String connect(String driver, String url, String id,
String password) throws RemoteException{
// connect to the database at url using the driver driver
// authentication using ID and password
String result = null; // Result of the method
try{
// Load the driver
Class.forName(driver);
// connection request
DB = DriverManager.getConnection(url, id, mdp);
// OK
result="200 Connection successful";
} catch (Exception e){
// error
result = "500 Connection failed (" + e + ")";
}
// end
return result;
}
// ------------- executeSQL
public String[] executeSQL(String query, String separator)
throws RemoteException{
// executes an SQL query on the database
// and places the results in an array of strings
// data required to execute the query
Statement S = null;
ResultSet RS = null;
String[] rows = null;
Vector results = new Vector();
String row = null;
try{
// Create the query container
S = DB.createStatement();
// execute query
if (! S.execute(query)) {
// update query
// return the number of updated rows
rows = new String[1];
lines[0] = "100 " + S.getUpdateCount();
return rows;
}
// This was a query
// retrieve the results
RS = S.getResultSet();
// number of fields in the ResultSet
int numFields = RS.getMetaData().getColumnCount();
// we process them
while(RS.next()){
// create the results row
row = "101 ";
for (int i = 1; i < nbChamps; i++)
line += RS.getString(i) + separator;
line += RS.getString(nbChamps);
// Add to the results array
results.addElement(line);
}// while
// end of result processing
// release resources
RS.close();
S.close();
// return the results
int numLines = results.size();
if (numberOfLines == 0) {
lines = new String[1];
lines[0] = "501 No results";
} else {
lines = new String[results.size()];
for(int i=0;i<lines.length;i++)
lines[i] = (String) results.elementAt(i);
}//if
return lines;
} catch (Exception e){
// error
lines = new String[1];
lines[0] = "500 " + e;
return lines;
}// try-catch
}// executeSQL
// --------------- close
public String close() throws RemoteException {
// closes the database connection
String result = null;
try{
DB.close();
result = "200 Database closed";
} catch (Exception e) {
result = "500 Error closing database (" + e + ")";
}
// Return the result
return result;
}
// ----------- main
public static void main(String[] args) {
// security manager
System.setSecurityManager(new RMISecurityManager());
// launch the service
srvSQL serverSQL = null;
try{
// creation
SQLServer = new srvSQL();
// registration
Naming.rebind("srvSQL", serverSQL);
// monitoring
System.out.println("SQL server ready");
} catch (Exception e){
// error
System.err.println("Error starting the SQL server (" + e + ")");
}// try-catch
}// main
}// class
9.3.4. Writing the RMI client
The RMI server client is called with the following parameters:
directoryServiceURL: RMI URL of the directory service that registered the SQL server
driver: driver that the SQL server must use to manage the database
urlBase: JDBC URL of the database to be managed
id: client ID or null if no ID
password: client password or null if no password
separator: character that the SQL server must use to separate fields in the result rows of a query
Here is an example of possible parameters:
with here:
RMI name of the SQL server
the standard driver for databases with an ODBC interface
to use an articles database listed in the Windows machine's public ODBC databases
no identity
no password
the fields in the results will be separated by a comma
Once launched with the above parameters, the client follows these steps:
- They connect to the RMI server srvSQL, which is an RMI server on the same machine as the client
- they request a connection to the articles database
- It prompts the user to type an SQL query
- it sends it to the SQL server
- It displays the results returned by the server on the screen
- it prompts the user again to type an SQL query on the keyboard. It will stop when the query is finished.
The Java client code follows. The comments should be sufficient for understanding it.
import java.rmi.*;
import java.io.*;
public class cltSQL {
// class-level variables
private static String syntax =
"syntax: cltSQL urlServiceAnnuaire driver urlBase id password separator";
private static BufferedReader in = null;
private static interSQL sqlServer = null;
public static void main(String arg[]){
// syntax: cltSQL directoryServiceURL separator driver URL ID password
// urlServiceDirectory: URL of the RMI service directory to contact
// driver: driver to use for the database to be used
// urlBase: JDBC URL of the database to use
// id: user ID
// password: the user's password
// separator: string separating fields in query results
// Check the number of arguments
if(arg.length!=6)
error(syntax, 1);
// initialize database connection parameters
String serviceUrl = arg[0];
String driver = arg[1];
String databaseUrl = arg[2];
String id, password, separator;
if (arg[3] == "null") id = ""; else id = arg[3];
if (arg[4] == "null") password = ""; else password = arg[4];
if(arg[5].equals("null")) separator=" "; else separator=arg[5];
// Install a security manager
System.setSecurityManager(new RMISecurityManager());
// client-server communication
String request = null;
String response = null;
String[] lines = null;
String errorCode = null;
try{
// Open the keyboard input stream
in = new BufferedReader(new InputStreamReader(System.in));
// monitoring
System.out.println("--> Connecting to the RMI server...");
// locate the service
SQLServer = (InterSQL) Naming.lookup(urlService);
// follow-up
System.out.println("--> Connecting to the database...");
// Initial database connection request
response = SQLServer.connect(driver, databaseURL, id, password);
// follow-up
System.out.println("<-- " + response);
// Analyze response
errorCode = response.substring(0, 3);
if(errorCode.equals("500"))
error("Aborted due to database connection error", 3);
// loop to read requests to send to the SQL server
System.out.print("--> Query: ");
query = in.readLine().toLowerCase().trim();
while(! query.equals("end")){
// Send the query to the server and receive the response
rows = SQLServer.executeSQL(query, separator);
// processing
displayRows(rows);
// next query
System.out.print("--> Query: ");
query = in.readLine().toLowerCase().trim();
}// while
// follow-up
System.out.println("--> Closing the connection to the remote database");
// closing the connection
response = sqlServer.close();
// followed by
System.out.println("<-- " + response);
// end
System.exit(0);
// error handling
} catch (Exception e){
error("Aborting due to error: " + e, 2);
}// try
}// main
// ----------- DisplayLines
private static void displayLines(String[] lines){
for (int i = 0; i < lines.length; i++)
System.out.println("<-- " + lines[i]);
}// displayLines
// ------------ error
private static void error(String msg, int exitCode){
// display error message
System.err.println(msg);
// release resources if necessary
try{
in.close();
SQLServer.close();
} catch (Exception e) {}
// exit
System.exit(exitCode);
}// error
}// class
9.3.5. Step 3: Creating .class files
- The server is compiled
E:\data\java\RMI\sql\server>j:\jdk12\bin\javac interSQL.java
E:\data\java\RMI\sql\server>j:\jdk12\bin\javac srvSQL.java
E:\data\java\RMI\sql\server>dir *.class
INTERS~1 CLA 451 03/12/99 17:54 interSQL.class
SRVSQL~1 CLA 3,238 03/12/99 17:54 srvSQL.class
- The Stub and Skel files are created
E:\data\java\RMI\sql\server>j:\jdk12\bin\rmic srvSQL
E:\data\java\RMI\sql\server>dir *.class
INTERS~1 CLA 451 03/12/99 17:54 interSQL.class
SRVSQL~1 CLA 3,238 03/12/99 17:54 srvSQL.class
SRVSQL~2 CLA 4,491 03/12/99 5:56 PM srvSQL_Stub.class
SRVSQL~3 CLA 2,414 03/12/99 5:56 p.m. srvSQL_Skel.class
- Move the files interSQL.class, srvSQL_Stub.class, and srvSQL_Skel.class to the client directory
E:\data\java\RMI\sql\client>dir
CLTSQL~1 JAV 3,486 03/11/99 11:39 cltSQL.java
INTERS~1 CLA 451 11/03/99 10:55 interSQL.class
SRVSQL~1 CLA 4,491 03/11/99 13:19 srvSQL_Stub.class
SRVSQL~2 CLA 2,414 03/11/99 13:19 srvSQL_Skel.class
- Compiling the client
E:\data\java\RMI\sql\client>j:\jdk12\bin\javac cltSQL.java
E:\data\java\RMI\sql\client>dir *.class
INTERS~1 CLA 451 03/11/99 10:55 interSQL.class
CLTSQL~1 CLA 2,839 03/12/99 18:00 cltSQL.class
SRVSQL~1 CLA 4,491 03/11/99 13:19 srvSQL_Stub.class
SRVSQL~2 CLA 2,414 03/11/99 1:19 PM srvSQL_Skel.class
9.3.6. Step 4: Testing with server & client on the same Windows machine
- The directory service is launched in a directory other than that of the server and client
- Place the following mypolicy file in the client and server directories
- Start the server
E:\data\java\RMI\sql\server>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=file:/e:/data/java/rmi/sql/server/ srvSQL
- Launch the client
E:\data\java\RMI\sql\client>j:\jdk12\bin\java -Djava.security.policy=mypolicy cltSQL srvSQL
sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connecting to the RMI server...
--> Connecting to the database...
<-- 200 Connection successful
--> Query: select name, current_stock from items order by current_stock desc
<-- 101 bike,31
<-- 101 trial3,13
<-- 101 water skis,13
<-- 101 canoe,13
<-- 101 panther,11
<-- 101 leopard,11
<-- 101 sperm whale,10
<-- 101 rifle, 10
<-- 101 bow,10
--> Query: update articles set stock_actu=stock_actu-1 where stock_actu<=11
<-- 100 5
--> Query: select name,current_stock from items order by current_stock asc
<-- 101 sperm whale,9
<-- 101 rifle,9
<-- 101 bow,9
<-- 101 panther,10
<-- 101 leopard,10
<-- 101 trial 3,13
<-- 101 water skis,13
<-- 101 canoe,13
<-- 101 bike,31
--> Query: end
--> Closing connection to remote database
<-- 200 Database closed
9.3.7. Step 5: Testing with a server on a Windows machine and a client on a Linux machine
- If necessary, stop the server and the directory service
- Transfer the client's .class files to a Linux machine
shiva[serge]:/home/admin/serge/java/rmi/sql/client#
$ dir *.class
-rw-r--r-- 1 serge admin 2839 Mar 11 14:37 cltSQL.class
-rw-r--r-- 1 serge admin 451 Mar 11 14:37 interSQL.class
- The server files are placed in a directory accessible to the Windows machine's HTTP server
D:\Inetpub\wwwroot\rmi\sql>dir
INTERS~1 CLA 451 03/11/99 10:55 interSQL.class
SRVSQL~1 CLA 3,238 03/11/99 13:19 srvSQL.class
SRVSQL~2 CLA 4,491 03/11/99 13:19 srvSQL_Stub.class
SRVSQL~3 CLA 2,414 03/11/99 1:19 p.m. srvSQL_Skel.class
MYPOLICY 81 06/08/98 15:01 mypolicy
- Restart the directory service
- Restart the server with different parameters than those used in the previous test
D:\Inetpub\wwwroot\rmi\sql>start j:\jdk12\bin\java -Djava.security.policy=mypolicy
-Djava.rmi.server.codebase=http://tahe.istia.univ-angers.fr/rmi/sql/ srvSQL
- Launch the client on the Linux machine
/usr/local/bin/jdk/bin/java cltSQL rmi://tahe.istia.univ-angers.fr/srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Query: select name,current_stock,min_stock from items order by name
<-- 101 arc,9,8
<-- 101 cachalot,9,6
<-- 101 canoe,13,7
<-- 101 test3,13,9
<-- 101 rifle,9,8
<-- 101 leopard,10,7
<-- 101 panther,10.7
<-- 101 water skis, 13.8
<-- 101 bike,31,8
--> Query: update articles set stock_actu=stock_mini where stock_mini<=7
<-- 100 4
--> Query: select name,current_stock,min_stock from items order by name
<-- 101 bow,9,8
<-- 101 sperm whale,6,6
<-- 101 canoe,7,7
<-- 101 test3,13,9
<-- 101 rifle,9,8
<-- 101 leopard,7,7
<-- 101 panther,7,7
<-- 101 water skis, 13.8
<-- 101 bike,31,8
--> Query: end
--> Closing the connection to the remote database
<-- 200 Database closed
9.3.8. Conclusion
This is an interesting application in that it allows access to a database from any workstation on the network. We could very well have written it in the traditional way using sockets, which is actually what is required in an exercise in the chapter on databases. If we were to write this application in the traditional way:
- we would have a client and a server that could be written in different languages
- the client and server would communicate by exchanging lines of text and would have a dialogue that might look something like this:
where the first two parameters specify where to find the server, and the next four indicate the connection parameters for the database to be used
The server might respond with something like:
to ask the server to execute an SQL query on the database connected to the client. separator is the character used to separate fields in the response lines.
The server might respond with something like
for a database update query, where n is the number of rows updated
if the query generated an error
if the query returned no results
if the query returned results. The rows returned by the server are the query results.
to close the connection to the remote database. The server may return a string indicating the result of this closure:
Here we see that if we are able to build a traditional application using a protocol of the type described above, we can deduce a possible structure for the RMI server. Where in the protocol we have a message from the client to the server of the type:
We can have a method within the RMI server
and this method, which is accessible to the client, must therefore be part of the server’s published interface.
To wrap this up, note that our server currently handles only one client: it cannot handle multiple clients as it is currently written. In fact, if a client connects to database B1, the server creates a Connection object with DB=DB1. If a second client requests a connection to database B2, the server establishes it with Connection DB=DB2, thereby breaking the first client’s connection to database B1.
9.4. Exercises
9.4.1. Exercise 1
Extend the previous SQL server so that it can handle multiple clients.
9.4.2. Exercise 2
Write the Java e-commerce applet presented in the exercises of the JDBC chapter so that it works with the RMI server from the previous exercise.