Skip to content

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:

  1. Client/server applications are Java applications at both ends of the communication
  2. The client can use objects located on the server as if they were local
  3. 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:

  1. 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.
  2. 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:

  1. the method that performs the echo
  2. 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:

  1. 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.
  2. 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:

Naming.rebind(String name, Remote obj)

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

  1. reads a line typed on the keyboard
  2. sends it to the echo server
  3. displays the response it sends
  4. 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:

            server = (interEcho) Naming.lookup(urlService);

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:

    rmi://machine:port/service_name

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:

E:\data\java\RMI\echo\client>dir *.java

CLTECH~1 JAV         1 427  03/09/99  16:08 cltEcho.java

as well as the interEcho.class file that was generated during server compilation:

E:\data\java\RMI\echo\client>dir *.class

INTERE~1 CLA           256  03/09/99  15:59 interEcho.class

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:

            server = (interEcho) Naming.lookup(urlService);                

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:

E:\data\java\RMI\echo\server>j:\jdk12\bin\rmic srvEcho

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:

start j:\jdk12\bin\rmiregistry

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:

E:\data\java\RMI\echo\server>start j:\jdk12\bin\java srvEcho

The echo server runs in a new DOS window and displays the requested output:

Echo server ready

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:

grant {
    // Allow everything for now
    permission java.security.AllPermission;
};

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.

Image

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:

    200 - Connection successful
    500 - Connection failed

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:

    100 n
    for a database update query, where n is the number of rows updated
    500 error message
    if the query generated an error
    501 No results
    if the query returned no results
    101 row1
    101 line2
    101 ...
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:

    200 Database closed
    500 Error closing database (error message)

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 driverBaseURL id password separator

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:

    srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,

with here:

urlserviceAnnuaire    srvSQL
RMI name of the SQL server
driver     sun.jdbc.odbc.JdbcOdbcDriver
the standard driver for databases with an ODBC interface
dbUrl    jdbc:odbc:articles
to use an articles database listed in the Windows machine&#x27;s public ODBC databases
id    null
no identity
mdp    null
no password
separator    , 
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
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • It prompts the user to type an SQL query
  • it sends it to the SQL server
executeSQL(query, ‘’, ‘’);
  • 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
F:\>start j:\jdk12\bin\rmiregistry
  • Place the following mypolicy file in the client and server directories
grant {
    // Allow everything for now
    permission java.security.AllPermission;
};
  • 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
F:\>start j:\jdk12\bin\rmiregistry
  • 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:
client: connect machine port driver urlBase id password

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:

200 - Connection successful
500 - Connection failed
client: executeSQL query, separator

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

    100 n
    for a database update query, where n is the number of rows updated
    500 error message
    if the query generated an error
    501 No results
    if the query returned no results
    101 row1
    101 line2
    101 ...
if the query returned results. The rows returned by the server are the query results.
client: close

to close the connection to the remote database. The server may return a string indicating the result of this closure:

    200 Database closed
    500 Error closing database (error message)

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:

    method param1 param2 ... paramq

We can have a method within the RMI server

    String command(param1, param2, ..., paramq)

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.