Remote Method Invocation in Java: An Example

In this section, we shall develop another powerful distributed application using RMI technology that makes use of the power of dynamic class downloading.

In this application [Figure 14.17:], the server creates a remote object called scheduler, which accepts jobs from the clients, executes these jobs locally, calculates the execution time, and finally returns the output as well as completion time. This way, clients can execute their jobs remotely on a powerful computer, or a computer having specialized hardware or one that has special permission.

The interesting part of the application is that the scheduler does not have to define the jobs that it executes. Clients can create their custom jobs as and when required and submit them to the scheduler for execution. The only restriction imposed on a job is that its class must implement an interface defined by the scheduler. The class definition of a job can be downloaded at runtime from the client that submits the job using RMI technology. Once the class file is available, the scheduler can execute the job locally using its own resource.

How can a scheduler return the result to the client? If the result’s class is a built-in class, the client already has the class definition and can use it easily. However, if the result’s class is a scheduler- defined custom class, how can the client get it? Again there is no problem, since the client can download it from the server side and get the result. The only requirement of a result object is that its class implements an interface known to the client.

This way, the scheduler can execute arbitrary jobs without any prior knowledge of the job’s class definition. Clients can also get the result without any prior knowledge of the result’s class definition. The Java RMI runtime environment will download the necessary class files from the specified location as and when required. This way, Java RMI allows us to change the behaviour of an object and install it in a remote machine dynamically.

1. Writing an RMI server

The server has three parts: scheduler interface, an implementation of that interface, and code that creates the scheduler object.

1.1. Write an Interface

In this section, we shall describe the interface Scheduler that defines the client’s view of the remote object. The Scheduler interface makes a connection between the server and the client. Here is the source code (scheduier.java) for Scheduler interface.

package intf;

import java.rmi.Remote;

import java.rmi.RemoteException;

public interface Scheduler extends Remote {

Result run(Job aJob) throws RemoteException;

}

The interface has a single method run() which will be used by clients to submit their jobs. This interface uses two other interfaces Job and Result. The interface Job defines the structure of a job to be submitted by the client. Here is the source code (Job.java) for the Job interface.

package intf;

public interface Job {

String run();

}

The Job interface defines a single method run() that actually executes the job and returns the result as a string. The class of every job submitted by clients must implement this interface and must define the run() method. The scheduler must download this class definition to execute the job locally.

The Result interface describes the structure of the result returned by the scheduler. Here is the source code (Result.java) for Result interface.

package intf;

public interface Result {

String output();

double completionTime();

}

It defines two methods, output() and completionTime(). The methods output() and completionTime() return output and execution time of the job, respectively. The Result object’s class must implement this interface. The client must download this Result object’s class definition to get the result.

Since, Java’s object serialization procedure is used by RMI to transfer objects, classes that implement Job and Result must implement the java.io.Serializable interface.

1.2. Implement the interface

The server defines two classes: SchedulerImpl and ResultImpl, which implements remote interface Scheduler and Result, respectively. Here is the source code (ResultImpl. java.) for

ResultImpl class.

package impl;

import java.io.Serializable;

import intf.Result;

public class ResultImpl implements Result, Serializable {

String output;

double completionTime;

public ResultImpl(String o, double c) { output = o; completionTime = c;

}

public String output() { return output; }

public double completionTime() {       return completionTime; }

}

An object of this class represents the result of a job in terms of its output and completion time. This result object, generated by the scheduler, will be transferred to the client side and hence must be serializable. This object is indeed serializable as its class implements the Serializable interface. The definition of ResultImpl must be downloaded to the client side to reconstruct the result object.

Here is the source code (SchedulerImpl.java.) for SchedulerImpl class.

package impl;

import java.rmi.RemoteException; import intf.*;

public class SchedulerImpl implements Scheduler

{

public SchedulerImpl()

{

super();

}

public Result run(Job aJob) throws RemoteException

{

double startTime = System.nanoTime();

String output = aJob.run();

double endTime = System.nanoTime();

return new ResultImpl(output, endTime-startTime);

}

}

In the run() method of the scheduler object, the job is executed. Note that the class for job object is defined by the client. As a result, the scheduler does not have any idea about the job. So, it simply calls the run() method on the job object. The server must download the class definition of the job object from the client side before calling the run() method. It calculates the execution time, creates a result object, and returns it.

1.3. Implement the server

A server first creates an instance of the SchedulerImpl class using the usual syntax as follows:

SchedulerImpl scheduler = new SchedulerImpl();

This object is then exported to the RMI runtime so that it can accept the incoming remote method call using the following piece of code [Figure 14.18]:

Scheduler stub = (Scheduler) UnicastRemoteObject.exportObject(scheduler, 0);

To register this stub with the object registry, we need to have a reference to object registry.

Registry registry = LocateRegistry.getRegistry();

The stub is then registered with object registry as follows:

String name = “Scheduler”; registry.rebind(name, stub);

The entire execution sequence is shown in Figure 14.18. The entire piece of code is embedded in a try-catch block.

Since class files of the Job will be downloaded to the Scheduler, it is always safe to install a security manager that will protect access to the system resources by this downloaded code. If the code upon download performs any operation, the security manager will check whether the downloaded code has the privilege to do that operation and takes necessary actions. The following code is used to install a security manager:

if (System.getSecurityManager() == null)

{

System.setSecurityManager(new SecurityManager());

}

A security policy must be specified which will be used by the security manager. We shall specify the security policy during the execution of the application. The source code for Server is stored in the file Server.java. The source code for the Server class is given as follows:

package impl;

import java.rmi.registry.LocateRegistry;

import java.rmi.registry.Registry;

import java.rmi.server.UnicastRemoteObject;

import intf.*;

import impl.*;

public class Server {

public static void main(String[] args) {

if (System.getSecurityManager() == null) {

System.setSecurityManager(new SecurityManager());

}

try {

SchedulerImpl scheduler = new SchedulerImpl();

Scheduler stub = (Scheduler)

UnicastRemoteObject.exportObject(scheduler, 0);

Registry registry = LocateRegistry.getRegistry();

String name = “Scheduler”;

registry.rebind(name, stub);

System.out.println(”SchedulerImpl bound”);

} catch (Exception e) {

System.err.println(”SchedulerImpl exception:”);

e.printStackTrace();

}

}

2. Writing a client

A client gets a reference to the scheduler and submits jobs. However, the client must define the job to be executed by the scheduler. Here we shall define a simple job that calculates the factorial of a number. In practice, clients submit computationally intensive jobs to the server for execution. The class definition of Factorial is shown as follows:

package impl;

import intf.*;

import java.io.Serializable;

public class Factorial implements Job, Serializable {

int n;

public Factorial(int v) { n = v;

}

public String run() { int result = 1; for(int i = 2; i <= n; i++) result *= i;

return String.valueOf(result);

}

}

The Factorial class i^nple^nents the Job interface. Its run() ^method calculates and returns the factorial of a specified integer. When a client submits such a Factorial job object to the remote scheduler, the object is transferred to the scheduler using Java’s serialization procedure. So, Factorial job object must be serializable. Since the Factorial class implements the Serializable interface, objects of the Factorial class are indeed serializable.

To reconstruct the Factorial job object in the Scheduler object’s JVM, job object’s class definition is needed. The RMI system downloads the class definition of Factorial on behalf of the Scheduler object. The client must specify the location where the RMI system can find the object’s class definition. Now, Scheduler object’s run() method is invoked which, in turn, invokes Factorial object’s run() method where the job is executed.

Clients may also submit other jobs to the remote Scheduler object. The Scheduler executes these jobs using the same procedure described. It need not know each job’s implementation procedure. It only knows that each job it receives implements the Job interface and has the method run().

Let us now write the code for the client. The client will first get a reference to the remote registry using the LocateRegistry.getRegistry() method as follows:

Registry registry = LocateRegistry.getRegistry(args[0]);

The argument to the getRegistry() method is the first command line argument args[0] which is the name or IP address of the computer where the registry runs on the default port (1099). If the registry runs on a port other than 1099, you specify the port as the second argument to the

getRegistry() method.

The client then uses the lookup() method on this registry to get a reference to the remote Scheduler object as follows:

String name = “Scheduler”;

Scheduler scheduler = (Scheduler) registry.lookup(name);

To submit a task, the client has to create an instance of the Factorial object.

Factorial aJob = new Factorial(Integer.parseInt(args[1]));

The second command line argument, after converting it to an integer, is passed to the Factorial constructor. This argument indicates the integer whose factorial has to be calculated. The client can now submit the job to the remote object for execution.

Result r = scheduler.run(aJob);

The result of job execution is stored in the Result object. Remember that the Result interface was implemented by the server. So, the client does not have the definition of Result object’s class. The RMI system will download the necessary class definition from the location as specified by the server. So, the client can invoke methods on this Result object to get the desired result and other information such as completion time.

System.out.println(”Execution time = ” + r.completionTime() + ” micro sec(s)”);

The entire piece of code is embedded in a try-catch block to handle the error that may occur at runtime. A security manager should also be installed to protect the system as Result object’s class definition is downloaded in the client’s JVM.

Here is the complete source code for the client which is stored in Client.java.

package impl;

import java.rmi.registry.LocateRegistry;

import java.rmi.registry.Registry; import intf.*;

import impl.*;

public class Client {

public static void main(String args[]) {

if (System.getSecurityManager() == null) {

System.setSecurityManager(new SecurityManager());

}

try {

Registry registry = LocateRegistry.getRegistry(args[0]);

String name = “Scheduler”;

Scheduler scheduler = (Scheduler) registry.lookup(name);

Factorial aJob = new Factorial(Integer.parseInt(args[1]));

Result r = scheduler.run(aJob);

System.out.println(args[1] + ”! = ” + r.output());

System.out.println(”Execution time = ” + r.completionTime() + ” micro

sec(s)”);

} catch (Exception e) {

e.printStackTrace();

}

}

}

3. Compiling the program

In practice, the server should deploy the necessary files for a client program to use. In our case the client must have a Scheduler, Job, and Result interface. The client also needs the Result object’s class (Resultimpl in this case) definition which will be downloaded by the RMI system from the location specified by the server at runtime. So, server must deploy these four classes.

The server will then write the implementation of Scheduler and Result interface and deploy an object in a computer accessible by the clients. The client programmers can then download the necessary class files and develop their applications which will use the Scheduler object.

All the files are packaged as follows:

Server machine

intf: Scheduler, Job, Result

impl: SchedulerImpl, ResultImpl, Server

Client machine

impl: Factorial, Client

Suppose the developer of the server is developing an application in the directory “E:\Net\rmi\ scheduler\server” of a computer having IP address 172.16.5.81. Hereafter, we shall refer to this directory as server_home. Create the directory structure and put server-side files as shown in Figure 14.19: (i).

3.1. Creating Interface classes

Go to the directory server_home. Give the following command to compile the interface source files and generate a jar file (SchedulerIntf.jar) containing the generated interface class files.

javac intf\*.java

jar cvf SchedulerIntf.jar intf\*.class

Make sure that the directory containing the Java compiler is included in your PATH environment variable. The jar command will generate the following output:

added manifest

adding: intf/Job.class(in = 129) (out= 112)(deflated 13%)

adding: intf/Result.class(in = 167) (out= 143)(deflated 14%)

adding: intf/Scheduler.class(in = 222) (out= 169)(deflated 23%)

This SchedulerIntf.jar file is needed by the client to develop its own application. This file must be put in a network accessible place so that the client can access it. For example, it can be put on a web server accessible by the client.

3.2. Compiling Server

The impl package contains three source files: SchedulerImpl.java, ResultImpl.java, and Server.java. SchedulerImpl.java is the implementation of Scheduler interface, ResultImpl. java is the implementation of Result interface, and Server.java creates and exports the Scheduler object. Use the following command to compile these source files.

javac impl\*.java

The stub and skeleton for SchedulerImpl implements the Scheduler interface which refers to the Job and Result interfaces. So, Java’s object registry needs the definition for these interfaces. Remember, the class definition of Result interface is needed by the client. So, create a jar file containing these four classes and put it in a network accessible place. Give the following command to create the jar file scheduler.jar:

jar cvf scheduler.jar intf\*.class impl\ResultImpl.class

This generates the following sample output:

added manifest

adding: intf/Job.class(in = 129) (out= 112)(deflated 13%)

adding: intf/Result.class(in = 167) (out= 143)(deflated 14%)

adding: intf/Scheduler.class(in = 222) (out= 169)(deflated 23%)

adding: impl/ResultImpl.class(in = 688) (out= 417)(deflated 39%)

To deploy this jar file, we shall use Apache’s tomcat web server running in the server computer (IP address is 172.16.5.81) on a port 80 8 0. Put this jar file in the web server’s document root. So, the URL of this jar file will be http://172.16.5.81:8080/scheduier.jar. We shall specify this URL while running the server application.

3.3. Compiling Client

Suppose client application is developed in another computer having IP address 172.16.4.248 in directory E:\Net\rmi\scheduler\client which we shall refer to as client_home. Create the directory structure and put client-side files as shown in Figure 14.19: (ii).

First download the SchedulerIntf.jar file from a location specified by the server and put it in the directory client_home. In the client computer, there are two files in the impl package Factorial. java and Client.java. Factorial.java is the implementation of the Job interface. The Client. java gets a reference of the Scheduler object and invokes the run() method using this reference. Give the following command to compile source files:

javac -cp schedulerIntf.jar;. impl/*.java

Note that the server needs the implementation of Job interface, i.e., Factorial class in our case which will be downloaded by the Java RMI system. However, the client must specify the location of this class file. In the client application, we shall again use Apache’s tomcat server to deploy the Factorial class. Configure the tomcat web server and put the Factorial.class in the impl subdirectory of document root so that the RMI system can download this file whenever necessary. The base URL of this class file is http://172.16.4.248:8080/. We shall specify this URL while running the client application.

4. Running the application

Remember, we had installed a security manager in the Server.java and Client.java applications. When we run these applications, we must specify a security policy for them. In this case we shall specify the policy as a file in the form of command line argument. Create a file server.policy in the server_home directory. The content of the file looks like this:

grant CodeBase “file:/E:/Net/rmi/scheduler/server”

{

permission java.security.AllPermission;

};

Similarly, create another file client.policy in the directory client_home with the following content:

grant CodeBase “file:/E:/Net/rmi/scheduler/client” {

permission java.security.AllPermission;

}

Before starting the server application, we need to start an object registry. In Java RMI, it is started using the rmiregistry command. So, open a terminal and type the following command:

rmiregistry

The command does not generate any output; it starts the object registry application on the default port 1099. Give the following command to start the server:

java -Djava.rmi.server.codebase=http://172.16.5.81:8080/scheduler.jar

-Djava.security.policy=server.policy

impl.Server

It produces a sample output as shown in Figure 14.20:

5. Start client

Give the following command to start the client:

java -cp ./schedulerIntf.jar;.

-Djava.rmi.server.codebase=http://172.16.4.248:8080/

-Djava.security.policy=client.policy

impl.Client 172.16.5.81 5

It produces a sample output as shown in Figure 14.21:

Source: Uttam Kumar Roy (2015), Advanced Java programming, Oxford University Press.

Leave a Reply

Your email address will not be published. Required fields are marked *