Sunday 7 September 2014

A Simple Guide to Java RMI


A brief introduction is given first, if you want directly to set up and start coding  jump here.  But it is highly recommended to read basics of RMI first

Introduction:

Java RMI is a mechanism that allows one to invoke a method on an object that exists in another address
space. The other address space could be on the same machine or a different one.

Following is logical diagram of RMI 


Logical Diagram of RMI


RMI applications often comprise two separate programs, a server and a client. A typical server program creates some remote objects, makes references to these objects accessible, and waits for clients to invoke methods on these objects. A typical client program obtains a remote reference to one or more remote objects on a server and then invokes methods on them. RMI provides the mechanism by which the server and the client communicate and pass information back and forth.

Here is step by step tutorial to run Hello World program.
Using RMI to develop a distributed application involves these general steps:
  1. Designing and implementing the components of your distributed application.
  2. Compiling sources.
  3. Making classes network accessible.
  4. Starting the application.

Designing and Implementing the Application Components

First, determine your application architecture, including which components are local objects and
which components are remotely accessible. This step includes:
  • Defining the remote interfaces (HelloWorldInterface.java):
    A remote interface specifies the methods that can be invoked remotely by a client. Executable(jar) file of this interface is used by both server and client
          Now let’s first program remote interface

Remote interface should extend from java.rmi.Remote
It is used to define methods which can be invoke from clients. In our case we will define a simple interface with one method to receive "Hello, How are you" from client and respond with "I am Fine"


/**
* Remote Interface
*HelloWorldInterface.java
*/

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface HelloWorldInterface extends Remote{

  /**
  * Remotely invocable method.
  * @exception RemoteException if the remote invocation fails.
  */
    public String greetings(String clientMessage) throws RemoteException;

}

  • This file(HelloWorld.java) is implementation of interface define in previous file and it should only be present on server
/**
* Remote Interface
*HelloWorld.java
*/

import java.rmi.RemoteException;

public class HelloWorld implements HelloWorldInterface{
  String reply_message = "I am Fine";
  protected HelloWorld() throws RemoteException {
    super();
    // TODO Auto-generated constructor stub
  }

  @Override
  public String greetings(String clientMessage) throws RemoteException {
    // TODO Auto-generated method stub
    System.out.println("Client: "+ clientMessage);
    return "I am Fine";
  }
}





Okay so we are done with remote class code. Now we will write simple server and client side program to use Remote Interface and invoke its method.

1st step is to create java security manager and give it permission to access your code-base both in client and server machines using Java security manager. Creating java security manager is quite simple. You just need to create securityManager object in both your client and server files.
Here is sample code for creating securityManager.  But don't worry about where to put it , it is already including in both HelloWorldServer.java and HelloWorld.Lient.java files

if (System.getSecurityManager() == null) {
  System.setSecurityManager(new SecurityManager());
}
Here is a sample server program

By default RMI use port 1099.

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class HelloWorldServer {
  private static int PORT_NO = 1099;
  public static void main(String[] args){
    // TODO Auto-generated method stub
    //sending port number from command line , you can skip this if you want to use default port         //number
    if(args.length > 1) {

      for(int i=0; i < args.length ; i++){

        if(args[i].equals("-port"))

          PORT_NO = Integer.parseInt(args[i+1]);

       }
    }

    ////////Giving permision to our code base /// must be careful while giving path//
    if (System.getSecurityManager() == null) {
      System.setSecurityManager(new SecurityManager());
    }
    String name = "HelloWorldInterface";
    HelloWorldInterface engine;
    try {
      engine = new HelloWorld();
      HelloWorldInterface stub =
      (HelloWorldInterface) UnicastRemoteObject.exportObject(engine, 0);
      Registry registry = LocateRegistry.getRegistry();
      registry.rebind(name, stub);
      System.out.println("ComputeEngine bound");
    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}


Explanation of Server program:
The java.rmi.registry.Registry remote interface is the API for binding (or registering) and looking up remote objects in the registry. Rebind method invocation makes a remote call to the RMI registry on the local host. Like any remote call, this call can result in a RemoteException being thrown, which is handled by the catch block at the end of the main method.

Client program:

import java.rmi.RMISecurityManager;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class HelloWorldClient {
  private static String HOST = "localhost";
  private static int PORT_NO = 1099;
  private static String message = "Hello, How Are you?";

  public static void main(String[] args) {
    // TODO Auto-generated method stub
    if(args.length > 1){
      for(int i=0; i < args.length ; i++){

        if(args[i].equals("-port"))
          PORT_NO = Integer.parseInt(args[i+1]);

        if(args[i].equals("-host"))
          HOST = args[i+1];
      }
    }

    if (System.getSecurityManager() == null) {
      System.setSecurityManager(new SecurityManager());
    }
    String name = "HelloWorldInterface";
    Registry registry;
    try {
     //LocateRegistry.getRegistry take paramters  port number and host address of your server
      registry = LocateRegistry.getRegistry(HOST, PORT_NO);
      HelloWorldInterface remote = (HelloWorldInterface) registry.lookup(name);
      System.out.println("Server: " + remote.greetings(message));
    } catch (Exception e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }
}

For running it on your local machine address should be "localhost" and to run it on EC2/or any other server use public address of your instance similar to ec2-x-x-x-x.compute-1.amazonaws.com in case of amazon ec2 client

Compiling Files:

Now we have completed our code . So lets compile and execute it .You can also use
eclipse/netbeans etc for compiling but I am using terminal/commandpromt.
First we need to compile our interface which is pretty simple.
  • javac HelloWorldInterface.java
  • javac -classpath HelloWorld.java
Now to compile client code we only need interface file so include your interface file in classpath
  • javac -classpath HelloWorldInterface HelloWorldCLient.java
To compile server side add both interface and your main code file in class path
  • javac -classpath HelloWorldInterface;HelloWorld HelloWorldServer
(
to run it locally as a shortcut you can compile simply with javac *.java)
Now we discuss different cases 



Case1: Both Client and server on same machine:


First start RMI registery server
  • rmiregistry &
After this you'll see it's process id


  • Now you need policy files to allow RMI registry server to access your code-base. For this create two policy files and give address to your code-base.
Client.policy
grant codeBase "file:///home/abubakar/Cloud/Rmitutorial/src" {
  permission java.security.AllPermission;
};



Server.policy
grant codeBase "file:///home/abubakar/Cloud/Rmitutorial/src" {
  permission java.security.AllPermission;
};



(For simplicity to run it locally I have placed all my files in same folder).
Now execute server file


Include path to your policy file
   simple command
 
   jave -Djava.security.policy =<path_to_your_policy_file> Server_or_Client_executablefile
  • java -Djava.security.policy=/home/abubakar/Cloud/Rmitutorial/src/server.policy HelloWorldServer
Execute Client
  • java -Djava.security.policy=/home/abubakar/Cloud/Rmitutorial/src/client.policy HelloWorldClient
And there you go you will see exchange of greeting messages between client and server

Case: 2 Server on EC2(any other server) and client on local machine:
Note: you can use this to connect any server just change address to your remotely located server

For a change I am using fedora as a server, you can use any other UNIX flavor

Running server on EC2 instance is bit tricky. Before starting server we need to place our server program on some network accessible place. For this we will use apache2 server and create public_html file in /home/fedora which will be accessible over network.
For this install apache2 on both local and EC2 instance
  • sudo yum install apache2





  • mkdir /home/fedora public_html
  • sudo a2enmod userdir
  • chmod -R 755 ~/public_html
  • sudo /etc/init.d/apache2 restart

  • Now to test its works open ec2 public DNS on your local browser you'll see welcome file
    okay we have completed installing apache-server. Now transfer your all files except HelloWorldclient.class and client.policy to your EC2 instance.
    Place your server.class HelloWorldInterface.class in your public_html folder on ec2 (donot place your actual implementation file(HelloWorld.class ) in public_html instead place it in any other folder)
    Change file path in server.policy and point it to the folder containing HelloWorld.class
    Now start RMI server on EC2 Instance
    • rmiregistry &
    To understand long complex command you can use table given below for help.

    start your server file
    • sudo java -Djava.security.policy=/home/fedora/helloworld/helloworld-rmi/server.policy -Djava.rmi.server.hostname=ec2-54-224-236-43.compute-1.amazonaws.com:2451 -Djava.rmi.server.codebase=//home/fedora/helloworld/helloworld-rmi/ HelloWorldServer
    Now our server is ready. Let’s start our client, before this place your HelloWorldInterface.class and HelloWorldClient.class in locally created public_html folder and also change your path in client.policy file to your public_html folder
    •  java -cp /home/abubakar/public_html/HelloWorldInterface -Djava.rmi.server.codebase=/localhost/public_html/ -Djava.security.policy=/home/abubakar/Cloud/Assignments/HelloWorld/src/client.policy -Djava.rmi.server.hostname=ec2-54-224-236-43.compute-1.amazonaws.com HelloWorldClient ec2-54-224-236-43.compute-1.amazonaws.com


    Now your server will receive 'How are you' and respond with 'I am fine' 


    Here are CodeFiles I used as a sample.

    For any issue, comment ,suggestion and need further help please drop a comment.

    Thanks
    Happy Coding :)