- Implementing a Client
- Parsing Strings by Using StringTokenizer
- Example: A Client to Verify E-Mail Addresses
- Example: A Network Client That Retrieves URLs
- The URL Class
- WebClient: Talking to Web Servers Interactively
- Implementing a Server
- Example: A Simple HTTP Server
- RMI: Remote Method Invocation
- Summary
17.9 RMI: Remote Method Invocation
Java directly supports distributing run-time objects across multiple computers through Remote Method Invocation (RMI). This distributed-objects package simplifies communication among Java applications on multiple machines. If you are already familiar with the Common Object Request Broker Architecture, think of RMI as a simpler but less powerful variation of CORBA that only works with Java systems. If you don't know anything about CORBA, think of RMI as an object-oriented version of remote procedure calls (RPC).
The idea is that the client requests an object from the server, using a simple high-level request. Once the client has the object, the client invokes the object's methods as though the object were a normal local object. Behind the scenes, however, the requests are routed to the server, where methods in the "real" object are invoked and the results returned. The beauty of the process is that neither the client nor the server has to do anything explicit with input streams, output streams, or sockets. The values that are sent back and forth can be complex Java objects (including windows and other graphical components), but no parsing is required at either end. The conversion of the object is handled by the Java serialization facility.
Now, RMI seems so convenient that you might wonder why anyone would implement sockets "by hand." First of all, RMI only works among Java systems, so RMI cannot be used for an HTTP client, an HTTP server, an e-mail client, or other applications where the other end will not necessarily be using Java. Second, even for Java-to-Java applications, RMI requires some common code to be installed on both the client and the server. This approach is in contrast to sockets, where random programs can talk to each other as long as they both understand the types of commands that should be sent. Finally, RMI is a bit more taxing on the server than regular sockets because RMI requires two versions of the Java Virtual Machine to be running (one to broker requests for objects, the other to provide the actual object implementation).
Following, we summarize the steps necessary to build an RMI application. Afterward, we present four RMI examples:
A simple RMI example that returns a message string from a remote object.
A realistic example that performs numerical integration through a remote object.
Extension of the numerical integration to an enterprise configuration showing how to set up a security policy file and HTTP server for downloading of RMI files.
An RMI applet that connects to a remote object.
Steps to Build an RMI Application
To use RMI, you need to do two things: build four classes and execute five compilation steps. We briefly describe the classes and the steps here. In the next subsections, we build the classessimple and more advancedand show the command-line commands that execute the steps, along with the output, if any, of the commands.
The Four Required Classes
To use RMI, you will need to build four main classes:
An interface for the remote object. This interface will be used by both the client and the server.
The RMI client. This client will look up the object on the remote server, cast the object to the type of the interface from Step 1, then use the object like a local object. Note that as long as a "live" reference to the remote object is present, the network connection will be maintained. The connection will be automatically closed when the remote object is garbage-collected on the client.
The object implementation. This object must implement the interface of Step 1 and will be used by the server.
The RMI server. This class will create an instance of the object from Step 3 and register the object with a particular URL.
Compiling and Running the System
Once you have the basic four classes, five further steps are required to actually use the application.
Compile client and server. This step automatically compiles the remote object interface and implementation.
Generate the client stub and the server skeleton. The client stub and server skeleton support the method calls and provide parameter marshalling (device-independent coding and serialization for transmission across a byte stream). Use the rmic compiler on the remote object implementation for this step.
The client system will need the client class, the interface class, and the client stub class. If the client is an applet, these three classes must be available from the applet's home machine.
The server system will need the server class, the remote object interface and implementation, and the server skeleton class. Note that Java 2 no longer requires the skeleton class normally placed on the server. If both the server and client are running the Java 2 Platform, then use the -v1.2 switch for the rmic compiler.
Start the RMI registry. This step needs to be done only once, not for each remote object. The current version of RMI requires this registry to be running on the same system as the server.
Start the server. This step must be done on the same machine as the registry of Step 3.
Start the client. This step can be on an arbitrary machine.
A Simple Example
Here's a simple example to illustrate the process. The remote object simply returns a message string. See the next subsection for a more realistic example.
A Simple Example of the Four Required Classes
1. The interface for the remote object.
The interface should extend java.rmi.Remote, and all the methods should throw java.rmi.RemoteException. Listing 17.21 shows an example.
Listing 17.21 Rem.java
import java.rmi.*; /** The RMI client will use this interface directly. The RMI * server will make a real remote object that implements this, * then register an instance of it with some URL. */ public interface Rem extends Remote { public String getMessage() throws RemoteException; }
2. The RMI client.
This class should look up the object from the appropriate host, using N_aming.lookup, cast the object to the appropriate type, then use the object like a local object. Unlike the case in CORBA, RMI clients must know the host that is providing the remote services. The URL can be specified by rmi://host/path or rmi://host:port/path. If the port is omitted, 1099 is used. This process can throw three possible exceptions: RemoteException, NotBoundException, and Mal_formedURLException. You are required to catch all three. You should import java.rmi.* for RemoteException, Naming, and NotBoundException. You should import java.net.* for Mal_formedURLException. In addition, many clients will pass Serializ_able objects to the remote object, so importing java.io.* is a good habit, even though it is not required in this particular case. Listing 17.22 shows an example.
Listing 17.22 RemClient.java
import java.rmi.*; // For Naming, RemoteException, etc. import java.net.*; // For MalformedURLException import java.io.*; // For Serializable interface /** Get a Rem object from the specified remote host. * Use its methods as though it were a local object. */ public class RemClient { public static void main(String[] args) { try { String host = (args.length > 0) ? args[0] : "localhost"; // Get the remote object and store it in remObject: Rem remObject = (Rem)Naming.lookup("rmi://" + host + "/Rem"); // Call methods in remObject: System.out.println(remObject.getMessage()); } catch(RemoteException re) { System.out.println("RemoteException: " + re); } catch(NotBoundException nbe) { System.out.println("NotBoundException: " + nbe); } catch(MalformedURLException mfe) { System.out.println("MalformedURLException: " + mfe); } } }
3. The remote object implementation.
This class must extend UnicastRemoteObject and implement the remote object interface defined earlier. The constructor should throw RemoteException. Listing 17.23 shows an example.
Listing 17.23 RemImpl.java
import java.rmi.*; import java.rmi.server.UnicastRemoteObject; /** This is the actual implementation of Rem that the RMI * server uses. The server builds an instance of this, then * registers it with a URL. The client accesses the URL and * binds the result to a Rem (not a RemImpl; it doesn't * have this). */ public class RemImpl extends UnicastRemoteObject implements Rem { public RemImpl() throws RemoteException {} public String getMessage() throws RemoteException { return("Here is a remote message."); } }
4. The RMI server.
The server's job is to build an object and register the object with a particular URL. Use Naming.rebind (replace any previous bindings) or Nam_ing.bind (throw AlreadyBoundException if a previous binding exists) to register the object. (The term "bind" is used differently than in the CORBA world; here, bind means "register" and is performed by the server, not the client.) You are required to catch RemoteException and MalformedURLException. Listing 17.24 shows an example.
Listing 17.24 RemServer.java
import java.rmi.*; import java.net.*; /** The server creates a RemImpl (which implements the Rem * interface), then registers it with the URL Rem, where * clients can access it. */ public class RemServer { public static void main(String[] args) { try { RemImpl localObject = new RemImpl(); Naming.rebind("rmi:///Rem", localObject); } catch(RemoteException re) { System.out.println("RemoteException: " + re); } catch(MalformedURLException mfe) { System.out.println("MalformedURLException: " + mfe); } } }
Compiling and Running the System for the Simple Example
As outlined earlier in this section, compiling and running the system requires five steps.
For this example, you must start the RMI registry, the server (RemServer), and the client (RemClient) in the same host directory. If the client and server are started from different directories, then the RMI protocol requires a SecurityManager on the client to load the stub files. Configuration of RMI to run the client and server on different hosts (or different directories) is explained later.
Core Note
For the following example to execute properly, the RMI registry, server, and client must be started from the same directory.
1. Compile the client and the server.
The following command automatically compiles the Rem interface.
Prompt> javac RemClient.java
The following command automatically compiles the RemImpl object implementation.
Prompt> javac RemServer.java
2. Generate the client Stub and server Skeleton.
The following command builds RemImpl_Stub.class and RemImpl_Skeleton.class.
Prompt> rmic RemImpl
or
Prompt> rmic -v1.2 RemImpl (for Java 2 Platform)
The client requires Rem.class, RemClient.class, and RemImpl_Stub.class. The server requires Rem.class, RemImpl.class, RemServer.class, and RemImpl_Skeleton.class.
For the Java 2 platform, the RemImpl_Skeleton.class is no longer required. To generate only the stub file required for the Java 2 Platform, add the -v1.2 switch in the RMI compiler command. This switch generates a stub file consistent with the RMI 1.2 stub protocol used by the Java 2 Platform. By default, rmic creates stubs and skeletons compatible with both the RMI 1.2 stub protocol and the earlier RMI 1.1 stub protocol used in JDK 1.1.
Core Note
If the client and server are both running the Java 2 Platform, use rmic -v1.2 to compile the interface. Using the -v1.2 switch does not generate the unnecessary skeleton file.
3. Start the RMI registry.
Start the registry as follows.
Prompt> rmiregistry
On Unix systems, you would probably add & to put the registry process in the background. On Windows, you would probably precede the command with start, as in start rmiregistry. You can also specify a port number; if omitted, port 1099 is used.
4. Start the server.
Start the server as follows.
Server> java RemServer
Again, on Unix systems, you would probably add & to put the process in the background. On Windows, you would probably precede the command with start, as in start java RemServer.
5. Start the client.
Issue the following command.
Prompt> java RemClient Here is a remote message.
A Realistic Example: A Server for Numeric Integration
Listing 17.25 shows a class that provides two methods. The first method, sum, calculates
The definition of f(x) is provided by an Evaluatable object (Listing 17.26). The second method, integrate, uses the midpoint rule (Figure 173) to approximate the integral
Figure 173 The integrate method approximates the area under the curve by adding up the area of many small rectangles that have width stepSize and whose length y = f(x) is evaluated at the midpoint of each width.
Listing 17.25 Integral.java
/** A class to calculate summations and numeric integrals. The * integral is calculated according to the midpoint rule. */ public class Integral { /** Returns the sum of f(x) from x=start to x=stop, where the * function f is defined by the evaluate method of the * Evaluatable object. */ public static double sum(double start, double stop, double stepSize, Evaluatable evalObj) { double sum = 0.0, current = start; while (current <= stop) { sum += evalObj.evaluate(current); current += stepSize; } return(sum); } /** Returns an approximation of the integral of f(x) from * start to stop, using the midpoint rule. The function f is * defined by the evaluate method of the Evaluatable object. */ public static double integrate(double start, double stop, int numSteps, Evaluatable evalObj) { double stepSize = (stop - start) / (double)numSteps; start = start + stepSize / 2.0; return(stepSize * sum(start, stop, stepSize, evalObj)); } }
Listing 17.26 Evaluatable.java
/** An interface for evaluating functions y = f(x) at a specific * value. Both x and y are double-precision floating-point * numbers. */ public interface Evaluatable { public double evaluate(double value); }
Now suppose that you have a powerful workstation that has very fast floating-point capabilities and a variety of slower PCs that need to run an interface that makes use of numerical integration. A natural approach is to make the workstation the integration server. RMI makes this solution very simple.
A Realistic Example of the Four Required Classes
In this section we provide listings for the four classed required to establish a workstation and integration server.
1. The RemoteIntegral interface.
Listing 17.27 shows the interface that will be shared by the client and server.
Listing 17.27 RemoteIntegral.java
import java.rmi.*; /** Interface for remote numeric integration object. */ public interface RemoteIntegral extends Remote { public double sum(double start, double stop, double stepSize, Evaluatable evalObj) throws RemoteException; public double integrate(double start, double stop, int numSteps, Evaluatable evalObj) throws RemoteException; }
2. The RemoteIntegral client.
Listing 17.28 shows the RMI client. It obtains a RemoteIntegral from the specified host, then uses it to approximate a variety of integrals. Note that the Evaluatable instances (Sin, Cos, Quadratic) implement Seria_lizable in addition to Evaluatable so that these objects can be transmitted over the network. The Sin, Cos, and Quadratic classes are shown in Listing 17.29, 17.30, and 17.31, respectively. The toString method in each of the three classes is used later in the RMI Applet example.
Listing 17.28 RemoteIntegralClient.java
import java.rmi.*; import java.net.*; import java.io.*; /** This class calculates a variety of numerical integration * values, printing the results of successively more accurate * approximations. The actual computation is performed on a * remote machine whose hostname is specified as a command- * line argument. */ public class RemoteIntegralClient { public static void main(String[] args) { try { String host = (args.length > 0) ? args[0] : "localhost"; RemoteIntegral remoteIntegral = (RemoteIntegral)Naming.lookup("rmi://" + host + "/RemoteIntegral"); for(int steps=10; steps<=10000; steps*=10) { System.out.println ("Approximated with " + steps + " steps:" + "\n Integral from 0 to pi of sin(x)=" + remoteIntegral.integrate(0.0, Math.PI, steps, new Sin()) + "\n Integral from pi/2 to pi of cos(x)=" + remoteIntegral.integrate(Math.PI/2.0, Math.PI, steps, new Cos()) + "\n Integral from 0 to 5 of x^2=" + remoteIntegral.integrate(0.0, 5.0, steps, new Quadratic())); } System.out.println ("'Correct' answer using Math library:" + "\n Integral from 0 to pi of sin(x)=" + (-Math.cos(Math.PI) - -Math.cos(0.0)) + "\n Integral from pi/2 to pi of cos(x)=" + (Math.sin(Math.PI) - Math.sin(Math.PI/2.0)) + "\n Integral from 0 to 5 of x^2=" + (Math.pow(5.0, 3.0) / 3.0)); } catch(RemoteException re) { System.out.println("RemoteException: " + re); } catch(NotBoundException nbe) { System.out.println("NotBoundException: " + nbe); } catch(MalformedURLException mfe) { System.out.println("MalformedURLException: " + mfe); } } }
Listing 17.29 Sin.java
import java.io.Serializable; /** An evaluatable version of sin(x). */ class Sin implements Evaluatable, Serializable { public double evaluate(double val) { return(Math.sin(val)); } public String toString() { return("Sin"); } }
Listing 17.30 Cos.java
import java.io.Serializable; /** An evaluatable version of cos(x). */ class Cos implements Evaluatable, Serializable { public double evaluate(double val) { return(Math.cos(val)); } public String toString() { return("Cosine"); } }
Listing 17.31 Quadratic.java
import java.io.Serializable; /** An evaluatable version of x^2. */ class Quadratic implements Evaluatable, Serializable { public double evaluate(double val) { return(val * val); } public String toString() { return("Quadratic"); } }
3. The RemoteIntegral implementation.
Listing 17.32 shows the implementation of the RemoteIntegral interface. It simply uses methods in the Integral class.
Listing 17.32 RemoteIntegralImpl.java
import java.rmi.*; import java.rmi.server.UnicastRemoteObject; /** The actual implementation of the RemoteIntegral interface. */ public class RemoteIntegralImpl extends UnicastRemoteObject implements RemoteIntegral { /** Constructor must throw RemoteException. */ public RemoteIntegralImpl() throws RemoteException {} /** Returns the sum of f(x) from x=start to x=stop, where the * function f is defined by the evaluate method of the * Evaluatable object. */ public double sum(double start, double stop, double stepSize, Evaluatable evalObj) { return(Integral.sum(start, stop, stepSize, evalObj)); } /** Returns an approximation of the integral of f(x) from * start to stop, using the midpoint rule. The function f is * defined by the evaluate method of the Evaluatable object. * @see #sum */ public double integrate(double start, double stop, int numSteps, Evaluatable evalObj) { return(Integral.integrate(start, stop, numSteps, evalObj)); } }
4. The RemoteIntegral server.
Listing 17.33 shows the server that creates a RemoteIntegralImpl object and registers the object with the URL RemoteIntegral on the local system.
Listing 17.33 RemoteIntegralServer.java
import java.rmi.*; import java.net.*; /** Creates a RemoteIntegralImpl object and registers it under * the name 'RemoteIntegral' so that remote clients can connect * to it for numeric integration results. The idea is to place * this server on a workstation with very fast floating-point * capabilities, while slower interfaces can run on smaller * computers but still use the integration routines. */ public class RemoteIntegralServer { public static void main(String[] args) { try { RemoteIntegralImpl integral = new RemoteIntegralImpl(); Naming.rebind("rmi:///RemoteIntegral", integral); } catch(RemoteException re) { System.out.println("RemoteException: " + re); } catch(MalformedURLException mfe) { System.out.println("MalformedURLException: " + mfe); } } }
Compiling and Running the System for the Realistic Example
For this example, you must start the RMI registry, the server (RemoteIntegralServer), and the client (RemoteIntegralClient) in the same host directory. If the client and server are started from different directories, then the RMI protocol requires a SecurityManager on the client to load the stub files. Configuration of RMI to run the client and server on different hosts (or different directories) is explained following this example.
Core Note
For the following example to execute properly, the RMI registry, server, and client must be started from the same directory.
1. Compile the client and server.
At the prompt, enter these commands:
Prompt> javac RemoteIntegralClient.java Prompt> javac RemoteIntegralServer.java
2. Generate the client Stub and server Skeleton.
At the prompt, enter this command.
Prompt> rmic -v1.2 RemoteIntegralImpl
The classes required by the client are: RemoteIntegral.class, RemoteIntegralClient.class. and RemoteIntegralImpl_Stub.class. The classes required by the server are: RemoteIntegral.class, RemoteIntegralImpl.class, and RemoteIntegralServer.class. If the server and client are both running JDK 1.1, use the -v1.1 switch to produce the RMI 1.1 skeleton stub, RemoteIntegralImpl_Skeleton, required by the server.
3. Start the RMI registry.
The following command starts the RMI registry:
Prompt> rmiregistry
4. Start the Server.
At the prompt, enter this command:
Prompt> java RemoteIntegralServer
5. Start the client.
At the prompt, enter this command to obtain the output listed below:
Prompt> java RemoteIntegralClient Approximated with 10 steps: Integral from 0 to pi of sin(x)=2.0082484079079745 Integral from pi/2 to pi of cos(x)=-1.0010288241427086 Integral from 0 to 5 of x^2=41.5625 Approximated with 100 steps: Integral from 0 to pi of sin(x)=2.0000822490709877 Integral from pi/2 to pi of cos(x)=-1.000010280911902 Integral from 0 to 5 of x^2=41.665624999999906 Approximated with 1000 steps: Integral from 0 to pi of sin(x)=2.0000008224672983 Integral from pi/2 to pi of cos(x)=-1.000000102808351 Integral from 0 to 5 of x^2=41.666656249998724 Approximated with 10000 steps: Integral from 0 to pi of sin(x)=2.00000000822436 Integral from pi/2 to pi of cos(x)=-1.0000000010278831 Integral from 0 to 5 of x^2=41.666666562504055 'Correct' answer using Math library: Integral from 0 to pi of sin(x)=2.0 Integral from pi/2 to pi of cos(x)=-0.9999999999999999 Integral from 0 to 5 of x^2=41.666666666666664
The actual integral value are:
As the number of steps increases, the numerical integration approaches the actual value. The benefit of RMI is that you can off-load the integration to a more powerful server.
Enterprise RMI Configuration
In the previous examples, the RMI registry, server, and client, were all assumed to be running on the same host. Certainly, this configuration does not take advantage of the distributed capabilities of RMI. However, once the server and client are running on different hosts, Java 2 requires that the client and server have an installed security manager to load the RMI classes remotely; by default, the required RMI classes can only be loaded from the local host. Furthermore, the RMI registry and server must be started on the same host. The rmiregistry only permits registration of remote objects from the local host.
In addition to a security manager, changes in the default security policies are required to allow the client to open connections to remote hosts. Following, we show you the steps to use RMI in an enterprise environment where the server and client are located on different machines.
To load classes remotely, the client must install an RMISecurityManager
System.setSecurityManager(new RMISecurityManager());
as shown in the modified client, RemoteIntegralClient2 (Listing 17.34).
Core Note
In an enterprise configuration, the rmiregistry and server must be started on the same host; otherwise, an AccessException is thrown. Additionally, the client must provide a policy file and set an RMISecurityManager to remotely load the stub files.
Listing 17.34 RemoteIntegralClient2.java
import java.rmi.*; import java.net.*; import java.io.*; /** This class is a Java 2 version of RemoteIntegralClient * that imposes a SecurityManager to allow the client to * connect to a remote machine for loading stub files and * performing numerical integration through a remote * object. */ public class RemoteIntegralClient2 { public static void main(String[] args) { try { System.setSecurityManager(new RMISecurityManager()); String host = (args.length > 0) ? args[0] : "localhost"; RemoteIntegral remoteIntegral = (RemoteIntegral)Naming.lookup("rmi://" + host + "/RemoteIntegral"); for(int steps=10; steps<=10000; steps*=10) { System.out.println ("Approximated with " + steps + " steps:" + "\n Integral from 0 to pi of sin(x)=" + remoteIntegral.integrate(0.0, Math.PI, steps, new Sin()) + "\n Integral from pi/2 to pi of cos(x)=" + remoteIntegral.integrate(Math.PI/2.0, Math.PI, steps, new Cos()) + "\n Integral from 0 to 5 of x^2=" + remoteIntegral.integrate(0.0, 5.0, steps, new Quadratic())); } System.out.println ("'Correct' answer using Math library:" + "\n Integral from 0 to pi of sin(x)=" + (-Math.cos(Math.PI) - -Math.cos(0.0)) + "\n Integral from pi/2 to pi of cos(x)=" + (Math.sin(Math.PI) - Math.sin(Math.PI/2.0)) + "\n Integral from 0 to 5 of x^2=" + (Math.pow(5.0, 3.0) / 3.0)); } catch(RemoteException re) { System.out.println("RemoteException: " + re); } catch(NotBoundException nbe) { System.out.println("NotBoundException: " + nbe); } catch(MalformedURLException mfe) { System.out.println("MalformedURLException: " + mfe); } } }
In Java 2, in addition to imposing a security manager, the client requires a policy file to specify permission for dynamic loading of remote classes. Basically, you need to state in which host and ports the client can open a socket connection. Listing 17.35 illustrates the client permissions to connect to the rmiregistry and server running on rmihost and to an HTTP server, webhost, to load the stub files.
By default the rmiregistry listens on port 1099. When the client looks up the remote object, it first connects to the rmiregistry on port 1099. Afterward, the client communicates directly to the remote server on the port at which the server is listening. Note that when the server is started and registers the remote object, the source port used to communicate with the RMI registry and client is randomly selected from an available port in the range 102465535 on the host. As a consequence, to allow connection to the rmihost, permission is required over the complete range of possible ports, 102465535, not just port 1099. Also, the stub classes are often placed on an HTTP server, so the policy file should also grant permission for the client to connect to the webhost.
Listing 17.35 Policy file for client, rmiclient.policy
grant { // rmihost - RMI registry and the server // webhost - HTTP server for stub classes permission java.net.SocketPermission "rmihost:1024-65535", "connect"; permission java.net.SocketPermission "webhost:80", "connect"; };
You can specify the security policy file through a command-line argument when executing the client, as in
java -Djava.security.policy=rmiclient.policy RemoteIntegralClient2
Or, you can add the permission statements to the java.policy file used by the Java Virtual Machine and avoid the command-line argument. For JDK 1.3, the java.policy file is located in the directory /root/jdk1.3/lib/security/.
When starting the server that registers the remote object, you also need to specify the codebase location (HTTP server) to load the stub files. As with the policy file, you can specify the codebase on the command line through the system property, java.rmi.server.codebase, as in
java -Djava.rmi.server.codebase=http://webhost:port/directory/ RemoteIntegralServer
The java.rmi.server.codebase property tells the server that any stub files required by the client or the RMI registry should be loaded from the HTTP server at http://webhost:port/directory/. The port parameter is required only if the HTTP server is not running on standard port 80.
Compiling and Running the System for an Enterprise RMI Configuration
1. Compile the client and server.
At the prompt, enter these commands:
Prompt> javac RemoteIntegralClient2.java Prompt> javac RemoteIntegralServer.java
2. Generate the client Stub and server Skeleton.
At the prompt, enter this command.
Prompt> rmic -v1.2 RemoteIntegralImpl
3. Place the appropriate files on the correct machines.
Table 17.1 summarizes the locations to place the class files. The client requires RemoteIntegralClient2 and all other instantiated or referenced classes, including RemoteIntegral, Sin, Cos, and Quadratic. As the later three classes inherit from Evaluatable, that class is also needed on the client machine; Evaluatable is not downloaded from the server. Also, the client needs the policy file, rmipolicy.client.
The server that instantiates and registers the remote object requires RemoteIntegralServer, and when it creates the remote object, the server requires RemoteIntegralImpl, RemoteIntegral (inherited class), and Eva_luatable; these last three classes are not downloaded from the codebase directory (HTTP server). The class Integral is required on the server, since the static methods, Inte_ger.sum and Inte_ger.evaluate, are called in RemoteIntegralImpl. The server also requires RemoteIntegralImpl_Stub when registering the remote object. Finally, Sin, Cos, and Quadratic are required on the server; these classes override the evaulate method defined in the interface Evaluatable and are later required through dynamic binding in Integral when evaluate is called. These class definitions are not sent from the client.
The HTTP server requires the stub file, RemoteIntegralImpl_Stub, for downloading to the client. In addition, you must place any RemoteIntegralImpl dependencies, RemoteIntegral and Evaluatable, on the HTTP server for use when the remote object is registered; the rmiregis_try does not receive these files from the server instantiating the remote object.
Table 17.1 Required location for class files
Client |
Server |
HTTP Server |
RemoteIntegralClient2 RemoteIntegral Evaluatable Sin Cos Quadratic |
RemoteIntegralServer RemoteIntegralImpl RemoteIntegralImpl_Stub RemoteIntegral Integral Evaluatable Sin Cos Quadratic |
RemoteIntegralImpl_Stub RemoteIntegral Evaluatable |
4. Start the HTTP server.
Place RemoteIntegral_Stub.class, RemoteIntegeral.class, and Evaluatable.class on an HTTP server and verify that you can access the files through a browser.
5. Start the RMI registry.
Start the RMI registry:
Server> /somedirectory/rmiregistry
When you start the rmiregistry, make sure that none of the class files are in the directory in which you started the registry or available through the classpath.
Core Warning
The client and server may not be able to load the stub files from the correct location if the RMI registry is able to locate the files through the classpath. Always start the rmiregistry, without a set classpath, in a different directory than that of the server.
6. Start the server.
At the prompt, enter this command:
Server> java -Djava.rmi.server.codebase=http://webhost/rmi/ RemoteIntegralServer
assuming that the stub files are placed on the HTTP server, webhost, and in the rmi subdirectory. Note that the server must be started on the same host as the rmiregistry, but not from within the same directory. If an exception is thrown when starting the server, correct the source of the problem, and restart the RMI registry before attempting to restart the server.
Core Note
The rmiregistry and server need to run on the same host or an AccessException is received by the server.
7. Start the client.
At the prompt, enter the following command (where rmihost is the host in which you started the rmiregistry and server) to obtain the output listed below:
Client> java -Djava.security.policy=rmiclient.policy RemoteIntegralClient2 rmihost
Approximated with 10 steps: @Integral from 0 to pi of sin(x)=2.0082484079079745 @Integral from pi/2 to pi of cos(x)=-1.0010288241427086 Integral from 0 to 5 of x^2=41.5625 Approximated with 100 steps: Integral from 0 to pi of sin(x)=2.0000822490709877 Integral from pi/2 to pi of cos(x)=-1.000010280911902 Integral from 0 to 5 of x^2=41.665624999999906 Approximated with 1000 steps: Integral from 0 to pi of sin(x)=2.0000008224672983 Integral from pi/2 to pi of cos(x)=-1.000000102808351 Integral from 0 to 5 of x^2=41.666656249998724 Approximated with 10000 steps: Integral from 0 to pi of sin(x)=2.00000000822436 Integral from pi/2 to pi of cos(x)=-1.0000000010278831 Integral from 0 to 5 of x^2=41.666666562504055 'Correct' answer using Math library: Integral from 0 to pi of sin(x)=2.0 Integral from pi/2 to pi of cos(x)=-0.9999999999999999 Integral from 0 to 5 of x^2=41.666666666666664
RMI Applet Example
Compared to writing an application communicating with a remote object through RMI, writing an applet that communicates through RMI is greatly simplified because the applet already invokes a security manager for loading remote files; an applet using RMI does not require a RMISecurityManager. In contrast though, an applet cannot open network connections other than to the server from which the applet was loaded. Therefore, the RMI registry, the server registering the remote object, and the HTTP server from which the applet and stub files are loaded must be the same host.
When posting the applet on an HTTP server, be sure to place all the client files in the same directory as the applet class file. Or, alternatively, you can place a single JAR file in the applet directory and specify the JAR file through the ARCHIVE attribute in the APPLET tag. See Section 7.10 (Packages, Classpath, and JAR Archives) for details on creating JAR files.
Listing 17.36 presents an applet client that communicates to a remote object through RMI. Since the RMI registry and server are located on the same host in which the applet was loaded, the host for the RMI URL is determined by a call to getCodeBase().getHost(). The results are shown in Figure 174 in Netscape 6.
Figure 174 Applet that communicates to a remote object through RMI in Netscape 6.
Listing 17.36 RemoteIntegralApplet.java
import java.awt.*; import java.awt.event.*; import java.rmi.*; import java.net.*; import java.io.*; import javax.swing.*; /** This class is an applet version of RemoteIntegralClient * that connects to a remote machine for performing * numerical integration in a sine, cosine, or quadratic * equation. As an Applet imposes its own security * manager, a RMISecurityManager is not needed to load * the stub classes. */ public class RemoteIntegralApplet extends JApplet implements ActionListener { private Evaluatable[] shapes; private RemoteIntegral remoteIntegral; private JLabel result; private JTextField startInput, stopInput, stepInput; private JComboBox combo; public void init() { String host = getCodeBase().getHost(); try { remoteIntegral = (RemoteIntegral)Naming.lookup("rmi://" + host + "/RemoteIntegral"); } catch(RemoteException re) { reportError("RemoteException: " + re); } catch(NotBoundException nbe) { reportError("NotBoundException: " + nbe); } catch(MalformedURLException mfe) { reportError("MalformedURLException: " + mfe); } Container context = getContentPane(); // Set up combo box. shapes = new Evaluatable[]{ new Sin(), new Cos(), new Quadratic() }; combo = new JComboBox(shapes); context.add(combo, BorderLayout.NORTH); // Input area. startInput = new JTextField(); stopInput = new JTextField(); stepInput = new JTextField(); result = new JLabel(); JPanel labelPanel = new JPanel(new GridLayout(4,1)); labelPanel.add(new JLabel("Start:")); labelPanel.add(new JLabel("Stop:")); labelPanel.add(new JLabel("Steps:")); labelPanel.add(new JLabel("Result: ")); context.add(labelPanel, BorderLayout.WEST); JPanel inputPanel = new JPanel(new GridLayout(4,1)); inputPanel.add(startInput); inputPanel.add(stopInput); inputPanel.add(stepInput); inputPanel.add(result); context.add(inputPanel, BorderLayout.CENTER); (continued) // Set up button. JPanel buttonPanel = new JPanel(new FlowLayout()); JButton submit = new JButton("Submit"); submit.addActionListener(this); buttonPanel.add(submit); context.add(buttonPanel, BorderLayout.SOUTH); } public void actionPerformed(ActionEvent event) { try { int steps = Integer.parseInt(stepInput.getText()); double start = Double.parseDouble(startInput.getText()); double stop = Double.parseDouble(stopInput.getText()); showStatus("Calculating ..."); Evaluatable shape = (Evaluatable)combo.getSelectedItem(); double area = remoteIntegral.integrate(start, stop, steps, shape); result.setText(Double.toString(area)); showStatus(""); } catch(NumberFormatException nfe) { reportError("Bad input: " + nfe); } catch(RemoteException re) { reportError("RemoteException: " + re); } } private void reportError(String message) { System.out.println(message); showStatus(message); } }
Aside from Netscape 6, earlier versions of Netscape and all versions of Internet Explorer do not support Java 2 and the RMI 1.2 stub protocol without having the Java Plug-In installed (see Section 9.9, "The Java Plug-In"). Even worse, Internet Explorer does not support the RMI 1.1 stub protocol without having the RMI add-on installed. The RMI add-on is available at ftp://ftp/microsoft.com/developr/msdn/unsup-ed/. The RMI 1.1 stub protocol is supported in Netscape 4.06 and later.