Sunday, 6 November 2011

RMI Basics

JAVA Remote Method Invocation (RMI)

The Java Remote Method Invocation API or Java RMI it’s a Java API that performs the object-oriented equivalent of Remote Procedure Calls (RPC).

RPC – from wiki:

In computer science, a remote procedure call (RPC) is an inter-process communication that allows a computer program to cause a subroutine or procedure to execute in another address space (commonly on another computer on a shared network) without the programmer explicitly coding the details for this remote interaction. That is, the programmer writes essentially the same code whether the subroutine is local to the executing program, or remote. When the software in question uses object-oriented principles, RPC is called remote invocation or remote method invocation.

Many different (often incompatible) technologies can be used to implement the concept.

Sequence of events during a RPC

  1. The client calls the client stub. The call is a local procedure call, with parameters pushed on to the stack in the normal way.
  2. The client stub packs the parameters into a message and makes a system call to send the message. Packing the parameters is called marshalling.
  3. The kernel sends the message from the client machine to the server machine.
  4. The kernel on the server machine passes the incoming packets to the server stub.
  5. Finally, the server stub calls the server procedure. The reply traces the same steps in the reverse direction.
For more information of RPC check wiki.

Coming back to the RMI:
  1. The original implementation depends on Java Virtual Machine (JVM) class representation mechanisms and it thus only supports making calls from one JVM to another. The protocol underlying this Java-only implementation is known as Java Remote Method Protocol (JRMP).
  2. In order to support code running in a non-JVM context, a CORBA version was later developed.

The programmers of the original RMI API generalized the code somewhat to support different implementations, such as a HTTP transport. Additionally, the ability to pass arguments "by value" was added to CORBA in order to support the RMI interface. Still, the RMI-IIOP and JRMP implementations do not have fully identical interfaces.

RMI functionality comes in the package java.rmi, while most of Sun's implementation is located in the sun.rmi package. Note that with Java versions before Java 5.0 developers had to compile RMI stubs in a separate compilation step using rmic. Version 5.0 of Java and beyond no longer require this step.

Jini offers a more advanced version of RMI in Java. It functions similarly but provides more advanced searching capabilities and mechanisms for distributed object applications.
The following classes implement a simple client-server program using RMI that displays a message. For example check wiki

I have implemented another example, in this we implement a service that can calculate the square of a number, and the power of two numbers (238 for example). Due to the large size of the numbers, we'll use the java.math.BigInteger class for returning values rather than an integer or a long.

Writing an interface

The first thing we need to do is to agree upon an interface, An interface is a description of the methods we will allow remote clients to invoke. Let's consider exactly what we'll need.
  1. A method that accepts as a parameter an integer, squares it, and returns a BigInteger
    public BigInteger square ( int number_to_square );
  2. A method that accepts as a parameter two integers, calculates their power, and returns a BigInteger
    public BigInteger power ( int num1, int num2 );
Once we've decided on the methods that will compose our service, we have to create a Java interface. An interface is a method which contains abstract methods; these methods must be implemented by another class. Here's the source code for our service that calculates powers.

import java.math.BigInteger;
import java.rmi.*;
 
//
// RemoteServiceInterface
//
// Interface for a RMI service that calculates powers
//
public interface RemoteServiceInterface extends java.rmi.Remote{
     
      // Calculate the square of a number
      public BigInteger square ( int number )
            throws RemoteException;

      // Calculate the power of a number
      public BigInteger power  ( int num1, int num2)
            throws RemoteException;

}

Our interface extends java.rmi.Remote, which indicates that this is a remote service. We provide method definitions for our two methods (square and power), and the interface is complete. The next step is to implement the interface, and provide methods for the square and power functions.

Implementing the interface

Implementing the interface is a little more tricky - we actually have to write the square and power methods! Don't worry if you're not sure how to calculate squares and powers, this isn't a math lesson. The real code we need to be concerned about is the constructor and main method.
We have to declare a default constructor, even when we don't have any initialization code for our service. This is because our default constructor can throw a java.rmi.RemoteException, from its parent constructor in UnicastRemoteObject. Sound confusing? Don't worry, because our constructor is extremely simple.

      public RemoteServer() throws RemoteException {
            super();
      }

Our implementation of the service also needs to have a main method. The main method will be responsible for creating an instance of our RemoteServer, and registering (or binding) the service with the RMI Registry. Our main method will also assign a security manager to the JVM, to prevent any nasty surprises from remotely loaded classes. In this case, a security manager isn't really needed, but in more complex systems where untrusted clients will be using the service, it is critical.

   public static void main ( String args[] ) throws Exception
    {
        // Assign a security manager, in the event that dynamic
            // classes are loaded
        if (System.getSecurityManager() == null)
            System.setSecurityManager ( new RMISecurityManager() );

       
        try { //special exception handler for registry creation
            LocateRegistry.createRegistry(1099);
            System.out.println("java RMI registry created.");
        } catch (RemoteException e) {
            //do nothing, error means registry already exists
            System.out.println("java RMI registry already exists.");
        }
       
       
        // Create an instance of our power service server ...
        RemoteServer svr = new RemoteServer();

        // ... and bind it with the RMI Registry
        Naming.rebind ("RemoteService", svr);

        System.out.println ("Service bound....");
    }

Once the square and power methods are added, our server is complete. Here's the full source code for the RemoteServer.

import java.math.*;
import java.rmi.*;
import java.rmi.server.*;
 
//
// RemoteServer
//
// Server for a RMI service that calculates powers
//
public class RemoteServer extends UnicastRemoteObject implements RemoteServiceInterface {

      public RemoteServer() throws RemoteException {
            super();
      }

      //Calculate the power of a number
      public BigInteger power(int num1, int num2) throws RemoteException {
            String numrep = String.valueOf(num1);
            BigInteger bi = new BigInteger (numrep);
           
            //Calculate the power
            bi = bi.pow(num2);

            return bi;
      }

      //Calculate the square of a number
      public BigInteger square(int number) throws RemoteException {
            String numrep = String.valueOf(number);
            BigInteger bi = new BigInteger (numrep);

            // Square the number
            bi.multiply(bi);

            return (bi);
      }
     
     
      public static void main ( String args[] ) throws Exception
    {
        // Assign a security manager, in the event that dynamic
            // classes are loaded
        if (System.getSecurityManager() == null)
            System.setSecurityManager ( new RMISecurityManager() );

       
        try { //special exception handler for registry creation
            LocateRegistry.createRegistry(1099);
            System.out.println("java RMI registry created.");
        } catch (RemoteException e) {
            //do nothing, error means registry already exists
            System.out.println("java RMI registry already exists.");
        }
       
       
        // Create an instance of our power service server ...
        RemoteServer svr = new RemoteServer();

        // ... and bind it with the RMI Registry
        Naming.rebind ("RemoteService", svr);

        System.out.println ("Service bound....");
    }

}


Writing a RMI client

 

What good is a service, if you don't write a client that uses it? Writing clients is the easy part - all a client has to do is call the registry to obtain a reference to the remote object, and call its methods. All the underlying network communication is hidden from view, which makes RMI clients simple.
Our client must first assign a security manager, and then obtain a reference to the service. Note that the client receives an instance of the interface we defined earlier, and not the actual implementation. Some behind-the-scenes work is going on, but this is completely transparent to the client.

    // Assign security manager
    if (System.getSecurityManager() == null)
    {
        System.setSecurityManager   (new RMISecurityManager());
    }

    // Call registry for RemoteService
   RemoteService service = (RemoteService) Naming.lookup
        ("RemoteService");

To identify a service, we specify an RMI URL. The URL contains the hostname on which the service is located, and the logical name of the service. This returns a Service instance, which can then be used just like a local object reference. We can call the methods just as if we'd created an instance of the remote RemoteServer ourselves.

// Call remote method
System.out.println    ("Answer : " + service.square(value));
        // Call remote method
        System.out.println    ("Answer : " + service.power(value,power));

Writing RMI clients is the easiest part of building distributed services. In fact, there's more code for the user interface menu in the client than there is for the RMI components! To keep things simple, there's no data validation, so be careful when entering numbers. Here's the full source code for the RMI client.

import java.rmi.*;
import java.rmi.Naming;
import java.io.*;
 
//
//
// RMIClient
//
//
public class RMIClient {
     
      public static void main(String args[]) throws Exception
      {
            // Check for hostname argument
            if (args.length != 1)
            {
                  System.out.println
                  ("Syntax - RemoteServiceClient host");
                  System.exit(1);
            }

            // Assign security manager
            if (System.getSecurityManager() == null)
            {
                  System.setSecurityManager
                  (new RMISecurityManager());
            }

            // Call registry for RemoteService
            RemoteServiceInterface service = (RemoteServiceInterface) Naming.lookup
                  ("RemoteService");

            DataInputStream din = new
                  DataInputStream (System.in);

            for (;;)
            {
                  System.out.println
                    ("1 - Calculate square");
                  System.out.println
                    ("2 - Calculate power");
                  System.out.println
                    ("3 - Exit"); System.out.println();
                  System.out.print ("Choice : ");

                  String line = din.readLine();
                  Integer choice = new Integer(line);
                 
                  int value = choice.intValue();

                  switch (value)
                  {
                  case 1:
                    System.out.print ("Number : ");
                    line = din.readLine();System.out.println();
                    choice = new Integer (line);
                    value  = choice.intValue();
                 
                    System.out.println("Sending "+value+" to server..");
                    // Call remote method
                    System.out.println
                    ("Answer : " + service.square(value));
                       
                    break;
                  case 2:
                    System.out.print ("Number : ");
                    line = din.readLine();                             
                    choice = new Integer (line);
                    value  = choice.intValue();
                       
                    System.out.print ("Power  : ");
                    line = din.readLine();
                    choice = new Integer (line);
                    int power = choice.intValue();

                    // Call remote method
                    System.out.println
            ("Answer : " + service.power(value, power));

                    break;
                  case 3:
                    System.exit(0);
                  default :
                    System.out.println ("Invalid option");
                  break;
                  }
            }
      }

}

Running the client and server

Our example was extremely simple. More complex systems, however, might contain interfaces that change, or whose implementation changes. To run this article's examples, both the client and server will have a copy of the classfiles, but more advanced systems might share the code of the server on a webserver, for downloading as required. If your systems do this, don't forget to set the system property java.rmi.server.codebase to the webserver directory in which your classes are stored!

1.     Start the rmiregistry

To start the registry, Windows users should do the following (assuming that your java\bin directory is in the current path):-
start rmiregistry 
 
To start the registry, Unix users should do the following:-
rmiregistry &

2.     Compile the server

Compile the server, and use the rmic tool to create stub files.

3.     Start the server

From the directory in which the classes are located, type the following:-
java RemoteServer

4.     Start the client

You can run the client locally, or from a different machine. In either case, you'll need to specify the hostname of the machine where you are running the server. If you're running it locally, use localhost as the hostname.

java RMIClient localhost