Sunday, August 7, 2016

Java RMI Remote Method Invocation - An insight into distributed applications

Java Remote Method Invocation enables a programmer to create distributed Java technology based applications in which object running in one JVM is able to invoke methods of a Java Application running in another JVM , even on different host. RMI uses object serialization on marshal and unmarshal parameters.

Overview of RMI Applications:

RMI applications comprise of two programs, a client and a server. A server program is a basically a Java application that provides remote objects by implementing a Remote Interface, makes references to such objects accessible and waits for a client to remotely invoke methods on such objects. A client obtains the reference to such remote objects and invokes methods on such objects.

RMI provides the mechanism by which the client and server communicate with each other and pass information back and forth. Such applications are called distributed object applications. RMI is implemented based on the Registry pattern.

As explained in the Registry pattern , a server calls the RMI Registry to register(bind) the remote object with a serviceName | role.The client lookup the Server's registry by the service name and gets the reference to a proxyObject that is used to invoke the remote methods of the object at the server side.

-> Locate remote objects using RMI Registry
-> Communicate with remote Objects.
-> Load class definitions of the objects passed back and forth.

Dynamic Code Loading -


The important feature of RMI is its ability to download the definition of an object's class if the class is not defined in the receiver's JVM. RMI sends the objects by their actual classes, not changing the object's behavior in the other JVM, enabling new types and behaviors to be introduced into a remote JVM, thus dynamically extending the behavior of an application.

 ----------------------------------------------------------------

Remote Object: 

  • An object becomes remote by implementing a remote interface which:
    -> extends the interface java.rmi.Remote.
    -> each method of the this interface declares java.rmi.RemoteException in its throws clause, in addition to any application-specific exceptions.

RMI treats a remote object differently from a non-remote object when an object is passed from one JVM to another JVM. Rather than making a copy of the implementation object in the receiving VM, RMI passes a remote stub for a remote object, that acts as a proxy, for the remote object. The client invokes a method on the local stub , which is responsible for carrying out the method invocation on the remote object.

Creating distributed applications using RMI

  1. Designing and implementing the components of the distributed application.
  2. Compiling Resources
  3. Making the classes accessible on the network.
  4. Running the application
Let's start with designing the architecture of our application. I will use a simple calculator application to be a distributed application. The methods exposed are sum, diff, prod and divide.

Step 1. Define the Remote Interface:

package remoteInterface;

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


public interface Calculator extends Remote {

public int sum(int a, int b) throws RemoteException;
public int diff(int a, int b) throws RemoteException;
public int prod(int a, int b) throws RemoteException;
public int divide (int a, int b) throws RemoteException;
}

Like previously said , a remote interface should extend java.rmi.remote and each of its methods should throw RemoteException.

Step 2. Implementing the Remote Object:

package remoteServer;
import remoteInterface.Calculator;

public class RemoteCalculator implements Calculator {

@Override
public int sum(int a, int b) {
return a + b;
}

@Override
public int diff(int a, int b) {
return a - b;
}

@Override
public int prod(int a, int b) {
return a * b;
}

@Override
public int divide(int a, int b) {
return a / b;
}
}

Above we have implemented the remote object whose methods are to invoked by another application running in another JVM.

Step 3: Create and export a remote object and register it with Java RMI registry:

package remoteServer;

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

import remoteInterface.Calculator;

public class RegisterRemoteCalculator {

public static void main(String[] args) {
try {
Calculator stub = (Calculator) UnicastRemoteObject.exportObject(
new RemoteCalculator(), 0);
Registry r = LocateRegistry.getRegistry();
r.bind("calculator", stub);
} catch (RemoteException | AlreadyBoundException e) {
e.printStackTrace();
}
}
}

The above class defines a main method that creates a remote object of class RemoteCalculator. Additionally, the remote object must be exported to the Java RMI runtime so that it may receive incoming remote calls.

The static method UnicastRemoteObject.exportObject exports the supplied remote object to receive incoming remote method invocations on an anonymous TCP port and returns the stub / proxy for the remote object to pass to clients. Post this call , the run-time may begin to listen on a new server socket or may use a shared server socket to accept incoming remote calls for the remote object. The returned stub implements the same set of remote interfaces as the remote object's class and contains the host name and port over which the remote object can be contacted.

Java RMI provides a registry API for applications to bind a name to a remote object's stub and for clients to look up remote objects by name in order to obtain their stubs. A Java RMI registry is a simplified name service that allows clients to get a reference (a stub) to a remote object.

The static method LocateRegistry.getRegistry returns a stub that implements the remote interface java.rmi.registry.Registry and sends invocations to the registry on localhost on the default port of 1099.

The bind method is then invoked on the registry stub in order to bind the remote object's stub to the name "calculator" in the registry.

At this point ensure your /etc/hosts file contains an entry for 127.0.0.1 for localhost and the ipAddress of the machine to the domainName, and such information is sent over to the client with the stub for the client to be able to connect to the remote Object.

Step 4: Implementing the Client:

package client;

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

import remoteInterface.Calculator;

class Calculate {

private Calculator remoteProxy;

{
try {
Registry r = LocateRegistry.getRegistry("192.168.23.137");
remoteProxy = (Calculator) r.lookup("calculator");
} catch (RemoteException | NotBoundException e) {
e.printStackTrace();
}
}

public Calculate() {
}

public int add(int a, int b) throws RemoteException {
return remoteProxy.sum(a, b);
}

public int diff(int a, int b) throws RemoteException {
return remoteProxy.diff(a, b);
}

public int mul(int a, int b) throws RemoteException {
return remoteProxy.prod(a, b);
}

public int div(int a, int b) throws RemoteException {
return remoteProxy.quotient(a, b);
}
}

public class Caller {
public static void main(String[] args) {
try {
Calculate client = new Calculate();
System.out.println(client.add(Integer.valueOf(args[0]),
Integer.valueOf(args[1])));
System.out.println(client.diff(Integer.valueOf(args[0]),
Integer.valueOf(args[1])));
System.out.println(client.mul(Integer.valueOf(args[0]),
Integer.valueOf(args[1])));
System.out.println(client.diff(Integer.valueOf(args[0]),
Integer.valueOf(args[1])));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}

The above Client first obtains the stub for the registry by invoking the static LocateRegistry.getRegistry("192.168.23.137") method with the ipAddress of the server where the remote Object is defined.If no hostname is specified, then null is used as the hostname indicating that the localhost address should be used.

Next, the client invokes the remote method lookup on the registry stub to obtain the stub for the remote object(calculator) here, from the server's registry.

Finally, the client invokes the the calculator methods one by one on the remote object's stub, sending in two command line arguments, which causes the following actions to happen:


  1. The client-side runtime opens a connection to the server using the host and port information in the remote object's stub and then serializes the call data.
  2. The server-side runtime accepts the incoming call, dispatches the call to the remote object, and serializes the result to the client.
  3. The client-side runtime receives, deserializes, and returns the result to the caller.
  4. The response message returned from the remote invocation on the remote object is then printed to System.out.

 ----------------------------------------------------------------

Running the distributed applications :
-> Set the JAVA CLASSPATH to include the bin directory which contains the classfiles on the server side 

anmol@anmol-bt:$ echo $CLASSPATH
/home/anmol/workspace/rmiJava8/bin

This step is needed so the rmiRegistry can lookup for class files on the CLASSPATH when the client connects to the rmiregistry to lookup for the remoteObject.
-> Next start the rmiRegistry
rmiregistry &
->  Step 1 , 2 and 3 on the server .

java remoteServer.RegisterRemoteCalculator

-> Step 4 on the Client

java client.Caller 9 5 

Output : 
14 Sum
4 Difference
45 Product
1 Quotient

For further analysis on this application , you can try and put a StackTraceElement on the server remote Object and as well print at the client side the list of names registered with the server's Registry. 

Feel free to comment and improve the content of this blog.

Happy Learning 

No comments:

Post a Comment