Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
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