Lab_Session_I_-_CORBA___RMI_

advertisement
Vakgroep Informatietechnologie
Design of Distributed Software 2008-2009
Lab Session I : CORBA & RMI - Guidelines
SEPTEMBER 30, 2008
1. Introduction
The purpose of this document is to make you familiar with the technologies Java RMI (Remote Method
Invocation) and CORBA. This document contains tutorials, explaining how you can write both the client
and the server using these technologies based on exemplary programs.
2. BMI Calculator
The body mass index (BMI), or Quetelet index, is a statistical measurement which compares a person's
weight and height. Due to its ease of measurement and calculation, it is a useful tool to estimate a
healthy body weight based on how tall a person is. Body mass index is defined as the individual's body
weight divided by the square of their height (BMI = weight / length2).
The body mass index can be interpreted as follows: a BMI of 18.5 to 25 may indicate optimal weight; a
BMI lower than 18.5 suggests underweight while a number above 25 may indicate the person is
overweight; a BMI below 17.5 may indicate anorexia or a related disorder; a number above 30 suggests
the person is obese (over 40, morbidly obese).
In this tutorial we use the bmi calculator as an example of a client-server application as shown in figure
1.
Figure 1: BMI calculator used as client-server application.
3. CORBA
The CORBA client server paradigm can be implemented in two ways: with or without a Portable Object
Adapter (POA). The POA is the piece of CORBA that manages server-side resources for scalability. By
deactivating objects' servants when they have no work to do, and activating them again when they're
needed, we can stretch the same amount of hardware to service many more clients. Additionally, the
POA can make a CORBA object persistent over several server process lifetimes. In general, the POA is
an optimization technique, enabling a server to serve more clients by managing the lifecycle of the
server objects. For a simple application like the BMICalculator, using a POA maybe a bit overkill, but in
more advanced applications, the use of a POA is certainly advantageous.
Design of Distributed Software (website: http://minerva.ugent.be, e-mail: ods@atlantis.ugent.be)
Vakgroep Informatietechnologie - Gaston Crommenlaan 8, bus 201, B - 9050 Gent - www.intec.UGent.be
In this section we will detail how you can implement the BMICalculator with and without a POA.
3.1 BMI Calculator without a POA
3.1.1 Server-side
The first step is to write the Interface Description Language (IDL), which is a specification language
used to describe a software component's interface. IDLs describe an interface in a language-neutral
way: they indicate the operations the remote objects support, but not how they are implemented. The
implementation of a CORBA object is provided in a standard programming language: in this tutorial we
will use Java but also other programming languages are possible (e.g. C++). In fact, a C++ based
CORBA client can communicate smoothly with a Java based CORBA server and vice versa.
The IDL file for our server is:
module ods{
module corba{
module bmicalculator{
interface BMICalculator{
double calculateBMI(in double length,
in double weight);
};
};
};
};
This IDL defines a remote interface called BMICalculator. The module keyword defines the logical
package this interface belongs to. This interface supports the invocation of one remote method called
calculateBMI which expects two doubles as input parameters (defined by the in keyword) and
returns
another
double.
More
complex
IDL
examples
can
be
found
at
http://java.sun.com/developer/onlineTraining/corba/corba.html#c2
Once we have the IDL file we can use the idlj program to compile the IDL file to Java classes. At the
server side we use the following command:
idlj –oldImplBase –fserver bmicalculator.idl
The oldImplBase directive indicates that we won’t be using a
POA and the fserver directive guarantees that we only
generate the necessary classes needed at the server side. The
idlj program generates several files that will handle the CORBA
communication for you. This leaves room to focus on the
business logic itself.
The next step is to specify the business logic of the server by extending the
_BMICalculatorImplBase class, one of the generated files. Here, you don’t need to worry about any
CORBA specific operations or methods: you can just implement the server as you would implement any
regular method. For our BMICalculator we create a class called BMICalculatorImpl with the
following implementation:
package ods.corba.bmicalculator;
public class BMICalculatorImpl extends _BMICalculatorImplBase {
public double calculateBMI(double length, double weight){
return weight / (length * length);
}
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
2/14
}
A final step in implementing the server is starting up the server. This involves the following steps:

Define a main method

Initialize the ORB, CORBA’s main object

Instantiate at least one server object, in our case the BMICalculator

Connect each object to the orb

Register the server to the NamingService

Wait for requests
While registering the server to the NamingService isn’t obligated, it is a good practice to do so. A
NamingService provides a DNS like service to CORBA. Remote server objects register themselves to
the NamingService by using a unique name. When a client wants to connect to a remote server object,
he first connects to the NamingService and asks a reference to this remote server object by providing
the unique identifier. Similar to DNS, this has several advantages: the remote server can move without
needing to inform the client, the client does need to know the IP address of the server but can use a
more easy to remember identifier, etc.
The main method for starting the server looks like this (StartBMICalculatorServer.java)
First, we initialize the ORB. We provide the IP address and port of the NamingService through
command line arguments.
java.util.Properties props = System.getProperties();
props.put("org.omg.CORBA.ORBInitialPort", args[1]);
props.put("org.omg.CORBA.ORBInitialHost", args[0]);
ORB orb = ORB.init(new String[0], props);
Next, we instantiate our remote server object and connect it to the ORB
BMICalculator bmi_calc = new BMICalculatorImpl();
orb.connect(bmi_calc);
Then, we request a reference to the NamingService
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
NamingContext ncRef = NamingContextHelper.narrow(objRef);
And we use this reference to bind our remote server object to the NamingService. In this case, the
name of our object will be BMICalculator.
NameComponent nc = new NameComponent("BMICalculator", "");
NameComponent path[] = { nc };
ncRef.rebind(path, bmi_calc);
Finally, we wait for new requests.
orb.run();
3.1.2 Client-side
First, we need to generate the necessary stubs to allow CORBA communication with our remote object.
Similar to the server side, this is done through the idlj program. However, now we use the fclient
directive.
idlj –oldImplBase –fclient bmicalculator.idl
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
3/14
Once these files are generated we need to obtain a reference to the remote object. This contains the
following steps:

Define a main method

Initialize the ORB, CORBA’s main object

Instantiate at least one server object, in our case the BMICalculator

Connect each object to the orb

Register the server to the NamingService

Wait for requests
Similar to the server side, we initialize the ORB and obtain a reference to the Naming Service.
java.util.Properties props = System.getProperties();
props.put("org.omg.CORBA.ORBInitialPort", args[1]);
props.put("org.omg.CORBA.ORBInitialHost", args[0]);
ORB orb = ORB.init(new String[0], props);
org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
NamingContext ncRef = NamingContextHelper.narrow(objRef);
Next, we query the NamingService by asking a reference for the remote object that is binded under the
name “BMICalculator”. The BMICalculatorHelper.narrow method is a helper method that aids in
casting an object of the Object class into an object of the BMICalculator class. This
BMICalculatorHelper class is also generated from the idlj program.
NameComponent nc = new NameComponent("BMICalculator", "");
NameComponent path[] = { nc };
BMICalculator server = BMICalculatorHelper.narrow(ncRef.resolve(path));
Once we have a reference to this remote object, we can use it as any regular local object and perform
method invocations on it. We don’t have to worry about the remote nature of this object. CORBA hides
the communication details through the stubs.
System.out.println(server.calculateBMI(1.87, 75));
3.1.3 Starting the complete application
To start the application, we first need to start the Naming Service. In a command line, type
start orbd –ORBInitialPort 1049
This will start the Naming Service on port 1049. Next, the server can be started.
start java ods.corba.bmicalculator.StartBMICalculatorServer 157.193.214.253
1049
This will start the server as available in the accompanied code. This code expects the IP address and
port number of the Naming Service as command line argument together with the name to bind the
BMICalculator to.
Finally, the client can be started through the following command:
java ods.corba.bmicalculator.client.BMICalculatorClient 157.193.214.253 1049
3.2 BMI Calculator with a POA
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
4/14
The application can also be implemented using a POA. This method is very similar to the example
without POA. Here, we focus on the changes needed to the application in section 3.2.
3.2.1 Server-side
We don’t need to modify the IDL file. However, the generation of CORBA communication classes needs
to be invoked otherwise: we can omit the oldImplBase directive
idlj –fserver bmicalculator.idl
To implement the business logic of the server, we don’t need to extend the _BMICalculatorImplBase
class, but the BMICalculatorPOA class. Therefore, the new code is:
package ods.corba.bmicalculator;
public class BMICalculatorImpl extends BMICalculatorPOA {
public double calculateBMI(double length, double weight) {
return weight / (length * length);
}
}
Starting the server can be done through the following steps:
First, we initialize the ORB. Just like in the example without a POA we provide the IP address and port
of the NamingService.
java.util.Properties props = System.getProperties();
props.put("org.omg.CORBA.ORBInitialPort", 157.193.214.253);
props.put("org.omg.CORBA.ORBInitialHost", 1049);
ORB orb = ORB.init(new String[0], props);
Next, we instantiate our remote server object. Note that in this case, an object of
BMICalculatorImpl does not have BMICalculator as parent class. We will need to get a
reference from the POA (see further).
BMICalculatorImpl bmi_calc = new BMICalculatorImpl();
Then, we request a reference to the Naming Service and POA.
POA rootPOA = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
NamingContext namingContext =
NamingContextHelper.narrow(orb.resolve_initial_references("NameService"));
As the POA is responsible for activating and deactivating the server objects, we need to activate our
server and obtain its reference.
rootPOA.activate_object(bmi_calc);
BMICalculator bmicalcRef =
BMICalculatorHelper.narrow(rootPOA.servant_to_reference(bmi_calc));
The rest is the same as in the case without a POA. We use this reference to bind our remote server
object to the NamingService. In this case, the name of our object will be BMICalculator.
NameComponent nc = new NameComponent("BMICalculator", "");
NameComponent path[] = { nc };
namingContext.rebind(path, bmicalcRef);
Finally, we activate the POA and wait for new requests.
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
5/14
rootPOA.the_POAManager().activate();
orb.run();
3.2.2 Client-side
Similar to the server side case, we need to compile the IDL file by executing. Obviously, no changes are
needed here besides the execution of the idlj command:
idlj –fclient bmicalculator.idl
As the POA is an object that only manages resources at the server side, no modifications are needed to
the starting the client.
3.2.3 Starting the complete application
Starting the application is exactly the same as the example without the POA.
To start the Naming Service
start orbd –ORBInitialPort 1049
To start the server
java ods.corba.bmicalculator.StartBMICalculatorServer 157.193.214.253 1049
To start the client
java ods.corba.bmicalculator.client.BMICalculatorClient 157.193.214.253 1049
4. RMI
RMI provides a mechanism by which the server and the client communicate and pass information back
and forth in a distributed object application. The server calls the registry to associate (or bind) a name
with a remote object. The client looks up the remote object by its name in the server's registry and then
invokes a method on it, like it’s locally available.
RMI gives the ability to download the definition of an object's class if the class is not defined in the
receiver's Java virtual machine. This capability enables new types and behaviors to be introduced into a
remote Java virtual machine, thus dynamically extending the behavior of an application. RMI uses the
Java object serialization mechanism to transport objects by value between Java virtual machines.
4.1 Server-side
The server code consists of an interface and a class. The interface defines the methods that can be
invoked from the client. The class provides the implementation.
The first step is to write the Interface which defines the remotely accessible part. The source code for
the BMICalculatorServerInterface interface:
package ods.rmi.bmicalculator.server;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface BMICalculatorServerInterface extends Remote {
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
6/14
public double calculateBMI(double length, double weight) throws
RemoteException;
}
By extending the interface java.rmi.Remote, the BMICalculatorServerInterface interface
identifies itself as an interface whose methods can be invoked from another Java virtual machine.
Because calculateBMI is a remote method it must throw a java.rmi.RemoteException. This
exception is thrown by the RMI system from a remote method invocation to indicate that either a
communication failure or a protocol error has occurred.
The next step is to write the business logic by implementing the remote interface. In general, a class
that implements a remote interface should at least do the following: declare the remote interface(s)
being implemented define the constructor for each remote object and provide an implementation for
each remote method in the remote interface(s).
The class implementing the already specified remote interface is declared as:
Public class BMICalculatorServer implements
ods.rmi.bmicalculator.server.BMICalculatorServerInterface
This declaration states that the class implements the BMICalculatorServerInterface remote
interface and therefore can be used for a remote object.
Next, the implementing class has to have a single constructor that takes no arguments. The code for
the constructor is as follows:
public BMICalculatorServer() {
super();
}
Although the superclass constructor gets invoked even if omitted from the BMICalculatorServer
constructor, it is included for clarity.
The third and last step in implementing the remote interface is to provide implementations for each
remote method. The remote interface used contains a single remote method, calculateBMI, which is
implemented using the following code:
public double calculateBMI(double length, double weight)
throws RemoteException {
return weight / (length * length);
}
In the parameters and return values of remote method invocations, objects that are not remote objects
(classes not implementing the java.rmi.Remote interface) are passed by value. Thus, a copy of the
object is created in the receiving Java virtual machine.
When the implementation is finished, we have to enable the server to accept calls from clients.
Therefore, our RMI server program needs to create the initial remote BMICalculatorServer object
and export it to the RMI runtime, which makes it available to receive incoming remote invocations.
The main method's first task is to create and install a security manager, which protects access to
system resources from untrusted downloaded code running within the Java virtual machine. A security
manager determines whether downloaded code has access to the local file system or can perform any
other privileged operations. If an RMI program does not install a security manager, RMI will not
download classes (other than from the local class path) for objects received as arguments or return
values of remote method invocations.
The code that creates and installs a security manager is:
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
7/14
}
Next, the main method creates an instance of BMICalculatorServer and exports it to the RMI
runtime with the following statements:
BMICalculatorServerInterface bmiCalc = new BMICalculatorServer();
BMICalculatorServerInterface stub = (BMICalculatorServerInterface)
UnicastRemoteObject.exportObject(bmiCalc, 0);
The static UnicastRemoteObject.exportObject method exports the supplied remote object so
that it can receive invocations of its remote methods from remote clients. The second argument, 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. The exportObject method
returns a stub for the exported remote object and it can throw a RemoteException.
Before a client can invoke a method on a remote object, it must first obtain a reference to the remote
object. The RMI registry is a simple remote object naming service that enables clients to obtain a
reference to a remote object by name. Registration with the local RMI registry goes as follows:
Registry registry = LocateRegistry.getRegistry();
String name = (args[0]==null)?args[0]:"BMICalculator";
registry.rebind(name, stub);
The LocateRegistry.getRegistry() gives a reference to the RMI registry on the local host and
on the default registry port, 1099. Next, the stub is bound in the registry with the specified name. When
a client does a lookup in the registry the remote object will be effectively passed by (remote) reference
rather than by value, because remote implementation objects, such as instances of
BMICalculatorServer, never leave the Java virtual machine in which they were created.
Once the server has registered with the local RMI registry, it is not necessary to have a thread wait to
keep the server alive. As long as there is a reference to the BMICalculatorServer object in another
Java virtual machine, local or remote, the BMICalculatorServer object will not be shut down or
garbage collected.
4.2 Client-side
The client code (BMIClientProgram.java) does basically the following: look up the remote object in
the RMI registry of the server and perform an invocation on the remote object.
The first thing that has to be done is installing a security manager. This step is necessary because the
process of receiving the server remote object's stub could require downloading class definitions from
the server. For RMI to download classes, a security manager must be in force.
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
After installing a security manager, the client locates the servers RMI registry on default port 1099 by
using the method LocateRegistry.getRegistry and specifying the IP address of the host running
the registry. Next, the client performs a registry lookup using the name under which the server has
bound the remote object. This lookup method results in a remote object reference which has to be
casted to the appropriate interface. These steps are summarized in the following code fragment:
Registry registry = LocateRegistry.getRegistry(args[0]);
BMICalculatorServerInterface calculator = (BMICalculatorServerInterface)
registry.lookup(args[1]);
The final step is the actual remote method invocation:
System.out.println("Your
Body
mass
is
"+
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
calculator.calculateBMI(
8/14
Double.parseDouble(args[2]), Double.parseDouble(args[3])));
4.3 Compilation
The first step in the compilation step is the compiling the server files. This done by using the javac
command in the server folder containing the following files: BMICalculatorServer.java and
BMICalculatorServerInterface.java. After the compilation process you get two class files.
Because the client uses the remote interface of the server, the
compiler
should
be
able
to
find
the
compiled
BMICalculatorServerInterface class. When all client and
server source files are located in one directory (no packages are used)
there shouldn’t be a problem. When packages are used like in this
example one has to copy the compiled interface class into the clients
source directory.
Compilation is done again by using the javac command on the client folder containing the following files:
BMIClientProgram.java and BMICalculatorServerInterface.class.
In an ideal situation a developer would likely create a Java Archive (JAR) file containing all the remote
server interfaces and distribute it to the client side developer, so both server and client can be
independently developed.
4.4 Starting the complete application
The server and client programs both run with a security manager installed. When you run either
program, you need to specify a security policy file so that the code is granted the security permissions it
needs to run. The security policy file java.policy contains the following:
grant{
permission java.net.SocketPermission "*:1024-65535", "connect,accept";
};
In this example we reuse the policy file for both the client end server, the policy files enables the
creating and accepting connection in the port range 1024-65535.
Before starting the BMICalculatorServer, you need to start the RMI registry. The RMI registry is a
simple server-side naming facility that enables remote clients to obtain a reference to an initial remote
object. It can be started with the rmiregistry command. By default, the registry runs on port 1099.
To start the registry on a different port, specify the port number on the command line.
Creating two batch files startBMIClientProgram.bat and startBMICalculatorServer.bat
simplifies the startup process.
The code of the file startBMICalculatorServer.bat is given below:
Start java -Djava.security.policy=java.policy ods.rmi.bmicalculator.server.BMICalculatorServer
BMICalculator
The java.security.policy property, is used to specify the security policy file that contains the
permissions you intend to grant to various pieces of code. This property is followed by the package and
name of the server main program. The BMICalculatorServer has one argument, the name of the
rmiregistry bound remote object.
The code of the file startBMIClientProgram.bat contains the following:
java -Djava.security.policy=java.policy ods.rmi.bmicalculator.client.BMIClientProgram
157.193.214.124 BMICalculator %1 %2
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
9/14
Like in the case of the server program you also have specify the policy file followed by the package and
name of the client main program. The BMIClientProgram has 4 arguments:
 Host name of IP address where the rmiregistry and server is running.
 The name of the rmiregistry bound remote object.
 Length and weight.
By using the client batch startup file, the client can be started as follows:
startBMIClientProgram length weight
4.5 Time Service using RMI Callbacks
Callbacks are useful when one or multiple clients must be kept up to date about changes on the server.
RMI callback enables the server to call remote methods on the client side using the RMI technology. In
essence, callback is just an RMI invocation in the other direction. The process of creating callbacks is
given figure 2.
(1)
(2)
(3)
(1)
Client registers a notification
method with the server
(2)
Client triggers an event
(3)
The server calls back to the
registered clients
Client
(3)
Server
(2)
(1)
Client
Figure 2: Necessary steps for achieving RMI Callback.
For demonstrating the RMI callback mechanism we create a Time Service application using the PUSHbased model whereby a client can subscribe at the server to receive periodic time updates. To stop
receiving the updates, the client must explicitly unsubscribe.
The first thing we have to do is specifying the interfaces, unlike the BMICalculator application there is a
need for an additional client-side interface which the server for remote callback. The different interfaces
are given below.
Server-side interface: TimeServerInterface.java
package ods.rmi.callback.timeservice.server;
import java.rmi.RemoteException;
import ods.rmi.callback.timeservice.client.TimeUpdate;
public interface TimeServerInterface extends java.rmi.Remote {
public void register(TimeUpdate timeUpdater) throws RemoteException;
public void getTimeUpdates(TimeUpdate timeUpdater) throws
RemoteException;
public void unregister(TimeUpdate timeUpdater) throws RemoteException;
}
The server has three remote methods: the register method allows a client to subscribe itself for
receiving notifications. After registration the client can call the method getTimeUpdates and receiving
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
10/14
time updates. By invoking the remote method unregister one can unsubscribe itself and not time
updates are received anymore. Each method has one argument, which is a remote reference to the
client-side callback object and allows the server to identify the client.
Client-side interface file TimeUpdate.java
package ods.rmi.callback.timeservice.client;
import java.rmi.RemoteException;
public interface TimeUpdate extends java.rmi.Remote {
public void updateTime(String currentTime) throws RemoteException;
}
The client-side remote interface consists of one method updateTime that the server can call to push
time updates.
Secondly, the server has to maintain a list containing all the subscribed clients. During the subscription
process, a thread named TimeUpdaterThread is associated with the client. Calling the
getTimeUpdates method starts the thread, resulting in a client-side method invocation every
second. These steps are summarized in the code below:
Code piece from TimeServer.java
private HashMap<TimeUpdate, TimeUpdaterThread> timeUpdaterMap = new
HashMap<TimeUpdate, TimeUpdaterThread>();
public synchronized void register(TimeUpdate timeUpdater) throws
RemoteException {
timeUpdaterMap.put(timeUpdater, new TimeUpdaterThread(timeUpdater));
}
public synchronized void getTimeUpdates(TimeUpdate timeUpdater) throws
RemoteException {
if (timeUpdaterMap.containsKey(timeUpdater)) {
timeUpdaterMap.get(timeUpdater).setActive(true);
timeUpdaterMap.get(timeUpdater).start();
}
}
public synchronized void unregister(TimeUpdate timeUpdater) throws
RemoteException {
if (timeUpdaterMap.containsKey(timeUpdater)) {
timeUpdaterMap.get(timeUpdater).setActive(false);
timeUpdaterMap.remove(timeUpdater);
}
}
TimeUpdaterThread.java code
package ods.rmi.callback.timeservice.server;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import ods.rmi.callback.timeservice.client.TimeUpdate;
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
11/14
public class TimeUpdaterThread extends Thread {
private boolean active = false;
private TimeUpdate timeUpdater = null;
public TimeUpdaterThread(TimeUpdate timeUpdater) {
this.timeUpdater = timeUpdater;
}
public void run() {
while(active) {
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd
HH:mm:ss");
Date date = new Date();
try {
timeUpdater.updateTime(dateFormat.format(date).toString());
} catch (Exception e) {
System.err.println("TimeUpdaterThread: Remote exception, " +
e.getMessage());
}
try {
Thread.sleep(1000);
} catch (Exception e) {
System.err.println("TimeUpdaterThread: Exception, " +
e.getMessage());
}
}
}
public void setActive(boolean active) {
this.active = active;
}
}
One the client side we also have to implement the remote interface TimeUpdate by printing out the
provided timestamp on the screen. To demonstrate the unsubscription process the client unsubscribes
itself after 30 seconds. This goes as follows (code from TimeUpdateClient.java):
private TimeServerInterface timeserver = null;
private long startTime = 0;
public TimeUpdateClient(TimeServerInterface timeserver) {
this.timeserver = timeserver;
startTime = System.currentTimeMillis();
}
public void updateTime(String currentTime) throws RemoteException {
System.out.println("The current time: " + currentTime);
// Unsubscription after 30 sec
if (System.currentTimeMillis() - startTime > 30000) {
timeserver.unregister(this);
}
}
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
12/14
The final steps in implementing the server and client is creating a remote object, binding it in the RMI
registry, when this is done the client can obtain a remote reference by performing a lookup and starting
the remote method invocation.
The steps needed on the server for RMI registry binding are identical to the ones discussed in the
BMICalculator example. The additional code for the file TimeServer.java is given below:
public static void main(String[] args) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
if (args.length != 1) {
System.out.println("Usage:
ods.rmi.callback.timeservice.server.TimeServer serverName");
}
try {
String name = (args[0]==null)?args[0]:"TimeServer";
TimeServerInterface timeServer = new TimeServer();
TimeServerInterface stub = (TimeServerInterface)
UnicastRemoteObject.exportObject(timeServer, 0);
Registry registry = LocateRegistry.getRegistry();
registry.rebind(name, stub);
System.out.println("TimeServer bound in registry with name:
"+name);
} catch (Exception e) {
System.err.println("TimeServer exception:");
e.printStackTrace();
}
}
On the client-side there are some extra steps needed to make an object remotely available for callback.
The additional code for TimeUpdateClient.java goes as follows:
public static void main(String[] args) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
if (args.length != 2) {
System.out.println("Usage: ods.rmi.callback.timeservice.client
serverIP serverName");
} else {
try {
// Registry lookup
Registry registry = LocateRegistry.getRegistry(args[0]);
TimeServerInterface timeserver = (TimeServerInterface) registry
.lookup(args[1]);
// Export callback object
TimeUpdate timeUpdater = new TimeUpdateClient(timeserver);
try {
UnicastRemoteObject.exportObject(timeUpdater);
} catch (RemoteException re) {
re.printStackTrace();
}
// Register for callback
timeserver.register(timeUpdater);
// Start getting time updates
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
13/14
timeserver.getTimeUpdates(timeUpdater);
} catch (NotBoundException ex) {
Logger.getLogger(TimeUpdateClient.class.getName()).log(
Level.SEVERE, null, ex);
} catch (RemoteException ex) {
Logger.getLogger(TimeUpdateClient.class.getName()).log(
Level.SEVERE, null, ex);
}
}
}
The underlying principle of RMI callback is the following: the server must register itself with the
rmiregisty and tell the registry which port it is listening on. When a client wants to use a service it
doesn't initially know where that service is available (the servers listening port). So it asks the registry
for a particular service, and the registry will tell it what port that service is available on. The client then
contacts the service directly on its listening port. The client on the other hand does not have to run a
rmiregistry for callback. But, the client does have to call a special method named,
UnicastRemoteObject.exportObject(timeUpdater) where timeUpdater refers to the object
being exported. By using this command the remote object timeUpdater is made available to receive
incoming calls using an anonymous port. Next, by providing the server with a stub from the remote
object that has to be used for callback, the server can determine the port to which this object is bound
to and perform the callback.
The compiling and running procedure of the time service application is almost identical to the ones
performed in the BMICalculator example. To only difference is that you have to create stubs and
skeletons on the client side using the rmic compiler on the TimeUpdateClient class file (thus, after
he javac command on the client source code). This is done using the command:
rmic ods.rmi.callback.timeservice.client.TimeUpdateClient
Design of Distributed Software 2008-2009 – Lab Session I : CORBA & RMI
14/14
Download