27 May 2013

Remote Method Invocation (RMI)


 RMI is all about invoking methods of an object that resides in another JVM. So from one JVM we can invoke methods of an object that is in another JVM. The another JVM might be on the same machine or it could be somewhere else on the network. This is the basis for EJB.

In this the server (which has very high configuration and speed and rich in hardware) exposes the remote objects via RMI Registry. Clients fetches the remote object reference via a proxy object and executes the methods on remote machine and gets the return type given by the remote method to the client’s JVM and uses it for further processing.

The following is in detail:-

What Server does?:-

The server creates a remote object and exports it to the RMI environment, which means it creates a stub or proxy for the remote object and the stub will be binded in the RMI Registry with a name.

What Client does?-

It locates (connects) the RMI registry on the remote machine(server) and  performs a lookup operation with the name of the object. The lookup operations will download the stub class of the remote object and its state by which it will be able to create the same object in the clients machine and returns the stub object. With the help of this stub object we will be able to execute the remote object's methods and can obtain the result of the remote method which means client obtains the  return type of the remote method and this result will be used for further processing in the client.

Here on client and server machine when an object has to be downloaded first the class and the state  of that object will be downloaded which help rebuilding the same object with the same behaviour. The remote object always resides on the server and will never be downloaded by any client. The client only downloads the proxy or stub.

                       
                                 


Creating Remote Objects:-

First we have to create a remote interface by which we expose methods to the network. The remote interface is created by extending the “java.rmi.Remote”. Now the interface which extends the “java.rmi.Remote” interface have to declare methods that have to be exposed to the network. All the methods in the remote interface should throw “java.rmi.RemoteException”.



Example:-

package rmi.common;

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

public interface TaskEngine extends Remote{

 Boolean executeTask(Task t)throws RemoteException;

}

The return type or parameters of the remote method should be of primitive data types or remote objects or serializable classes.

Once we create the remote interface we need to provide implementation for that. So a class which implements the remote interface should be created. This class may contain additional methods other than the ones declared in the remote interface but these additional methods are local and will not be exposed to network(other JVM).

Example Implementation:-

package rmi.server;

import java.rmi.RemoteException;

import rmi.common.Task;
import rmi.common.TaskEngine;

public class TaskEngineImpl implements TaskEngine{

 @Override
 public Boolean executeTask(Task t)throws RemoteException {
           return t.execute();
 }
 
}

Server program:-

The server program will bind the remote object in the RMI Registry. The server program keeps waiting to listen to clients.. As long as the remote object is not unbound or as long as the client holds the reference to the remote object the remote object will not be garbage collected.

Example:-
package rmi.server;

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

import rmi.common.TaskEngine;

public class Server {

    public static void main(String[]args)throws RemoteException{
          if (System.getSecurityManager() == null) {
                   System.setSecurityManager(new SecurityManager());
           }
           String name="taskExe";
           TaskEngine te=new TaskEngineImpl();
            TaskEngine stub =(TaskEngine) UnicastRemoteObject.exportObject(te, 0);
            Registry registry = LocateRegistry.getRegistry();
            registry.rebind(name, stub);
            System.out.println("TaskEngine bound");
    }
}

There are few things to be kept in mind when writing server program.

 The first thing is to set the security manager. If security manager is not set the files from the client system will not be downloaded as they can harm the computer by accessing the resources on the server computer. So the security manager has to be set.

remote object should be created and should be assigned to a variable of type remote interface.
The remote object needs to be passed to the “UnicastRemoteObject.exportObject(te, 0)” so that this method can create a stub and returns that. The stub should be held by the variable of remote interface data type. It also exports the required classes to the RMI environment.

The second argument in the “UnicastRemoteObject.exportObject(te, 0)” , an int, specifies which TCP port to use to listen for incoming remote invocation requests for the object. It is common to use the value zero, which specifies the use of an anonymous port. The actual port will then be chosen at runtime by RMI or the underlying operating system. However, a non-zero value can also be used to specify a specific port to use for listening. Once the exportObject invocation has returned successfully, the TaskEngineImpl remote object is ready to process incoming remote invocations.

using  “LocateRegistry.getRegistry()”  when no arguments passed we can get the RMI registry running on the same machine.

The methods of RMIRegistry such as bind, rebind, unbind can perform operations only on the registry running on the same machine. when these methods are invoked from a remote machine on the servers registry they don’t have any effect. so clients cannot bind or unbind the objects on server.

Client Program:-

package rmi.client;

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

import rmi.common.Task;
import rmi.common.TaskEngine;

public class Client {
        public static void main(String[] args)throws Exception {
                 if (System.getSecurityManager() == null) {
                       System.setSecurityManager(new SecurityManager());
                }
                Registry registry = LocateRegistry.getRegistry("localhost");
                TaskEngine te = (TaskEngine) registry.lookup("taskExe");
                 Task t=new HugeProcess();
                 if(te.executeTask(t)){
                       System.out.println("executed the task successfully on remote machine");
                  }

       }
}

Points that need to be observed when writing Client:-

 The first thing is to set the security manager if not already there on the system.
“registry.lookup("taskExe")” downloads the stub class, state of the stub object that is bound in the registry on the remote computer and creates the stub object and returns it to the variable of data type remote interface. Stub also implements all the interfaces implemented by remote object.

when “Task t=new HugeProcess()” invoked the Task object is created and when it is passed to the stub objects method as parameter, the server downloads the “HugeProcess” class, state and creates the task object back again on the server and executes the remote method on the server and the return value is passed to the client back again. Client used the return value of the remote method in further processing.

Remote Interface , any custom classes or interfaces provided as arguments and return types  to the remote methods should be made available to both client and server. In our example “TaskEngine”, “Task” should be made available to both server and client. The “Task” implementation “HugeProcess” should be only in the client, It does not need to be on the server.

Running Server and Client:-

First we need to create security policy files for client and server which are required by the security manager.

The client security policy for example “client.policy” has to be on the classpath of the clients machine and this policy file should grant all the permissions to the local code (classes which are not downloaded).

client policy file:-

grant codeBase "file:D:/workspace/example/bin/" {
    permission java.security.AllPermission;
};

Server policy file for example “server.policy” should be on the classpath of the servers machine as the server program security manager needs it.

servers policy:-

grant codeBase "file:D:/workspace/example/bin/" {
    permission java.security.AllPermission;
};

In both the policy files the “codeBase” should refer to the root folder where your class files are residing.

Once the policy files are set we should start the RMI registry on the servers machine. just type “rmiregistry” in the command prompt and that would run the register.


Running Server:- 

java -Djava.rmi.server.codebase=file:/D:/workspace/example/bin/
        -Djava.rmi.server.hostname=localhost
        -Djava.security.policy=server.policy
         rmi.server.Server

The above java command defines the following system properties:

 The java.rmi.server.codebase property specifies the location, a codebase URL, from which the definitions for classes originating from this server can be downloaded. If the codebase specifies a directory hierarchy (as opposed to a JAR file), you must include a trailing slash at the end of the codebase URL.

The java.rmi.server.hostname property specifies the host name or address to put in the stubs for remote objects exported in this Java virtual machine. This value is the host name or address used by clients when they attempt to communicate remote method invocations. By default, the RMI implementation uses the server's IP address as indicated by the java.net.InetAddress.getLocalHost API. However, sometimes, this address is not appropriate for all clients and a fully qualified host name would be more effective. To ensure that RMI uses a host name (or IP address) for the server that is routable from all potential clients, set thejava.rmi.server.hostname property.

The java.security.policy property is used to specify the policy file that contains the permissions you intend to grant.

Running Client:-

java     -Djava.rmi.server.codebase=file:D:/workspace/example/bin
           -Djava.security.policy=client.policy
            rmi.client.Client

The above java command defines the following system properties:

The location where the client serves its classes (the “HugeProcess” class) by using the java.rmi.server.codebase property

The java.security.policy property, which is used to specify the security policy file that contains the permissions you intend to grant to various pieces of code.

No comments: