Download Figure 1: RMI Architecture

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
Chapter 11
JavaRMI and MPI: Distributed Processing Packages
Table of Contents
11.1 Introduction
11.2 Java RMI
11.2.1 Background
11.2.2 Fundamentals of JavaRMI
11.2.3 Interfaces
11.2.4 Examples
11.2.5 Security Manager
11.3 MPI
11.3.1 Background
11.3.2 Functions
11.3.3 Example
11.4 Summary
11.5 Future Readings
11.6 Exercises
11.1 Introduction
In this chapter, we will be studying the implementation of the concepts of Chapter
10. We will also be looking at two specific packages that can be used to write distributed
programs. First, we will cover Java's Remote Method Invocation package and how it can
be utilized to create fluid programs. We will look at the fundamental devices used by
JavaRMI to create a parallel environment. After JavaRMI, we will investigate the MPI
libraries that can be used by C, C++, and FORTRAN programs to create distributed
programs.
11.2 JavaRMI
11.2.1 Background
Remote Method Invocation (RMI) provides a framework for developing
distributed object systems using Java. By implementing RMI, programmers create Java
objects whose methods can be invoked by objects residing on different hosts. Object
serialization, introduced in the 1.1 release of the Java Develop Kit, provides a means for
converting objects to and from a stream of bytes. Serialization allows applications to send
full objects across a network. This feature is utilized by RMI to provide support for
passing full objects as parameters or return values.
At the most basic level, RMI can be compared to remote procedure calls (RPCs)
which were introduced by Birrell and Nelson in 1984 [1]. The idea behind a RPC is to
make it look identical to a local call. RPCs use client and server stubs to take the place of
the remote server and client. A client stub imitates the calling sequence of the
1
corresponding remote procedure. Unlike the actual procedure, the client stub packs the
parameters into a message, which is sent to the server stub. The server stub removes the
parameters from the message and makes the actual call on the remote machine. When the
call returns, the server stub packs the results into a message and sends it back to the client
stub. The client stub unpacks the message and returns the results to the caller.
The architecture of RMI is similar to that of RPC. The RMI system consists of
three layers: stubs/skeletons, the remote reference layer, and the transport layer (Figure
1). A stub is a client-side proxy for a remote object that implements all of the interfaces
supported by the remote object. A client's reference to a remote object is actually a
reference to a local stub. The stub forwards requests to the remote object through the
server-side skeleton. Upon receiving the request, the skeleton makes the actual call to the
method. The return value is sent back through the skeleton to the stub on the client side.
Figure 1: RMI Architecture
2
One of the primary roles of the stub and skeleton is to prepare parameters and
return values for transport. A marshal stream is an abstraction that enables RMI to
transmit objects between hosts. Marshal streams use object serialization to convert an
object into a byte stream that can be transmitted across a network. A stub will pass
parameters that refer to local objects by copy, whereas parameters that refer to remote
objects are passed by reference. The skeleton handles return values in a similar fashion.
The stubs and skeletons are generated automatically by the rmic compiler, which is
executed after the programs have been previously compiled with Java's compiler javac.
The remote reference layer consists of both client-side and server-side
components. When a remote method is invoked, these components implement the remote
reference semantics. For example, when working with replicated objects, the client-side
component multicasts the method invocation to each of the replicas. As replies are
received, the server-side component processes the replies and sends the appropriate result
to the client-side. If the client is looking for a single response, the server-side component
might forward the first reply it receives and discard any others. Multicast delivery is not
provided by release 1.1 of RMI.
The transport layer is responsible for connection management. The remote
reference layer transmits data to the transport layer using a stream-oriented connection.
The transport layer takes care of the implementation details of connections. Although
connections present a streams-based interface, other transport mechanisms could be
implemented.
//Note remove citing [1]
3
11.2.2 Fundamentals of JavaRMI
To demonstrate how to construct a JavaRMI program, we will work through a
simile example called a time server. The time client/server, which returns the current
time, is a demonstration of how to coordinate running programs on multiple machines.
When one begins to write a program in JavaRMI, the first thing that must be done
is to include the proper additional packages. The following line must be included with
the import statements.
import java.rmi.*;
This line imports the necessary information so that JavaRMI devices can be used in the
program. The JavaRMI packages that are included with this statement are java.rmi,
java.rmi.dgc, java.rmi.registry, java.rmi.server, java.rmi.Remote,
and
java.rmi.RemoteException.
The first of these JavaRMI devices is the UnicastRemoteObject, which includes
the class java.rmi.server. The UnicastRemoteObject, a convenience class, supplies
the implementations for methods in java.lang.Object. If you choose to extend a class
other than the UnicastRemoteObject, then you must supply the appropriate
implementations of methods from java.lang.Object. While the process is alive, the
UnicastRemoteObject
defines a non-replicated remote object. This object creates the
streams necessary for one class to call another class function. The
UnicastRemoteObject
is called as follows.
public class TimedImpl extends UnicastRemoteObject implements
Timed
{
Timed
is an interface, which will be discussed in section 11.2.3.
4
public TimedImpl() throws RemoteException
{
super();
}
public String getTime() throws RemoteException
{
return new Date().toString();
}
public static void main(String args[])
{
The next important device is the RMISecurityManager(), which is part of the class
java.rmi.
The RMISecurityManager() defines the security policy for the RMI
application. The first thing that the main() of a program must do is create and start a
security manager to protect against unauthorized downloads. The security manager
determines what each process can access. The RMISecurityManager() is the standard
security manager, however, you can design your own. The following line should be the
first in the main() of the program. See 11.2.5 for more on security managers.
System.setSecurityManager(new RMISecurityManager());
Next, the Naming class, which is also included in the package java.rmi, is used for
finding other remote objects. The rebind() function in Naming links a string name to a
new remote object and replaces any previous bindings. The string name is a combination
of a programmer designated string, in this case ObjectName, and the name of machine
the process is running on. The machine name is usually entered as a command line
parameter. This naming process makes it possible for another remote object to call this
object. An object can find another remote object by using the Naming.lookup()
function (see in section 11.2.3)
try {
TimedImpl obj = new TimedImpl();
Naming.rebind("TimeServer", obj);
}
catch (Exception e){
5
System.out.println("TimedImpl err: " +
e.getMessage());
System.exit(1);
}
}
}
The code below implements the time client, TimedClient, and is a regular Java
application. Since Timed and TimedClient are not running on the same machine,
TimedClient
must do a name lookup to find the time server running on the host named
taff. The address of the time server, TimedImpl, is URL-based.
//TimedClient.java
import java.rmi.*;
public class TimedClient
{
public static void main(String argv[])
{
int curTime = 0;
int tim = 0;
try {
Using the lookup function of the naming class, the client can find the server and then
make calls to the public methods listed in the interface. The name of the remote object,
the server, is given like a URL, Universal Remote Locator.
Timed obj =(Timed)Naming.lookup("//taff/TimeServer");
curTime = obj.getTime();
}//end try
catch (Exception e) {
System.out.println(e.getMessage());
System.exit(1);
}//end catch
System.out.println(curTime);
}//end main
}//end TimedClient
To run a JavaRMI program in a terminal window, first compile all the
components of the program, classes and interfaces, using the Javac compiler.
javac Timed.java
//the interface
6
javac TimedImpl.java
javac TimedClient.java
//the server which implements the interface
Next, use the rmic compiler to create the stub and skeleton of the MyWorkerImpl.
rmic TimedImpl
Then, start the rmiregistry, which returns an id number.
rmiregistry &
The rmiregistry is the naming service, which is used on a host to bind remote objects
to names. The Naming class operates on the rmiregistry and uses the information there
to locate remote objects. Now, finally, run the programs on the appropriate machines.
java TimedImpl
java TimedClient
After the programs have run, close the rmiregistry using the id number that was returned
when the rmiregistry was started.
kill –9 id number
11.2.3 Interfaces
JavaRMI uses interfaces as a means to link objects. They are not streams for
communication, but rather they contain a standardized list of the functions that can be
called by a remote object. The code below implements the time server, Timed, which is a
RMI object. Note that all RMI objects must be a subclass of java.rmi.Remote. Timed
is the interface for TimedImpl (see section 11.2.2)
//Timed.java
public interface Timed extends java.rmi.Remote
{
int getTime() throws java.rmi.RemoteException;
}
The class that this interface describes begins with the statement:
public class TimedImpl extends UnicastRemoteObject implements Timed
7
This line of code links the interface to the class. The interface defines methods, which
are later implemented by the associated class. Every function that is called by a remote
object must be listed in the interface in order for it to be remotely accessible.
Request
TimedClient
TimedImpl
Time
Figure 11.# Client/Server Model
TimedImpl Implementation
TimedClient
Timed
Interface
Figure 11.# Java Implemenation
11.3.4 Examples
Here are two slightly more complex programs that utilize JavaRMI. The first is a
merge sort and the second is a logger.
8
A merge sort uses a supervisor with N workers to sort a list of integers. Lists of
integers are sent to all of the workers. Each of the workers returns its sorted numbers one
at a time to the supervisor, which stores the numbers in a buffer. Next, the supervisor
selects the next number in order from the buffer. Then to fill the empty location, the
supervisor requests a number from the corresponding worker. This program, for learning
purposes, requires the supervisor and the workers to be executed on different hosts.
Additionally, the supervisor and worker objects have to be implemented as RMI objects
since the application requires bi-directional communication, meaning that the supervisor
sends data to the workers and the workers send data to the supervisor.
To review, a merge sort is commonly used to sort large data sets. A merge sort
begins by dividing the data set in half and having two workers sort each half. Then the
two sorted lists are merged, interwoven, together in order.
Merges, the merge sort server
import
import
import
import
import
import
import
import
java.net.*;
java.util.*;
java.lang.*;
java.io.*;
java.awt.*;
java.applet.*;
java.rmi.*;
java.rmi.server.UnicastRemoteObject;
//Christyann Ferraro
//merge sort server in JavaRMI
public class
{
static
static
static
static
merges
int
int
int
int
temp[] = null;
list[] = null;
NW = 5;
counter = 0;
public static void main(String argv[])
{
NW = (new Integer(argv[0])).intValue();
int NN = (new Integer(argv[1])).intValue();
9
int numsperwork = NN/NW;
int leftovers = NN%NW;
int nums;
temp = new int[NN];
list = new int[NN];
int min,j;
int emptycell = 0;
counter = NN;
mergeWorker worker[] = new mergeWorker[NW];
for (int i=0; i<NW; i=i+1)
{
try {
worker[i] = (mergeWorker)
Naming.lookup("//"+argv[i+2]+
"/mergeWorker" + i);
}
catch (Exception e) {System.out.println(e);}
}
// sending workers arrays of numbers
for (int i=0; i<NW; i=i+1)
{
for (int k=0; k<numsperwork; k=k+1)
{
temp[k] = (int)(Math.random()*101 + 1);
}
nums = 0;
if (leftovers != 0)
{
temp[numsperwork] =(int)(Math.random()*101 +1);
leftovers = leftovers-1;
nums = nums+1;
}
try {
worker[i].setArr(temp, numsperwork+nums);
}
catch(Exception e){System.out.println(e);}
}
// receiving first numbers from workers
for (int k = 0; k<NW; k = k+1)
{
try {
list[k] = worker[k].getNum();
}
catch(Exception e){System.out.println(e);}
}
// while there are more numbers to print
while (counter != 0)
{
// finding the smallest number in the list and printing it
min = -1;
j = 0;
while (min == -1)
10
{
min = list[j];
emptycell = j;
j = j+1;
}
for (int k = 0; k<NW; k = k+1)
{
if (min>list[k])
{
if (list[k] != -1)
{
min = list[k];
emptycell = k;
}
}
}
System.out.print("Number ");
System.out.println(min);
// getting a number from a worker to "fill" the empty cell
try {
list[emptycell] = worker[emptycell].getNum();
}
catch(Exception e){System.out.println(e);}
counter = counter-1;
}//end while
}//end main
}//end class
The interface that allows the server, merges, to call certain functions in the
mergeWorkerImpl.
//Christyann Ferraro
//interface for merge sort in java rmi
public interface mergeWorker extends java.rmi.Remote
{
void setArr(int arr[], int NN) throws java.rmi.RemoteException;
int getNum() throws java.rmi.RemoteException;
void starting() throws java.rmi.RemoteException;
}
MergeWorkerImpl - the workers
import
import
import
import
import
import
import
import
java.net.*;
java.util.*;
java.lang.*;
java.io.*;
java.awt.*;
java.applet.*;
java.rmi.*;
java.rmi.server.UnicastRemoteObject;
//Christyann Ferraro
//merge sort worker in JavaRMI
11
public class mergeWorkerImpl extends UnicastRemoteObject implements
mergeWorker
{
static int intarr[] = null;
static int count = 0;
static int Num = 0;
static int curpos = 0;
static int arrflag,getNumflag;
//0:flag is lowered, 1:flag is raised
static int id = 0;
static String mach = "";
public mergeWorkerImpl() throws java.rmi.RemoteException
{
super();
arrflag=0;
getNumflag=0;
// no object raised flag yet
}
public synchronized void setArr(int arr[], int NN) throws
java.rmi.RemoteException
//listed in the interface
{
count = NN;
intarr = new int[NN];
for (int i=0; i<count; i=i+1)
{
intarr[i] = arr[i];
}
arrflag = 1;
// sender was here
notify();
// notify any thread using wait() that sender was here
}
public synchronized void starting() throws
java.rmi.RemoteException
//listed in the interface
{
int p,q,a,b,temp;
while (arrflag == 0)
{
try {wait();}
catch (Exception e) {System.out.println(e);}
}
// sorting the list
for (p = 0; p<count; p = p+1)
{
for (q = p; q<count; q = q+1)
{
a = intarr[p];
b = intarr[q];
if (b<a)
{
temp = a;
a = b;
b = temp;
intarr[p] = a;
12
intarr[q] = b;
}
}
}
getNumflag = 1;
notify();
// numbers are ready to be sent
} // end starting
public static void main(String argv[])
{
System.setSecurityManager(new RMISecurityManager());
try {
mergeWorkerImpl obj = new mergeWorkerImpl();
Naming.rebind("mergeWorker"+argv[0], obj);
obj.starting();
}
catch (Exception e) {
System.out.println("WorkImpl err: " +
e.getMessage());
System.exit(1);
}
} //end main
public synchronized int getNum()
//listed in the interface
{
while (getNumflag == 0)
{
try {wait();}
catch (Exception e) {System.out.println(e);}
}
if (curpos < count)
{
Num = intarr[curpos];
}
else { Num = -1; }
curpos++;
return Num;
} // end getNum
}//end class
LOGGER…
Message:
import java.util.*;
public class LogMessage implements java.io.Serializable {
private String msg;
private Date time;
public LogMessage(String what)
{
msg = new String(what);
time = new Date();
13
}
public void setMessage(String what)
{
msg = new String(what);
time = new Date();
}
public String toString()
{
StringBuffer output = new StringBuffer();
output.append(time).append(": ");
output.append(msg);
return new String(output);
}
}
Interface:
// A simple log server. Provides a single method that
// writes a LogMessage to the system log.
public interface Log extends java.rmi.Remote
{
void log(LogMessage msg) throws java.rmi.RemoteException;
}
Server:
// The actual system logger. Accepts LogMessages from any number
// of clients. The log is a plain ASCII file.
import
import
import
import
java.io.*;
java.util.*;
java.rmi.*;
java.rmi.server.UnicastRemoteObject;
public class LogImpl extends UnicastRemoteObject implements Log
{
public static final String defaultFilename = "syslog.log";
private static PrintWriter logFile;
public LogImpl() throws RemoteException
{
super();
try {
logFile = new PrintWriter(new FileOutputStream
(defaultFilename,true));
}
catch (Exception e) {
System.out.println(e);
System.exit(1);
}
logFile.print("Log Service started at ");
logFile.println(new Date());
logFile.flush();
}
public synchronized void log(LogMessage msg) throws
14
RemoteException
{
logFile.println(msg);
logFile.flush();
}
public static void main(String args[])
{
try {
LogImpl obj = new LogImpl();
Naming.rebind("LogService", obj);
}
catch (Exception e) {
System.out.println(e);
System.exit(1);
}
}
}
Client:
// A log service client
import java.rmi.*;
public class LogClient
{
public static void main(String argv[])
{
Log syslog = null;
StringBuffer message = new StringBuffer("log entry X");
try {
syslog = (Log)Naming.lookup("//triton/LogService");
}
catch (Exception e) {
System.out.println( "Error1 LogClient: " +
e.getMessage() );
System.exit(1);
}
for (char ch='0'; ch<='9'; ch++)
{
message.setCharAt(message.length()-1,ch);
try {
syslog.log(new LogMessage(new String(message)));
}
catch (Exception e) {
System.out.println("Error2 LogClient: " +
e.getMessage() );
System.exit(1);
}//end catch
}//end for
}//end main
}//end class
15
These two programs are good tools to familiarize new users with JavaRMI. This
experience is helpful when developing more complex applications, such as a home
heating simulation (see chapter 12).
11.2.5 Security Managers
There is a default security manager defined in java.rmi, but you can create your
own security manager.
11.3 MPI
11.3.1 Background
From November 1992 through April 1994, a group of researchers met at the
Message Passing Interface Forum and defined the library contained in MPI. This forum
included vendors like IBM and Intel, companies such as ARCO and Cray Res, labs
including NOAA and NSF, and universities like Yale, Cornell, Tennessee, Syracuse, Rice,
and Colorado.
MPI stands for Message Passing Interface. MPI is a library of functions that can be
used in C, C++, or FORTRAN programs to utilize the existence of multiple processors to
pass messages. This creation of MPI produced a portable standard for message passing in
parallel processing. MPI is a message-passing model but is not compiler specific. It was
designed for parallel computers, clusters, and heterogeneous networks. The full features of
MPI permit access to advanced parallel software libraries for users, library creators, and
software developers.
16
MPI provides may general features which add to the ability, security, and
friendliness of C, C++, or FORTRAN. MPI provides security for messages and threads.
Features are also added to the languages’ point-to-point communications including
additional modes, structured buffers and special datatypes. Groups can be defined directly
or using topology. MPI also assists in error control.
11.3.2 Functions
MPI programs can be a supervisor/worker style or nearest neighbor style. The
general layout for an MPI program is as follows:
Call to MPI_Init()
if supervisor
Supervisor code includes
Provide workers with any initialization information
else if worker
Worker code
Call to MPI_Finalize()
Now, we will discuss a few of the functions that are found in MPI. These are widely
used functions that will be used in the MPI example programs. There are many more than
are described here.
There are two functions that are used to mark the being and end of the use of MPI
functions. MPI_Init() must be called before any of the MPI functions can be used.
MPI_Init(&argc, &argv);
To end the use of MPI functions, a call is made to MPI_Finalize.
MPI_Finalize();
One important concept in MPI is the communicator, which is a group of processes
that can send messages to each other. In general, the MPI_COMM_WORLD is used. It is a
predefined communicator that consists of all processes that are running at the time. Every
17
process has a rank, which can be accessed by using the MPI_Comm_rank function. This
function takes as its first parameter the communicator and returns the value of the rank as
the second parameter. Rank is the processor id number.
int MPI_Comm_rank(MPI_Comm comm, int rank)
Every communicator has a size, which is useful when your program depends on the number
of processes.
The MPI function MPI_Comm_size takes as its first argument the
communicator and returns the size of that communicator as its second argument.
int MPI_Comm_size(MPI_Comm comm, int size)
MPI has its own datatypes, which correlate closely with those of C, C++ and
FORTRAN. One example of an MPI_Datatype is MPI_Int, which is equivalent to a signed
integer.
MPI_Int x;
A communicator was defined as a group of processes that can send messages to each
other. The means for sending and receiving messages in MPI are two MPI functions
appropriately named MPI_Send() and MPI_Receive().
int MPI_Send(void* message, int count, MPI_Datatype datatype, int
dest, int tag, MPI_Comm comm);
int MPI_Receive(void* message, int count, MPI_Datatype datatype,
int source, int tag, MPI_Comm comm, MPI_Status* status);
The parameters for these functions begin with a message, which is the data message that is
being sent or received. The count, the number of data elements, and the datatype help the
system to identify the end of the message. The dest is the rank of the destination process
and the source is the rank or id of the source process. The tag is an integer message
18
identifier for the message. The comm is the communicator. The status returns information
on the data that was actually received. It returns 2 fields of the sender and the actual tag of
message received.
There is a general class of collective communications that includes among others
MPI_Bcast(), MPI_Reduce(),
and MPI_Gather(). A process can send messages to all
other processes. This is called a broadcast. The MPI_Bcast() allows this collective
communication.
int MPI_Bcast(void* message, int count, MPI_Datatype datatype,
int root, MPI_Comm comm)
MPI_Reduce()
combines data from all processes in a certain communicator using a binary
operator like add, max, or min.
int MPI_Reduce (void* operand, void* result, int count,
MPI_Datatype datatype, MPI_Op op, int root MPI_Comm comm)
int MPI_Gather (void* send_buf, int send_count, MPI_Datatype
send_type, void* recv_buf, int recv_count, MPI_Datatype
recv_type, int root, MPI_Comm comm)
One less common, but useful, function that is used is MPI_Wtime(), which returns
the current system time as a double. This can be used to calculate execution times.
curTime = MPI_Wtime();
These functions are used in examples in section 11.3.3 and again in Chapter 12.
11.3.3 Example
intro by Doc
19
#include "mpi.h"
#include <stdio.h>
#include <math.h>
//Rod Tosten
double f(a)
double a;
{
return (4.0 / (1.0 + a*a));
}
void main(argc,argv)
int argc;
char *argv[];
{
int done = 0, n, myid, numprocs, i;
double PI25DT = 3.141592653589793238462643;
double mypi, pi, h, sum, x;
double startwtime, endwtime;
int namelen;
char processor_name[MPI_MAX_PROCESSOR_NAME];
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
MPI_Get_processor_name(processor_name,&namelen);
fprintf(stderr,"Process %d on %s\n", myid, processor_name);
n = 0;
while (!done)
{
if (myid == 0)
{
if (n==0) n=100; else n=0;
startwtime = MPI_Wtime();
}
MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
if (n == 0)
done = 1;
else
{
h
= 1.0 / (double) n;
sum = 0.0;
for (i = myid + 1; i <= n; i += numprocs)
{
x = h * ((double)i - 0.5);
sum += f(x);
}
mypi = h * sum;
MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0,
MPI_COMM_WORLD);
if (myid == 0)
{
printf("pi is approximately %.16f, Error is
20
%.16f\n", pi, fabs(pi - PI25DT));
endwtime = MPI_Wtime();
printf("wall clock time = %f\n",
endwtime-startwtime);
}
}//end else
}//end while
MPI_Finalize();
}//end main
11.4 Summary
11.5 Future Readings
Here are a few web pages where you can get information about JavaRMI:
http://java.sun.com/products/jdk/1.1/docs/index.html
http://java.sun.com/marketing/collateral/RMI.html
http://java.sun.com/products/jdk/1.1/docs/guide/rmi/getstart.html
And a list of links to pages about MPI is found at:
http://www-unix.mcs.anl.gov/mpi
11.6 Exercises
21