Start of Tutorial
Start of Trail
RMI applications are often comprised of two separate programs: a server and a client. A typical server application creates some remote objects, makes references to them accessible, and waits for clients to invoke methods on these remote objects. A typical client application gets a remote reference to one or more remote objects in the server and then invokes methods on them. RMI provides the mechanism by which the server and the client communicate and pass information back and forth. Such an application is sometimes referred to as a distributed object application.Distributed object applications need to
The following illustration depicts an RMI distributed application that uses the registry to obtain a reference to a remote object. The server calls the registry to associate (or bind) a name with a remote object. The client looks up the remote object by its name in the server's registry and then invokes a method on it. The illustration also shows that the RMI system uses an existing Web server to load class bytecodes, from server to client and from client to server, for objects when needed.
- Locate remote objects: Applications can use one of two mechanisms to obtain references to remote objects. An application can register its remote objects with RMI's simple naming facility, the
rmiregistry, or the application can pass and return remote object references as part of its normal operation.- Communicate with remote objects: Details of communication between remote objects are handled by RMI; to the programmer, remote communication looks like a standard Java method invocation.
- Load class bytecodes for objects that are passed around: Because RMI allows a caller to pass objects to remote objects, RMI provides the necessary mechanisms for loading an object's code, as well as for transmitting its data.
This lesson contains the following sections:
One of the central and unique features of RMI is its ability to download the bytecodes (or simply code) of an object's class if the class is not defined in the receiver's virtual machine. The types and the behavior of an object, previously available only in a single virtual machine, can be transmitted to another, possibly remote, virtual machine. RMI passes objects by their true type, so the behavior of those objects is not changed when they are sent to another virtual machine. This allows new types to be introduced into a remote virtual machine, thus extending the behavior of an application dynamically. The compute engine example in this chapter uses RMI's capability to introduce new behavior to a distributed program.
Like any other application, a distributed application built using Java RMI is made up of interfaces and classes. The interfaces define methods, and the classes implement the methods defined in the interfaces and, perhaps, define additional methods as well. In a distributed application some of the implementations are assumed to reside in different virtual machines. Objects that have methods that can be called across virtual machines are remote objects.An object becomes remote by implementing a remote interface, which has the following characteristics.
- A remote interface extends the interface
java.rmi.Remote.- Each method of the interface declares
java.rmi.RemoteExceptionin itsthrowsclause, in addition to any application-specific exceptions.RMI treats a remote object differently from a nonremote object when the object is passed from one virtual machine to another. Rather than making a copy of the implementation object in the receiving virtual machine, RMI passes a remote stub for a remote object. The stub acts as the local representative, or proxy, for the remote object and basically is, to the caller, the remote reference. The caller invokes a method on the local stub, which is responsible for carrying out the method call on the remote object.
A stub for a remote object implements the same set of remote interfaces that the remote object implements. This allows a stub to be cast to any of the interfaces that the remote object implements. However, this also means that only those methods defined in a remote interface are available to be called in the receiving virtual machine.
When you use RMI to develop a distributed application, you follow these general steps.
- Design and implement the components of your distributed application.
- Compile sources and generate stubs.
- Make classes network accessible.
- Start the application.
Design and Implement the Application Components
First, decide on your application architecture and determine which components are local objects and which ones should be remotely accessible. This step includes:
- Defining the remote interfaces: A remote interface specifies the methods that can be invoked remotely by a client. Clients program to remote interfaces, not to the implementation classes of those interfaces. Part of the design of such interfaces is the determination of any local objects that will be used as parameters and return values for these methods; if any of these interfaces or classes do not yet exist, you need to define them as well.
- Implementing the remote objects: Remote objects must implement one or more remote interfaces. The remote object class may include implementations of other interfaces (either local or remote) and other methods (which are available only locally). If any local classes are to be used as parameters or return values to any of these methods, they must be implemented as well.
- Implementing the clients: Clients that use remote objects can be implemented at any time after the remote interfaces are defined, including after the remote objects have been deployed.
Compile Sources and Generate Stubs
This is a two-step process. In the first step you use thejavaccompiler to compile the source files, which contain the implementation of the remote interfaces and implementations, the server classes, and the client classes. In the second step you use thermiccompiler to create stubs for the remote objects. RMI uses a remote object's stub class as a proxy in clients so that clients can communicate with a particular remote object.Make Classes Network Accessible
In this step you make everything--the class files associated with the remote interfaces, stubs, and other classes that need to be downloaded to clients--accessible via a Web server.Start the Application
Starting the application includes running the RMI remote object registry, the server, and the client.The rest of this lesson walks through the steps to create a compute engine.
This trail focuses on a simple yet powerful distributed application called a compute engine. The compute engine, a remote object in the server, takes tasks from clients, runs them, and returns any results. The tasks are run on the machine where the server is running. This sort of distributed application could allow a number of client machines to make use of a particularly powerful machine or one that has specialized hardware.The novel aspect of the compute engine is that the tasks it runs do not need to be defined when the compute engine is written. New kinds of tasks can be created at any time and then given to the compute engine to be run. All that is required of a task is that its class implement a particular interface. Such a task can be submitted to the compute engine and run, even if the class that defines that task was written long after the compute engine was written and started. The code needed to accomplish the task can be downloaded by the RMI system to the compute engine, and then the engine runs the task, using the resources on the machine on which the compute engine is running.
The ability to perform arbitrary tasks is enabled by the dynamic nature of the Java platform, which is extended to the network by RMI. RMI dynamically loads the task code into the compute engine's Java virtual machine and runs the task without prior knowledge of the class that implements the task. An application like this, which has the ability to download code dynamically, is often called a behavior-based application. Such applications usually require full agent-enabled infrastructures. With RMI such applications are part of the basic mechanisms for distributed computing on the Java platform.
The compute engine server accepts tasks from clients, runs the tasks, and returns any results. The server is comprised of an interface and a class. The interface provides the definition for the methods that can be called from the client. Essentially the interface defines the client's view of the remote object. The class provides the implementation.This section shows you theComputeinterface, which provides the connection between the client and the server. You will also learn about the RMI API, which supports this communication.Implementing a Remote Interface
This section explores the class that implements theComputeinterface, thereby implementing a remote object. This class also provides the rest of the code that makes up the server program: amainmethod that creates an instance of the remote object, registers it with the naming facility, and sets up a security manager.
At the heart of the compute engine is a protocol that allows jobs to be submitted to the compute engine, the compute engine to run those jobs, and the results of the job to be returned to the client. This protocol is expressed in interfaces supported by the compute engine and by the objects that are submitted to the compute engine, as shown in the following figure.
Each of the interfaces contains a single method. The compute engine's interface,
Compute, allows jobs to be submitted to the engine; the client interface,Task,defines how the compute engine executes a submitted task.The
interface defines the remotely accessible part--the compute engine itself. Here is the remote interface with its single method:compute.Computepackage compute; import java.rmi.Remote; import java.rmi.RemoteException; public interface Compute extends Remote { Object executeTask(Task t) throws RemoteException; }By extending the interface
java.rmi.Remote, this interface marks itself as one whose methods can be called from any virtual machine. Any object that implements this interface becomes a remote object.As a member of a remote interface, the
executeTaskmethod is a remote method. Therefore the method must be defined as being capable of throwing ajava.rmi.RemoteException. This exception is thrown by the RMI system during a remote method call to indicate that either a communication failure or a protocol error has occurred. ARemoteExceptionis a checked exception, so any code making a call to a remote method needs to handle this exception by either catching it or declaring it in itsthrowsclause.The second interface needed for the compute engine defines the type
Task. This type is used as the argument to theexecuteTaskmethod in theComputeinterface. Theinterface defines the interface between the compute engine and the work that it needs to do, providing the way to start the work.compute.Taskpackage compute; import java.io.Serializable; public interface Task extends Serializable { Object execute(); }The
Taskinterface defines a single method,execute, which returns anObject, has no parameters, and throws no exceptions. Since the interface does not extendRemote, the method in this interface doesn't need to listjava.rmi.RemoteExceptionin itsthrowsclause.The return value for the
Compute'sexecuteTaskandTask'sexecutemethods is declared to be of typeObject. This means that any task that wants to return a value of one of the primitive types, such as anintor afloat, needs to create an instance of the equivalent wrapper class for that type, such as anIntegeror aFloat, and return that object instead.Note that the
Taskinterface extends thejava.io.Serializableinterface. RMI uses the object serialization mechanism to transport objects by value between Java virtual machines. ImplementingSerializablemarks the class as being capable of conversion into a self-describing byte stream that can be used to reconstruct an exact copy of the serialized object when the object is read back from the stream.Different kinds of tasks can be run by a
Computeobject as long as they are implementations of theTasktype. The classes that implement this interface can contain any data needed for the computation of the task and any other methods needed for the computation.Here is how RMI makes this simple compute engine possible. Since RMI can assume that the
Taskobjects are written in the Java programming language, implementations of theTaskobject that were previously unknown to the compute engine are downloaded by RMI into the compute engine's virtual machine as needed. This allows clients of the compute engine to define new kinds of tasks to be run on the server machine without needing the code to be explicitly installed on that machine. In addition, because theexecuteTaskmethod returns ajava.lang.Object, any type of object can be passed as a return value in the remote call.The compute engine, implemented by the
ComputeEngineclass, implements theComputeinterface, allowing different tasks to be submitted to it by calls to itsexecuteTaskmethod. These tasks are run using the task's implementation of theexecutemethod. The compute engine reports results to the caller through its return value: anObject.
Let's turn now to the task of implementing a class for the compute engine. In general the implementation class of a remote interface should at least
- Declare the remote interfaces being implemented
- Define the constructor for the remote object
- Provide an implementation for each remote method in the remote interfaces
The server needs to create and to install the remote objects. This setup procedure can be encapsulated in a
mainmethod in the remote object implementation class itself, or it can be included in another class entirely. The setup procedure should
- Create and install a security manager
- Create one or more instances of a remote object
- Register at least one of the remote objects with the RMI remote object registry (or another naming service such as one that uses JNDI), for bootstrapping purposes
The complete implementation of the compute engine follows. The
class implements the remote interfaceengine.ComputeEngineComputeand also includes themainmethod for setting up the compute engine.package engine; import java.rmi.*; import java.rmi.server.*; import compute.*; public class ComputeEngine extends UnicastRemoteObject implements Compute { public ComputeEngine() throws RemoteException { super(); } public Object executeTask(Task t) { return t.execute(); } public static void main(String[] args) { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } String name = "//host/Compute"; try { Compute engine = new ComputeEngine(); Naming.rebind(name, engine); System.out.println("ComputeEngine bound"); } catch (Exception e) { System.err.println("ComputeEngine exception: " + e.getMessage()); e.printStackTrace(); } } }Now let's take a closer look at each of the components of the compute engine implementation.
The implementation class for the compute engine is declared aspublic class ComputeEngine extends UnicastRemoteObject implements ComputeThis declaration states that the class implements the
Computeremote interface (and therefore defines a remote object) and extends the classjava.rmi.server.UnicastRemoteObject.
UnicastRemoteObjectis a convenience class, defined in the RMI public API, that can be used as a superclass for remote object implementations. The superclassUnicastRemoteObjectsupplies implementations for a number ofjava.lang.Objectmethods (equals,hashCode,toString) so that they are defined appropriately for remote objects. UnicastRemoteObjectalso includes constructors and static methods used to export a remote object, that is, make the remote object available to receive incoming calls from clients.A remote object implementation does not have to extend
UnicastRemoteObject, but any implementation that does not must supply appropriate implementations of thejava.lang.Objectmethods. Furthermore, a remote object implementation must make an explicit call to one ofUnicastRemoteObject'sexportObjectmethods to make the RMI runtime aware of the remote object so that the object can accept incoming calls. By extendingUnicastRemoteObject, theComputeEngineclass can be used to create a simple remote object that supports unicast (point-to-point) remote communication and that uses RMI's default sockets-based transport for communication.If you choose to extend a remote object from any class other than
Unicast-RemoteObjector, alternatively, extend from the new JDK 1.2 classjava.rmi.activation.Activatable(used to construct remote objects that can execute on demand), you need to export the remote object by calling either theUnicastRemoteObject.exportObjectorActivatable.exportObjectmethod explicitly from your class's constructor (or another initialization method, as appropriate).The compute engine example defines a remote object class that implements only a single remote interface and no other interfaces. The
ComputeEngineclass also contains some methods that can be called only locally. The first of these is a constructor forComputeEngineobjects; the second is amainmethod that is used to create aComputeEngineand make it available to clients.
TheComputeEngineclass has a single constructor that takes no arguments. The code for the constructor isThis constructor simply calls the superclass constructor, which is the no-argument constructor of thepublic ComputeEngine() throws RemoteException { super(); }UnicastRemoteObjectclass. Although the superclass constructor gets called even if omitted from theComputeEngineconstructor, we include it for clarity.During construction, a
UnicastRemoteObjectis exported, meaning that it is available to accept incoming requests by listening for incoming calls from clients on an anonymous port.
Note: In JDK 1.2 you may indicate the specific port that a remote object uses to accept requests.
The no-argument constructor for the superclass,
UnicastRemoteObject, declares the exceptionRemoteExceptionin itsthrowsclause, so theCompute-Engineconstructor must also declare that it can throwRemoteException. ARemoteExceptioncan occur during construction if the attempt to export the object fails--due to, for example, communication resources being unavailable or the appropriate stub class not being found.
The class for a remote object provides implementations for each of the remote methods specified in the remote interfaces. TheComputeinterface contains a single remote method,executeTask, which is implemented as follows:public Object executeTask(Task t) { return t.execute(); }This method implements the protocol between the
ComputeEngineand its clients. Clients provide theComputeEnginewith aTaskobject, which has an implementation of the task'sexecutemethod. TheComputeEngineexecutes theTaskand returns the result of the task'sexecutemethod directly to the caller.The
executeTaskmethod does not need to know anything more about the result of theexecutemethod than that it is at least anObject. The caller presumably knows more about the precise type of theObjectreturned and can cast the result to the appropriate type.Passing Objects in RMI
Arguments to or return values from remote methods can be of almost any type, including local objects, remote objects, and primitive types. More precisely, any entity of any type can be passed to or from a remote method as long as the entity is an instance of a type that is a primitive data type, a remote object, or a serializable object, which means that it implements the interfacejava.io.Serializable.A few object types do not meet any of these criteria and thus cannot be passed to or returned from a remote method. Most of these objects, such as a file descriptor, encapsulate information that makes sense only within a single address space. Many of the core classes, including those in the packages
java.langandjava.util, implement theSerializableinterface.The rules governing how arguments and return values are passed are as follows.
- Remote objects are essentially passed by reference. A remote object reference is a stub, which is a client-side proxy that implements the complete set of remote interfaces that the remote object implements.
- Local objects are passed by copy, using object serialization. By default all fields are copied, except those that are marked
staticortransient. Default serialization behavior can be overridden on a class-by-class basis.Passing an object by reference (as is done with remote objects) means that any changes made to the state of the object by remote method calls are reflected in the original remote object. When passing a remote object, only those interfaces that are remote interfaces are available to the receiver; any methods defined in the implementation class or defined in nonremote interfaces implemented by the class are not available to that receiver.
For example, if you were to pass a reference to an instance of the
ComputeEngineclass, the receiver would have access only to the compute engine'sexecuteTaskmethod. That receiver would not see either theComputeEngineconstructor or itsmainmethod or any of the methods injava.lang.Object.In remote method calls objects--parameters, return values, and exceptions--that are not remote objects are passed by value. This means that a copy of the object is created in the receiving virtual machine. Any changes to this object's state at the receiver are reflected only in the receiver's copy, not in the original instance.
The most involved method of theComputeEngineimplementation is themainmethod. Themainmethod is used to start theComputeEngineand therefore needs to do the necessary initialization and housekeeping to prepare the server for accepting calls from clients. This method is not a remote method, which means that it cannot be called from a different virtual machine. Since themainmethod is declaredstatic, the method is not associated with an object at all but rather with the classComputeEngine.
The first thing that themainmethod does is to create and to install a security manager, which protects access to system resources from untrusted downloaded code running within the virtual machine. The security manager determines whether downloaded code has access to the local file system or can perform any other privileged operations.All programs using RMI must install a security manager, or RMI will not download classes (other than from the local class path) for objects received as parameters, return values, or exceptions in remote method calls. This restriction ensures that the operations performed by downloaded code go through a set of security checks.
The
ComputeEngineuses a security manager supplied as part of the RMI system, theRMISecurityManager. This security manager enforces a similar security policy as the typical security manager for applets; that is to say, it is very conservative as to what access it allows. An RMI application could define and use anotherSecurityManagerclass that gave more liberal access to system resources or, in JDK 1.2, use a policy file that grants more permissions.Here's the code that creates and installs the security manager:
if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); }
Next, themainmethod creates an instance of theComputeEngine. This is done with the statementCompute engine = new ComputeEngine();As mentioned, this constructor calls the
UnicastRemoteObjectsuperclass constructor, which in turn exports the newly created object to the RMI runtime. Once the export step is complete, theComputeEngineremote object is ready to accept incoming calls from clients on an anonymous port, one chosen by RMI or the underlying operating system. Note that the type of the variableengineisCompute, notComputeEngine. This declaration emphasizes that the interface available to clients is theComputeinterface and its methods, not theCompute-Engineclass and its methods.Before a caller can invoke a method on a remote object, that caller must first obtain a reference to the remote object. This can be done in the same way that any other object reference is obtained in a program, such as getting it as part of the return value of a method or as part of a data structure that contains such a reference.
The system provides a particular remote object, the RMI registry, for finding references to remote objects. The RMI registry is a simple remote object name service that allows remote clients to get a reference to a remote object by name. The registry is typically used only to locate the first remote object an RMI client needs to use. That first remote object then provides support for finding other objects.
The
java.rmi.Naminginterface is used as a front-end API for binding, or registering, and looking up remote objects in the registry. Once a remote object is registered with the RMI registry on the local host, callers on any host can look up the remote object by name, obtain its reference, and then invoke remote methods on the object. The registry may be shared by all servers running on a host, or an individual server process may create and use its own registry, if desired.The
ComputeEngineclass creates a name for the object with the statementString name = "//host/Compute";This name includes the host name,
host, on which the registry (and remote object) is being run and a name,Compute, that identifies the remote object in the registry. The code then needs to add the name to the RMI registry running on the server. This is done later (within thetryblock) with the statementNaming.rebind(name, engine);Calling the
rebindmethod makes a remote call to the RMI registry on the local host. This call can result in aRemoteExceptionbeing generated, so the exception needs to be handled. TheComputeEngineclass handles the exception within thetry/catchblock. If the exception is not handled in this way,RemoteExceptionwould have to be added to thethrowsclause (currently nonexistent) of themainmethod.Note the following about the arguments to the call to
Naming.rebind.
- The first parameter is a URL-formatted
java.lang.Stringrepresenting the location and the name of the remote object. You will need to change the value ofhostto be the name, or IP address, of your server machine. If the host is omitted from the URL, the host defaults to the local host. Also, you don't need to specify a protocol in the URL. For example, supplyingComputeas the name in theNaming.rebindcall is allowed. Optionally a port number may be supplied in the URL; for example, the name//host:1234/objectnameis legal. If the port is omitted, it defaults to 1099. You must specify the port number only if a server creates a registry on a port other than the default 1099. The default port is useful in that it provides a well-known place to look for the remote objects that offer services on a particular host.- The RMI runtime substitutes a reference to the stub for the remote object reference specified by the argument. Remote implementation objects, such as instances of
ComputeEngine,never leave the VM where they are created, so when a client performs a lookup in a server's remote object registry, a reference to the stub is returned. As discussed earlier, remote objects in such cases are passed by reference rather than by value.- Note that for security reasons, an application can
bind,unbind, orrebindremote object references only with a registry running on the same host. This restriction prevents a remote client from removing or overwriting any of the entries in a server's registry. Alookup, however, can be requested from any host, local or remote.Once the server has registered with the local RMI registry, it prints out a message indicating that it's ready to start handling calls and then the
mainmethod exits. It is not necessary to have a thread wait to keep the server alive. As long as there is a reference to theComputeEngineobject in another virtual machine, local or remote, theComputeEngineobject will not be shut down, or garbage collected. Because the program binds a reference to theComputeEnginein the registry, it is reachable from a remote client, the registry itself! The RMI system takes care of keeping theComputeEngine's process up. TheComputeEngineis available to accept calls and won't be reclaimed until its binding is removed from the registry, and no remote clients hold a remote reference to theComputeEngineobject.The final piece of code in the
ComputeEngine.mainmethod deals with handling any exception that might arise. The only exception that could be thrown in the code is aRemoteException, thrown either by the constructor of theComputeEngineclass or by the call to the RMI registry to bind the object to the nameCompute. In either case the program can't do much more than exit after printing an error message. In some distributed applications it is possible to recover from the failure to make a remote call. For example, the application could choose another server and continue operation.
The compute engine is a pretty simple program: it runs tasks that are handed to it. The clients for the compute engine are more complex. A client needs to call the compute engine, but it also has to define the task to be performed by the compute engine.Two separate classes make up the client in our example. The first class,
ComputePi, looks up and calls aComputeobject. The second class,Pi, implements theTaskinterface and defines the work to be done by the compute engine. The job of thePiclass is to compute the value ofto some number of decimal places.
As you recall, the nonremote
interface is defined as follows:Taskpackage compute; public interface Task extends java.io.Serializable { Object execute(); }The
Taskinterface extendsjava.io.Serializableso that an object that implements the interface can be serialized by the RMI runtime and sent to a remote virtual machine as part of a remote method invocation. We could have chosen to have our implementation classes implement both theTaskinterface and theSerializableinterface and gotten the same effect. However, the whole purpose of theTaskinterface is to allow implementations of that interface to be passed to aComputeobject, so having a class that implements theTaskinterface that does not also implement theSerializableinterface doesn't make sense. Therefore we associate the two interfaces explicitly in the type system, ensuring that allTaskobjects are serializable.The code that calls a
Computeobject's methods must obtain a reference to that object, create aTaskobject, and then request that the task be executed. The definition of the taskPiis shown later. APiobject is constructed with a single argument, the desired precision of the result. The result of the task execution is ajava.math.BigDecimalrepresentingcalculated to the specified precision.
The client class
is as follows.client.ComputePipackage client; import java.rmi.*; import java.math.*; import compute.*; public class ComputePi { public static void main(String args[]) { if (System.getSecurityManager() == null) { System.setSecurityManager(new RMISecurityManager()); } try { String name = "//" + args[0] + "/Compute"; Compute comp = (Compute) Naming.lookup(name); Pi task = new Pi(Integer.parseInt(args[1])); BigDecimal pi = (BigDecimal) (comp.executeTask(task)); System.out.println(pi); } catch (Exception e) { System.err.println("ComputePi exception: " + e.getMessage()); e.printStackTrace(); } } }Like the
ComputeEngineserver, the client begins by installing a security manager. This is necessary because RMI could be downloading code to the client. In this example theComputeEngine's stub is downloaded to the client. Any time code is downloaded by RMI, a security manager must be present. As with the server, the client uses the security manager provided by the RMI system for this purpose.After installing a security manager, the client constructs a name used to look up a
Computeremote object. The value of the first command line argument,args[0], is the name of the remote host on which theComputeobject runs. The client uses theNaming.lookupmethod to look up the remote object by name in the remote host's registry. When doing the name lookup, the code creates a URL that specifies the host where the compute server is running. The name passed in theNaming.lookupcall has the same URL syntax as the name passed in theNaming.rebindcall, which was discussed earlier.Next, the client creates a new
Piobject, passing to thePiconstructor the second command line argument,args[1], which indicates the number of decimal places to use in the calculation. Finally, the client invokes theexecuteTaskmethod of theComputeremote object. The object passed into theexecuteTaskcall returns an object of typejava.math.BigDecimal, so the program casts the result to that type and stores the return value in the variableresult. Then, the program prints out the result. The following figure depicts the flow of messages among theComputePiclient, thermiregistry, and theComputeEngine.
Finally, let's look at the reason for all of this in the first place: the
Piclass. This class implements theTaskinterface and computes the value ofto a specified number of decimal places. For this example the actual algorithm is unimportant except, of course, for the accuracy of the computation. All that is important is that the computation is numerically rather expensive and thus the sort of thing that you would want to have occur on a more capable server.
Here is the code for the class
client.Pi, which implementsTask.package client; import compute.*; import java.math.*; public class Pi implements Task { /** constants used in pi computation */ private static final BigDecimal ZERO = BigDecimal.valueOf(0); private static final BigDecimal ONE = BigDecimal.valueOf(1); private static final BigDecimal FOUR = BigDecimal.valueOf(4); /** rounding mode to use during pi computation */ private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN; /** digits of precision after the decimal point */ private int digits; /** * Construct a task to calculate pi to the specified * precision. */ public Pi(int digits) { this.digits = digits; } /** * Calculate pi. */ public Object execute() { return computePi(digits); } /** * Compute the value of pi to the specified number of * digits after the decimal point. The value is * computed using Machin's formula: * * pi/4 = 4*arctan(1/5) - arctan(1/239) * * and a power series expansion of arctan(x) to * sufficient precision. */ public static BigDecimal computePi(int digits) { int scale = digits + 5; BigDecimal arctan1_5 = arctan(5, scale); BigDecimal arctan1_239 = arctan(239, scale); BigDecimal pi = arctan1_5.multiply(FOUR).subtract( arctan1_239).multiply(FOUR); return pi.setScale(digits, BigDecimal.ROUND_HALF_UP); } /** * Compute the value, in radians, of the arctangent of * the inverse of the supplied integer to the speficied * number of digits after the decimal point. The value * is computed using the power series expansion for the * arc tangent: * * arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 + * (x^9)/9 ... */ public static BigDecimal arctan(int inverseX, int scale) { BigDecimal result, numer, term; BigDecimal invX = BigDecimal.valueOf(inverseX); BigDecimal invX2 = BigDecimal.valueOf(inverseX * inverseX); numer = ONE.divide(invX, scale, roundingMode); result = numer; int i = 1; do { numer = numer.divide(invX2, scale, roundingMode); int denom = 2 * i + 1; term = numer.divide(BigDecimal.valueOf(denom), scale, roundingMode); if ((i % 2) != 0) { result = result.subtract(term); } else { result = result.add(term); } i++; } while (term.compareTo(ZERO) != 0); return result; } }The most interesting feature of this example is that the
Computeobject never needsPi's class definition until aPiobject is passed in as an argument to theexecuteTaskmethod. At that point the code for the class is loaded by RMI into theComputeobject's virtual machine, theexecutemethod is called, and the task's code is executed. The resultingObject, which in the case of thePitask is ajava.math.BigDecimalobject, is handed back to the calling client, where it is used to print out the result of the calculation.The fact that the supplied
Taskobject computes the value ofPiis irrelevant to theComputeEngineobject. You could also implement a task that, for example, generated a random prime number by using a probabilistic algorithm. That would also be numerically intensive and therefore a candidate for being shipped over to theComputeEngine, but it would involve very different code. This code could also be downloaded when theTaskobject was passed to aComputeobject. In just the way that the algorithm for computingPiis brought in when needed, the code that generates the random prime would be brought in when needed. TheComputeobject knows only that each object it receives implements theexecutemethod; it does not know, and does not need to know, what the implementation does.
In a real-world scenario in which a service like the compute engine is deployed, a developer would likely create a JAR (Java ARchive) file that contains theComputeandTaskinterfaces for server classes to implement and client program to use. Next, a developer, perhaps the same developer of the interface JAR file, would write an implementation of theComputeinterface and deploy that service on a machine available to clients. Developers of client programs can use theComputeand theTaskinterfaces, contained in the JAR file, and independently develop a task and client program that uses aComputeservice.In this section you learn how to set up the JAR file, server classes, and client classes. You will see that the client's
Piclass will be downloaded to the server at runtime. Also, theComputeEngine's remote stub will be downloaded from the server to the client at runtime.The example separates the interfaces, remote object implementation, and client code into three packages:
compute(ComputeandTaskinterfaces)engine(ComputeEngineimplementation class and its stub)client(ComputePiclient code andPitask implementation)Let's first build the interface JAR file to provide to server and client developers.
First, you need to compile the interface source files in thecomputepackage and then build a JAR file that contains their class files. Let's suppose a user,waldo, has written these particular interfaces and has placed the source files inc:\home\waldo\src\compute(on UNIX:/home/waldo/src/compute). Given these paths, you can use the following commands to compile the interfaces and create the JAR file.
Win32: cd c:\home\waldo\src javac compute\Compute.java javac compute\Task.java jar cvf compute.jar compute\*.classUNIX: cd /home/waldo/src javac compute/Compute.java javac compute/Task.java jar cvf compute.jar compute/*.class
The
jarcommand displays the following output (due to the-voption):added manifest adding: compute/Compute.class (in=281) (out=196) (deflated 30%) adding: compute/Task.class (in=200) (out=164) (deflated 18%)Now you can distribute the
compute.jarfile to developers of server and client applications so that they can make use of the interfaces.When you build either server- or client-side classes with the
javacandrmiccompilers, you generally need to specify where the resulting class files should reside so that they are network accessible. In this example this location is, for UNIX,/home/user/public_html/classes, because some web servers allow accessing a user'spublic_htmldirectory via an HTTP URL constructed ashttp://host/~user/. If your web server does not support this convention, you could use a file URL instead. The file URLs take the formfile:/home/user/public_html/classes/on UNIX or, on the Win32 platform,file:/c:/home/user/public_html/classes/. You may also select another type of URL, as appropriate.The network accessibility of the class files allows the RMI runtime to download code when needed. Rather than defining its own protocol for code downloading, RMI uses URL protocols supported by the Java platform (for example, HTTP) to download code. Note that a full, heavyweight web server is not needed to accomplish this downloading of class files. In fact, a simple HTTP server that provides all of the functionality needed to make classes available for downloading in RMI via HTTP can be found at ftp://ftp.javasoft.com/pub/jdk1.1/rmi/class-server.zip.
Theenginepackage contains only one server-side implementation class,Com-puteEngine, the remote object implementation of theComputeinterface. SinceComputeEngineis an implementation of a remote interface, you need to generate a stub for the remote object so that clients can contact the remote object.Let's say that
ann, the developer of theComputeEngineclass, has placedComputeEngine.javain thec:\home\ann\src\enginedirectory and is deploying the class files for clients to use in a subdirectory of herpublic_htmldirectory,c:\home\ann\public_html\classes(on UNIX that would be/home/ann/public_html/classes, accessible via some web servers ashttp://host/~ann/classes/).Now let's assume that the
compute.jarfile is located in the directoryc:\home\ann\public_html\classes. To compile theComputeEngineclass, your class path must include thecompute.jarfile and the source directory itself.
Note: Normally we recommend that you set the class path on the command line, using the-classpathoption. However, for several compounding reasons this example uses theCLASSPATHenvironment variable, because bothjavacandrmicrequire a class path and the-classpathoption is treated differently in JDK 1.1 and JDK 1.2. For detailed information onCLASSPATHrefer to:
Solaris:
http://java.sun.com/products/jdk/1.2/docs/tooldocs/solaris/classpath.html
Win32:
http://java.sun.com/products/jdk/1.2/docs/tooldocs/win32/classpath.htmlWe recommend that you do not set
CLASSPATHin a login or startup file and that you remember to unset it when you're finished working with this example.
Here's how to set the
CLASSPATHenvironment variable
Win32: set CLASSPATH=c:\home\ann\src;c:\home\ann\public_html\classes\compute.jarUNIX: setenv CLASSPATH /home/ann/src:/home/ann/public_html/classes/compute.jar
Now you compile the
ComputeEngine.javasource file, generate a stub for theComputeEngineclass, and make that stub network accessible. To create stub (and optionally skeleton files), run thermiccompiler on the fully qualified class names of the remote object implementations that must be found in the class path. Thermiccommand takes one or more class names as input and produces as output class files of the formClassName_Stub.classandClassName_Skel.class. A skeleton file will not be generated if you runrmicwith the-v1.2option. This option should be used only if all of your clients will be running JDK 1.2 or compatible versions.
Win32: cd c:\home\ann\src javac engine\ComputeEngine.java rmic -d . engine.ComputeEngine mkdir c:\home\ann\public_html\classes\engine cp engine\ComputeEngine_*.class c:\home\ann\public_html\classes\engineUNIX: cd /home/ann/src javac engine/ComputeEngine.java rmic -d . engine.ComputeEngine mkdir /home/ann/public_html/classes/engine cp engine/ComputeEngine_*.class /home/ann/public_html/classes/engine
The
-doption tells thermiccompiler to place the generated class files,ComputeEngine_Stub.classandComputeEngine_Skel.class, in the directory c:\home\ann\src\engine. You also need to make the stubs and the skeletons network accessible, so you must copy the stub and the skeleton class to the area:public_html\classes.Since the
ComputeEngine's stub implements theComputeinterface, which refers to theTaskinterface, you need to make these two interface class files network accessible along with the stub. So the final step is to unpack thecompute.jarfile in the directoryc:\home\ann\public_html\classesto make theComputeand theTaskinterfaces available for downloading.
Win32: cd c:\home\ann\public_html\classes jar xvf compute.jarUNIX: cd /home/ann/public_html/classes jar xvf compute.jar
The
jarcommand displays the following output:Now the compute engine is ready to deploy. You could do that now or wait until after you have built the client. While we are on a building spree, let's build the client-side program next.created: META-INF/ extracted: META-INF/MANIFEST.MF extracted: compute/Compute.class extracted: compute/Task.class
Let's assume that userjoneshas created the client code in the directoryc:\home\jones\src\clientand will deploy thePiclass, so that it can be downloaded to the compute engine, in the network-accessible directoryc:\home\jones\public_html\classes, also available via some web servers ashttp://host/~jones/classes/. The two client-side classes are contained in the filesPi.javaandComputePi.javain theclientsubdirectory.In order to build the client code, you need the
compute.jarfile that contains theComputeand theTaskinterfaces that the client uses. Let's say that thecompute.jarfile is located inc:\home\jones\public_html\classes. The client classes can be built as follows:
Win32: set CLASSPATH=c:\home\jones\src;c:\home\jones\public_html\classes\compute.jar cd c:\home\jones\src javac client\ComputePi.java javac -d c:\home\jones\public_html\classes client\Pi.javaUNIX: setenv CLASSPATH /home/jones/src:/home/jones/public_html/classes/compute.jar cd /home/jones/src javac client/ComputePi.java javac -d /home/jones/public_html/classes client/Pi.java
Only the
Piclass needs to be placed in the directorypublic_html\classes\. (The
clientclientdirectory is created byjavacif it does not exist.) The reason is that only thePiclass needs to be available for downloading to the compute engine's virtual machine. Now you can run the server and then the client.
The JDK 1.2 security model is more sophisticated than the model used for JDK 1.1. JDK 1.2 contains enhancements for finer-grained security and requires code to be granted specific permissions to be allowed to perform certain operations.In JDK 1.1 code in the class path is trusted and can perform any operation; downloaded code is governed by the rules of the installed security manager. If you run this example in JDK 1.2, you need to specify a policy file when you run your server and client. Here is a general policy file that allows downloaded code, from any code base, to do two things:
- Connect to or accept connections on unprivileged ports (ports greater than 1024) on any host
- Connect to port 80 (the port for HTTP)
Here is the code for the general policy file:
grant { permission java.net.SocketPermission "*:1024-65535", "connect,accept"; permission java.net.SocketPermission "*:80", "connect"; };If you make your code available for downloading via HTTP URLs, you should use the preceding policy file when you run this example. However, if you use file URLs instead, you can use the following policy file. Note that in Windows-style file names, the backslash character needs to be represented by two backslash characters in the policy file.
grant { permission java.net.SocketPermission "*:1024-65535", "connect,accept"; permission java.io.FilePermission "c:\\home\\ann\\public_html\\classes\\-", "read"; permission java.io.FilePermission "c:\\home\\jones\\public_html\\classes\\-", "read"; };This example assumes that the policy file is called
java.policyand that it contains the appropriate permissions. If you run this example on JDK 1.1, you will not need to use a policy file, since theRMISecurityManagerprovides all of the protection you need.
Before starting the compute engine, you need to start RMI's registry, using thermiregistrycommand. As discussed earlier, the RMI registry is a simple server-side bootstrap naming facility that allows remote clients to get a reference to a remote object. Note that before you start thermiregistry, you must make sure that the shell or window in which you will runrmiregistryeither has noCLASSPATHenvironment variable set or has aCLASSPATHenvironment variable that does not include the path to any classes, including the stubs for your remote object implementation classes, that you want downloaded to clients of your remote objects.If you do start the
rmiregistryand it can find your stub classes inCLASSPATH, it will not remember that the loaded stub class can be loaded from your server's code base, specified by thejava.rmi.server.codebaseproperty when you started up your server application. Therefore, thermiregistrywill not convey to clients the true code base associated with the stub class and, consequently, your clients will not be able to locate and to load the stub class or other server-side classes.To start the registry on the server, execute the
rmiregistrycommand. This command produces no output and is typically run in the background. For this example, we will start the registry on the hostzaphod.
Win32 (use javawifstartis not available): unset CLASSPATH start rmiregistryUNIX: unsetenv CLASSPATH rmiregistry &
By default, the registry runs on port 1099. To start the registry on a different port, specify the port number on the command line. Do not forget to unset your
CLASSPATH.
Win32: start rmiregistry 2001UNIX: rmiregistry 2001 &
Once the registry is started, you can start the server. First, you need to make sure that both the
compute.jarfile and the remote object implementation class (since that is what you are starting) are in your class path.
Win32: set CLASSPATH=c:\home\ann\src;c:\home\ann\public_html\classes\compute.jarUNIX: setenv CLASSPATH /home/ann/src:/home/ann/public_html/classes/compute.jar
When you start the compute engine, you need to specify, using the
java.rmi.server.codebaseproperty, where the server's classes will be made available. In this example the server-side classes to be made available for downloading are theComputeEngine's stub and theComputeand theTaskinterfaces, available inann'spublic_html\classesdirectory. Here, we start the compute engine server on the hostzaphod, the same host where we started the registry.
Win32: java -Djava.rmi.server.codebase=file:/c:\home\ann\public_html\classes/ -Djava.rmi.server.hostname=zaphod.east.sun.com -Djava.security.policy=java.policy engine.ComputeEngineUNIX: java -Djava.rmi.server.codebase=http://zaphod/~ann/classes/ -Djava.rmi.server.hostname=zaphod.east.sun.com -Djava.security.policy=java.policy engine.ComputeEngine
The preceding
javacommand defines several properties.
- The
java.rmi.server.codebaseproperty specifies the location, a code base URL, of classes originating from this server so that class information for objects sent to other virtual machines will include the location of the class so that a receiver can load it. If the code base specifies a directory (as opposed to a JAR file), you must include the trailing slash in the code base URL.- The
java.rmi.server.hostnameproperty indicates the fully qualified host name of your server. In some networked environments a fully qualified host name is not obtainable by using the Java APIs. RMI makes a best-effort attempt to obtain the fully qualified host name. If one cannot be determined, it will fall back and use the IP address. To ensure that RMI will use a host name that is usable from potential clients, you may want to set thejava.rmi.server.hostnameproperty as a safety measure.- The
java.security.policyproperty is used to specify the policy file that contains the permissions you intend to grant specific code bases.The
ComputeEngine's stub class is dynamically loaded into a client's virtual machine only when the class is not already available locally and thejava.rmi.server.codebase property has been set properly, to the network-accessible location of the stub class, when the server is started. Once such a stub is loaded, it will not need to be reloaded for additional references toCom-puteEngine's objects.
Once the registry and the engine are running, you can start the client, specifying
- The location where the client serves up its classes (the
Piclass), using thejava.rmi.server.codebaseproperty- As command line arguments the host name of the server--so that the client knows where to locate the
Computeremote object--and the number of decimal places to use in thecalculation
- The
java.security.policyproperty, used to specify the policy file that contains the permissions you intend to grant specific code basesFirst, set the
CLASSPATHto seejones's client and the JAR file containing the interfaces. Then start the client on another host (one namedford, for example) as follows:
Win32: set CLASSPATH c:\home\jones\src;c:\home\jones\public_html\classes\compute.jar java -Djava.rmi.server.codebase=file:/c:\home\jones\public_html\classes/ -Djava.security.policy=java.policy client.ComputePi zaphod.east.sun.com 20UNIX: setenv CLASSPATH /home/jones/src:/home/jones/public_html/classes/compute.jar java -Djava.rmi.server.codebase=http://ford/~jones/classes/ -Djava.security.policy=java.policy client.ComputePi zaphod.east.sun.com 20
Note that the class path is set on the command line so that the interpreter can find
jones's client and the JAR file containing the interfaces.After starting the client, you should see the following output on your display:
3.14159265358979323846The following figure illustrates where the
rmiregistry, theComputeEngineserver, and theComputePiclient obtain classes during program execution.When the
ComputeEngineserver binds its remote object reference in the registry, the registry downloads theComputeEngine_Stub, as well as theComputeand theTaskinterfaces on which the stub class depends. These classes are downloaded from theComputeEngine's web server or file system, as the case may be.The
ComputePiclient loads theComputeEngine_Stub, also from theCompute-Engine's web server, as a result of theNaming.lookupcall. Since theComputePiclient has both theComputeand theTaskinterfaces available in its class path, those classes are loaded from the class path, not the remote location.
Finally, the
Piclass is loaded into theComputeEngine's virtual machine when thePiobject is passed in theexecuteTaskremote call to theComputeEngineobject. ThePiclass is loaded from the client's web server.
| Start of Tutorial > Start of Trail |