|
Distributed Java Programming with RMI and CORBA(1)
Distributed Java Programming with RMI and CORBAQusay H. Mahmoud January 2002 The Java Remote Method Invocation (RMI) mechanism and the Common Object Request Broker Architecture (CORBA) are the two most important and widely used distributed object systems. Each system has its own features and shortcomings. Both are being used in the industry for various applications ranging from e-commerce to health care. Selecting which of these two distribution mechanisms to use for a project is a tough task. This article presents an overview of RMI and CORBA, and more importantly it shows how to develop a useful application for downloading files from remote hosts. It then: - Presents a brief overview of distributed object systems
- Provides a brief overview of RMI and CORBA
- Gives you a flavor of the effort involved in developing applications in RMI and CORBA
- Shows how to transfer files from remote machines using RMI and CORBA
- Provides a brief comparison of RMI and CORBA
The Client/Server ModelThe client/server model is a form of distributed computing in which one program (the client) communicates with another program (the server) for the purpose of exchanging information. In this model, both the client and server usually speak the same language -- a protocol that both the client and server understand -- so they are able to communicate. While the client/server model can be implemented in various ways, it is typically done using low-level sockets. Using sockets to develop client/server systems means that we must design a protocol, which is a set of commands agreed upon by the client and server through which they will be able to communicate. As an example, consider the HTTP protocol that provides a method called GET, which must be implemented by all web servers and used by web clients (browsers) in order to retrieve documents. The Distributed Objects ModelA distributed object-based system is a collection of objects that isolates the requesters of services (clients) from the providers of services (servers) by a well-defined encapsulating interface. In other Words, clients are isolated from the implementation of services as data representations and executable code. This is one of the main differences that distinguishes the distributed object-based model from the pure client/server model. In the distributed object-based model, a client sends a message to an object, which in turns interprets the message to decide what service to perform. This service, or method, selection could be performed by either the object or a broker. The Java Remote Method Invocation (RMI) and the Common Object Request Broker Architecture (CORBA) are examples of this model. RMIRMI is a distributed object system that enables you to easily develop distributed Java applications. Developing distributed applications in RMI is simpler than developing with sockets since there is no need to design a protocol, which is an error-prone task. In RMI, the developer has the illusion of calling a local method from a local class file, when in fact the arguments are shipped to the remote target and interpreted, and the results are sent back to the callers. The Genesis of an RMI ApplicationDeveloping a distributed application using RMI involves the following steps: - Define a remote interface
- Implement the remote interface
- Develop the server
- Develop a client
- Generate Stubs and Skeletons, start the RMI registry, server, and client
We will now examine these steps through the development of a file transfer application. Example: File Transfer ApplicationThis application allows a client to transfer (or download) any type of file (plain text or binary) from a remote machine. The first step is to define a remote interface that specifies the signatures of the methods to be provided by the server and invoked by clients. Define a remote interface The remote interface for the file download application is shown in Code Sample 1. The interface FileInterface provides one method downloadFile that takes a String argument (the name of the file) and returns the data of the file as an array of bytes. Code Sample 1: FileInterface.Java import Java.rmi.Remote;import Java.rmi.RemoteException;public interface FileInterface extends Remote {public byte[] downloadFile(String fileName) throwsRemoteException;}Note the following characteristiCS about the FileInterface: - It must be declared
public, in order for clients to be able to load remote objects which implement the remote interface. - It must extend the
Remote interface, to fulfill the requirement for making the object a remote one. - Each method in the interface must throw a
Java.rmi.RemoteException.
Implement the remote interface The next step is to implement the interface FileInterface. A sample implementation is shown in Code Sample 2. Note that in addition to implementing the FileInterface, the FileImpl class is extending the UnicastRemoteObject. This indicates that the FileImpl class is used to create a single, non-replicated, remote object that uses RMI's default TCP-based transport for communication. Code Sample 2: FileImpl.Java import Java.io.*;import Java.rmi.*;import Java.rmi.server.UnicastRemoteObject;public class FileImpl extends UnicastRemoteObjectimplements FileInterface {private String name;public FileImpl(String s) throws RemoteException{super();name = s;}public byte[] downloadFile(String fileName){try {File file = new File(fileName);byte buffer[] = new byte[(int)file.length()];BufferedInputStream input = newBufferedInputStream(new FileInputStream(fileName));input.read(buffer,0,buffer.length);input.close();return(buffer);} catch(Exception e){System.out.println("FileImpl: "+e.getMessage());e.printStackTrace();return(null);}}}Develop the server The third step is to develop a server. There are three things that the server needs to do: - Create an instance of the
RMISecurityManager and install it - Create an instance of the remote object (
FileImpl in this case) - Register the object created with the RMI registry. A sample implementation is shown in Code Sample 3.
Code Sample 3: FileServer.Java import Java.io.*;import Java.rmi.*;public class FileServer {public static void main(String argv[]) {if(System.getSecurityManager() == null) {System.setSecurityManager(new RMISecurityManager());}try {FileInterface fi = new FileImpl("FileServer");Naming.rebind("//127.0.0.1/FileServer", fi);} catch(Exception e) {System.out.println("FileServer: "+e.getMessage());e.printStackTrace();}}}The statement Naming.rebind("//127.0.0.1/FileServer", fi) assumes that the RMI registry is running on the default port number, which is 1099. However, if you run the RMI registry on a different port number it must be specified in that statement. For example, if the RMI registry is running on port 4500, then the statement becomes: Naming.rebind("//127.0.0.1:4500/FileServer", fi)
Also, it is important to note here that we assume the rmi registry and the server will be running on the same machine. If they are not, then simply change the address in the rebind method. Develop a client The next step is to develop a client. The client remotely invokes any methods specified in the remote interface (FileInterface). To do so however, the client must first obtain a reference to the remote object from the RMI registry. Once a reference is obtained, the downloadFile method is invoked. A client implementation is shown in Code Sample 4. In this implementation, the client accepts two arguments at the command line: the first one is the name of the file to be downloaded and the second one is the address of the machine from which the file is to be downloaded, which is the machine that is running the file server. Code Sample 4: FileClient.Java import Java.io.*; import Java.rmi.*;public class FileClient{public static void main(String argv[]) {if(argv.length != 2) {System.out.println("Usage: Java FileClient fileName machineName");System.exit(0);}try {String name = "//" + argv[1] + "/FileServer";FileInterface fi = (FileInterface) Naming.lookup(name);byte[] filedata = fi.downloadFile(argv[0]);File file = new File(argv[0]);BufferedOutputStream output = newBufferedOutputStream(new FileOutputStream(file.getName()));output.write(filedata,0,filedata.length);output.flush();output.close();} catch(Exception e) {System.err.println("FileServer exception: "+ e.getMessage());e.printStackTrace();}}}Running the Application In order to run the application, we need to generate stubs and skeletons, compile the server and the client, start the RMI registry, and finally start the server and the client. To generate stubs and skeletons, use the rmic compiler: prompt> rmic FileImpl
This will generate two files: FileImpl_Stub.class and FileImpl_Skel.class. The stub is a client proxy and the skeleton is a server skeleton. The next step is to compile the server and the client. Use the Javac compiler to do this. Note however, if the server and client are developed on two different machines, in order to compile the client you need a copy of the interface (FileInterface). Finally, it is time to start the RMI registry and run the server and client. To start the RMI registry on the default port number, use the command rmiregistry or start rmiregistry on Windows. To start the RMI registry on a different port number, provide the port number as an argument to the RMI registry: prompt> rmiregistry portNumber
Once the RMI registry is running, you can start the server FileServer. However, since the RMI security manager is being used in the server application, you need a security policy to go with it. Here is a sample security policy: grant {permission Java.security.AllPermission "", "";};Note: this is just a sample policy. It allows anyone to do anything. For your mission critical applications, you need to specify more constraint security policies. Now, in order to start the server you need a copy of all the classes (including stubs and skeletons) except the client class (FileClient.class). To start the server use the following command, assuming that the security policy is in a file named policy.txt: prompt> Java -DJava.security.policy=policy.txt FileServer
To start the client on a different machine, you need a copy of the remote interface (FileInterface.class) and stub (FileImpl_Stub.class). To start the client use the command: prompt> Java FileClient fileName machineName
where fileName is the file to be downloaded and machineName is the machine where the file is located (the same machine runs the file server). If everything goes ok then the client exists and the file downloaded is on the local machine.
To run the client we mentioned that you need a copy of the interface and stub. A more appropriate way to do this is to use RMI dynamic class loading. The idea is you do not need copies of the interface and the stub. Instead, they can be located in a shared directory for the server and the client, and whenever a stub or a skeleton is needed, it is downloaded automatically by the RMI class loader. To do this you run the client, for example, using the following command: Java -DJava.rmi.server.codebase=http://hostname/locationOfClasses FileClient fileName machineName. For more information on this, please see Java.sun.com/prodUCts/jdk/1.2/doCS/guide/rmi/codebase.html">Dynamic Code Loading using RMI.
CORBAThe Common Object Request Broker Architecture (or CORBA) is an industry standard developed by the Object Management Group (OMG) to aid in distributed objects programming. It is important to note that CORBA is simply a specification. A CORBA implementation is known as an ORB (or Object Request Broker). There are several CORBA implementations available on the market sUCh as VisiBroker, ORBIX, and others. JavaIDL is another implementation that comes as a core package with the JDK1.3 or above. CORBA was designed to be platform and language independent. Therefore, CORBA objects can run on any platform, located anywhere on the network, and can be written in any language that has Interface Definition Language (IDL) mappings. Similar to RMI, CORBA objects are specified with interfaces. Interfaces in CORBA, however, are specified in IDL. While IDL is similar to C++, it is important to note that IDL is not a programming language. For a detailed introdUCtion to CORBA, please see Java.sun.com/developer/Books/corba">Distributed Programming with Java: Chapter 11 (Overview of CORBA). The Genesis of a CORBA ApplicationThere are a number of steps involved in developing CORBA applications. These are: - Define an interface in IDL
- Map the IDL interface to Java (done automatically)
- Implement the interface
- Develop the server
- Develop a client
- Run the naming service, the server, and the client.
We now eXPlain each step by walking you through the development of a CORBA-based file transfer application, which is similar to the RMI application we developed earlier in this article. Here we will be using the JavaIDL, which is a core package of JDK1.3+. Define the Interface When defining a CORBA interface, think about the type of operations that the server will support. In the file transfer application, the client will invoke a method to download a file. Code Sample 5 shows the interface for FileInterface. Data is a new type introdUCed using the typedef keyWord. A sequence in IDL is similar to an array except that a sequence does not have a fixed size. An octet is an 8-bit quantity that is equivalent to the Java type byte. Note that the downloadFile method takes one parameter of type string that is declared in. IDL defines three parameter-passing modes: in (for input from client to server), out (for output from server to client), and inout (used for both input and output). Code Sample 5: FileInterface.idl interface FileInterface {typedef sequence<octet> Data;Data downloadFile(in string fileName);};Once you finish defining the IDL interface, you are ready to compile it. The JDK1.3+ comes with the idlj compiler, which is used to map IDL definitions into Java declarations and statements. The idlj compiler accepts options that allow you to specify if you wish to generate client stubs, server skeletons, or both. The -f<side> option is used to specify what to generate. The side can be client, server, or all for client stubs and server skeletons. In this example, since the application will be running on two separate machines, the -fserver option is used on the server side, and the -fclient option is used on the client side. Now, let's compile the FileInterface.idl and generate server-side skeletons. Using the command: prompt> idlj -fserver FileInterface.idl
This command generates several files sUCh as skeletons, holder and helper classes, and others. An important file that gets generated is the _FileInterfaceImplBase, which will be subclassed by the class that implements the interface. Implement the interface Now, we provide an implementation to the downloadFile method. This implementation is known as a servant, and as you can see from Code Sample 6, the class FileServant extends the _FileInterfaceImplBase class to specify that this servant is a CORBA object. Code Sample 6: FileServant.Java import Java.io.*; public class FileServant extends _FileInterfaceImplBase {public byte[] downloadFile(String fileName){File file = new File(fileName);byte buffer[] = new byte[(int)file.length()];try {BufferedInputStream input = newBufferedInputStream(new FileInputStream(fileName));input.read(buffer,0,buffer.length);input.close();} catch(Exception e) {System.out.println("FileServant Error: "+e.getMessage());e.printStackTrace();}return(buffer); }}Develop the server The next step is developing the CORBA server. The FileServer class, shown in Code Sample 7, implements a CORBA server that does the following: - Initializes the ORB
- Creates a FileServant object
- Registers the object in the CORBA Naming Service (COS Naming)
- Prints a status message
- Waits for incoming client requests
Code Sample 7: FileServer.Java import Java.io.*;import org.omg.CosNaming.*;import org.omg.CosNaming.NamingContextPackage.*;import org.omg.CORBA.*;public class FileServer {public static void main(String args[]) {try{// create and initialize the ORBORB orb = ORB.init(args, null);// create the servant and register it with the ORBFileServant fileRef = new FileServant();orb.connect(fileRef);// get the root naming contextorg.omg.CORBA.Object objRef =orb.resolve_initial_references("NameService");NamingContext ncRef = NamingContextHelper.narrow(objRef);// Bind the object reference in namingNameComponent nc = new NameComponent("FileTransfer", " ");NameComponent path[] = {nc};ncRef.rebind(path, fileRef);System.out.println("Server started....");// Wait for invocations from clientsJava.lang.Object sync = new Java.lang.Object();synchronized(sync){sync.wait();}} catch(Exception e) {System.err.println("ERROR: " + e.getMessage());e.printStackTrace(System.out);}}}Once the FileServer has an ORB, it can register the CORBA service. It uses the COS Naming Service specified by OMG and implemented by Java IDL to do the registration. It starts by getting a reference to the root of the naming service. This returns a generic CORBA object. To use it as a NamingContext object, it must be narrowed down (in other Words, casted) to its proper type, and this is done using the statement: NamingContext ncRef = NamingContextHelper.narrow(objRef);
The ncRef object is now an org.omg.CosNaming.NamingContext. You can use it to register a CORBA service with the naming service using the rebind method. Develop a client The next step is to develop a client. An implementation is shown in Code Sample 8. Once a reference to the naming service has been obtained, it can be used to Access the naming service and find other services (for example the FileTransfer service). When the FileTransfer service is found, the downloadFile method is invoked. Code Sample 8: FileClient import Java.io.*;import Java.util.*;import org.omg.CosNaming.*;import org.omg.CORBA.*;public class FileClient {public static void main(String argv[]) {try {// create and initialize the ORBORB orb = ORB.init(argv, null);// get the root naming contextorg.omg.CORBA.Object objRef =orb.resolve_initial_references("NameService");NamingContext ncRef = NamingContextHelper.narrow(objRef);NameComponent nc = new NameComponent("FileTransfer", " ");// Resolve the object reference in namingNameComponent path[] = {nc};FileInterfaceOperations fileRef =FileInterfaceHelper.narrow(ncRef.resolve(path));if(argv.length < 1) {System.out.println("Usage: Java FileClient filename");}// save the fileFile file = new File(argv[0]);byte data[] = fileRef.downloadFile(argv[0]);BufferedOutputStream output = newBufferedOutputStream(new FileOutputStream(argv[0]));output.write(data, 0, data.length);output.flush();output.close();} catch(Exception e) {System.out.println("FileClient Error: " + e.getMessage());e.printStackTrace();}}}Running the application The final step is to run the application. There are several sub-steps involved: - Running the the CORBA naming service. This can be done using the command
tnameserv. By default, it runs on port 900. If you cannot run the naming service on this port, then you can start it on another port. To start it on port 2500, for example, use the following command: prompt> tnameserv -ORBinitialPort 2500
|