JAVA RMI - 0fees.net

Download Report

Transcript JAVA RMI - 0fees.net

Java
Remote Method Invocation
Distributed Computing with
RMI




Remote Method Invocation (RMI) technology,
First introduced in JDK 1.1
Elevates network programming to a higher
plane.
The world of distributed object computing.
Goals


To use the same syntax and semantics used
for non-distributed programs in the distributed
programs
This is done by carefully mapping Java
classes and objects to work in a distributed
(multiple JVM) computing environment.
Comparison
Local Object
Object Definition
A local object is defined by
a Java class.
Remote Object
A remote object's exported
behavior is defined by an
interface that must extend the
Remote interface.
Object Implementation
Object Creation
A local object is
implemented by its Java
class.
A remote object's behavior is
executed by a Java class that
implements the remote interface.
A new instance of a local
object is created by the new
A new instance of a remote
object is created on the host
computer with the new operator.
A client cannot directly create a
new remote object (unless using
Java 2 Remote Object
.
operator
activation).
Object Access
A local object is
accessed directly via an
object reference
variable.
A remote object is accessed
via an object reference
variable which points to a
proxy stub implementation
of the remote interface.
Comparison
References
In a single JVM, an object
reference points directly at an
object in the heap.
A "remote reference" is a pointer to a
proxy object (a "stub") in the local
heap. That stub contains information
that allows it to connect to a remote
object, which contains the
implementation of the methods.
Active References
In a single JVM, an object is considered
"alive" if there is at least one reference to it.
In a distributed environment, remote JVMs
may crash, and network connections may be
lost. A remote object is considered to have an
active remote reference to it if it has been
accessed within a certain time period (the
lease period). If all remote references have
been explicitly dropped, or if all remote
references have expired leases, then a
remote object is available for distributed
garbage collection.
Finalization
If an object implements the finalize() method,
it is called before an object is reclaimed by
the garbage collector.
If a remote object implements
the Unreferenced interface, the
unreferenced method of that
interface is called when all
remote references have been
dropped.
Garbage Collection
When all local references to an
object have been dropped, an
object becomes a candidate for
garbage collection.
The distributed garbage collector works with
the local garbage collector. If there are no
remote references and all local references to
a remote object have been dropped, then it
becomes a candidate for garbage collection
through the normal means.
Exceptions
Exceptions are either Runtime
exceptions or Exceptions. The Java
compiler forces a program to handle
all Exceptions.
RMI forces programs to deal with any
possible RemoteException objects that may
be thrown. This was done to ensure the
robustness of distributed applications.
Interfaces: The Heart of RMI







Important principle on which RMI is based:
The definition of behavior and the implementation of that behavior
are separate concepts the code for this is also separate and runs on
separate JVMs.
This way it fits with the needs of a distributed system where clients
are concerned about the definition of a service
Servers are focused on providing the service.
In RMI, the definition of a remote service is coded using a Java
interface.
The implementation of the remote service is coded in a class.
Therefore, the key to understanding RMI is to remember that
interfaces define behavior and classes define implementation.
RMI architecture



The first class is the implementation of the
behavior, and it runs on the server.
The second class acts as a proxy for the
remote service and it runs on the client.
This is shown in the diagram.
RMI architecture
RMI architecture




Java RMI is comprised of three layers that
support the interface
The first layer is the Stub/Skeleton Layer.
The second layer is the Remote Reference
Layer (RRL).
The third layer is the transport layer..
The RMI architecture
Stub and Skeleton Layer




This layer is responsible for managing the remote
object interface between the client and server.
The stub and skeleton layer of RMI lie just beneath
the view of the Java developer. In this layer, RMI
uses the Proxy design pattern.
In the Proxy pattern, an object in one context is
represented by another (the proxy) in a separate
context.
The proxy knows how to forward method calls
between the participating objects. The following
class diagram illustrates the Proxy pattern.
Stub & Skeleton layer
Stub & Skeleton layer




In RMI's use of the Proxy pattern, the stub class plays the role of
the proxy, and the remote service implementation class plays the
role of the RealSubject.
A skeleton is a helper class that is generated for RMI to use. The
skeleton understands how to communicate with the stub across
the RMI link. The skeleton carries on a conversation with the
stub; it reads the parameters for the method call from the link,
makes the call to the remote service implementation object,
accepts the return value, and then writes the return value back to
the stub.
RMI uses reflection to make the connection to the remote service
object.
Skeleton classes and objects are compatible in in JDK 1.1 and
JDK 1.1 system implementations.
Remote Reference Layer




This layer is responsible for managing the "liveliness" of the remote objects.
It also manages the communication between the client/server and virtual
machine s, (e.g., threading, garbage collection, etc.) for remote objects.
The Remote Reference Layers defines and supports the invocation
semantics of the RMI connection. This layer provides a RemoteRef object
that represents the link to the remote service implementation object.
The stub objects use the invoke() method in RemoteRef to forward the
method call. The RemoteRef object understands the invocation semantics
for remote services.
The JDK 1.1 implementation of RMI provides only one way for clients to
connect to remote service implementations: a unicast, point-to-point
connection. Before a client can use a remote service, the remote service
must be instantiated on the server and exported to the RMI system. (If it is
the primary service, it must also be named and registered in the RMI
Registry).
Remote reference layer


The Java 2 SDK implementation of RMI adds a new semantic for
the client-server connection. In this version, RMI supports
activatable remote objects. When a method call is made to the
proxy for an activatable object, RMI determines if the remote
service implementation object is dormant. If it is dormant, RMI
will instantiate the object and restore its state from a disk file.
Once an activatable object is in memory, it behaves just like JDK
1.1 remote service implementation objects.
Other types of connection semantics are possible. For example,
with multicast, a single proxy could send a method request to
multiple implementations simultaneously and accept the first
reply (this improves response time and possibly improves
availability). In the future, Sun may add additional invocation
semantics to RMI.
Transport Layer





This is the actual network/communication layer that is used to send
the information between the client and server over the wire. It is
currently TCP/IP based. If you are familiar with RPC, it is a UDPbased protocol which is fast but is stateless and can lose packets.
TCP is a higher-level protocol that manages state and error
correction automatically, but it is correspondingly slower than UDP
The Transport Layer makes the connection between JVMs. All
connections are stream-based network connections that use
TCP/IP.
Even if two JVMs are running on the same physical computer,
they connect through their host computer's TCP/IP network
protocol stack.
You will need an operational TCP/IP configuration on your
computer to run the Exercises.
The following diagram shows the use of TCP/IP connections
between JVMs.
Transport layer
Transport Layer





TCP/IP provides a persistent, stream-based connection between two machines based on
an IP address and port number at each end. Usually a DNS name is used instead of an IP
address; this means you could talk about a TCP/IP connection between
flicka.magelang.com:3452 and rosa.jguru.com:4432. In the current release of RMI, TCP/IP
connections are used as the foundation for all machine-to-machine connections.
On top of TCP/IP, RMI uses a wire level protocol called Java Remote Method Protocol
(JRMP). JRMP is a proprietary, stream-based protocol that is only partially specified is now
in two versions.
The first version was released with the JDK 1.1 version of RMI and required the use of
Skeleton classes on the server.
The second version was released with the Java 2 SDK. It has been optimized for
performance and does not require skeleton classes. Some other changes with the Java 2
SDK are that RMI service interfaces are not required to extend from java.rmi.Remote and
their service methods do not necessarily throw RemoteException.
Sun and IBM have jointly worked on the next version of RMI, called RMI-IIOP, which will be
available with Java 2 SDK Version 1.3. The interesting thing about RMI-IIOP is that instead
of using JRMP, it will use the Object Management Group (OMG) Internet Inter-ORB
Protocol, IIOP, to communicate between clients and servers.
Transport layer









The OMG is a group of more than 800 members that defines a vendor-neutral, distributed
object architecture called Common Object Request Broker Architecture (CORBA).
CORBA Object Request Broker (ORB) clients and servers communicate with each other
using IIOP. With the adoption of the Objects-by-Value extension to CORBA and the Java
Language to IDL Mapping proposal, the ground work was set for direct RMI to CORBA
integration. This new RMI-IIOP implementation supports most of the RMI feature set,
except for:
java.rmi.server.RMISocketFactory
UnicastRemoteObject
Unreferenced
The DGC interfaces
The RMI transport layer is designed to make a connection between clients and server,
even in the face of networking obstacles.
While the transport layer prefers to use multiple TCP/IP connections, some network
configurations only allow a single TCP/IP connection between a client and server (some
browsers restrict applets to a single network connection back to their hosting server).
In this case, the transport layer multiplexes multiple virtual connections within a single
TCP/IP connection.
Naming Remote Objects



During the presentation of the RMI Architecture, one question has been
repeatedly postponed: "How does a client find an RMI remote service?
" Now you'll find the answer to that question. Clients find remote
services by using a naming or directory service. This may seem like
circular logic. How can a client locate a service by using a service? In
fact, that is exactly the case. A naming or directory service is run on a
well-known host and port number.
RMI can use many different directory services, including the Java
Naming and Directory Interface (JNDI). RMI itself includes a simple
service called the RMI Registry, rmiregistry. The RMI Registry runs on
each machine that hosts remote service objects and accepts queries for
services, by default on port 1099.
On a host machine, a server program creates a remote service by first
creating a local object that implements that service. Next, it exports that
object to RMI. When the object is exported, RMI creates a listening
service that waits for clients to connect and request the service. After
exporting, the server registers the object in the RMI Registry under a
public name.
Naming Remote Objects



On the client side, the RMI Registry is accessed through the
static class Naming. It provides the method lookup() that a client
uses to query a registry. The method lookup() accepts a URL that
specifies the server host name and the name of the desired
service. The method returns a remote reference to the service
object. The URL takes the form:
rmi://<host_name> [:<name_service_port>] /<service_name>
where the host_name is a name recognized on the local area
network (LAN) or a DNS name on the Internet. The
name_service_port only needs to be specified only if the naming
service is running on a different port to the default 1099.
Using RMI









It is now time to build a working RMI system and get hands-on experience.
A working RMI system is composed of several parts.
Interface definitions for the remote services
Implementations of the remote services
Stub and Skeleton files
A server to host the remote services
An RMI Naming service that allows clients to find the remote services
A class file provider (an HTTP or FTP server)
A client program that needs the remote services
Assuming that the RMI system is already designed, you take the following steps to build a
system:

Write and compile Java code for interfaces

Write and compile Java code for implementation classes

Generate Stub and Skeleton class files from the implementation classes

Write Java code for a remote service host program

Develop Java code for RMI client program

Install and run RMI system
Example of RMI






The example used in this article is an amortization schedule
application.
The client requests a local schedule object from the server
through a remote object and passes an amount and duration of a
loan to the server.
The server instantiates a local schedule object with the amount
and duration along with the interest rate the server knows about.
Then the schedule object is serialized and returned back to the
client.
The client can then print the object or modify it at this point. The
client has its own private copy of the schedule object.
There is an illustration that serves as a reference for the parts of
the RMI application.
Example of RMI program
Creating the Interface Definitions
File


The first thing that you must do to develop an RMI
application is to define the remote interface. The
interface defines what remote methods/variables are
going to be exported from the remote object. Usually
the interface defines methods only because
variables have to be declared final (i.e., constant) if
they are in an interface definition.
The remote interface needs to import the RMI
package, and every exported method must throw an
RMI remote exception to manage errors during
invocation. Below is the code for the mathCalc.java
interface definition file in our example.
Example
/**************************************************** * module:
mathCalc.java ****************************************************/
import java.lang.*;
import java.rmi.*;
public interface mathCalc extends java.rmi.Remote
{
public schedule amortizeSchedule( float ammount, int duration )
throws java.rmi.RemoteException;
public void printRate() throws java.rmi.RemoteException;
}
If you are familiar with using Java and interfaces, converting your local objects
to remote objects can be done very quickly with minor modifications to your
source. You need to include the Java RMI package and manage RMI
remote exceptions on all your exported local methods.
Creating the Interface
Implementation File


Once the interface definition file is created,
you need to define the actual code that
supports the interface on the server.
Below is an example of the mathCalcImp.java
interface implementation file used to provide
that support.
Example
/**************************************************** * module: mathCalcImp.java
****************************************************/
import java.rmi.*;
import java.rmi.server.*;
class mathCalcImp extends UnicastRemoteObject implements mathCalc {
float interestRate = (float)7.5;
mathCalcImp() throws java.rmi.RemoteException {
}
public schedule amortizeSchedule( float ammount, int duration ) throws
java.rmi.RemoteException
{
System.out.println("Amortizeing Schedule.");
return( new schedule( interestRate, ammount, duration ) );
}
public void printRate() throws java.rmi.RemoteException
{
System.out.println("Current Interest Rate is " + interestRate );
}
}
Example





Notice the implementation file imports the package java.rmi.*. It
also imports the java.rmi.server.* package.
This is so you can extend the UnicastRemoteObject class to
support remote clients.
This class manages client/server and peer/peer connection
support for RMI.
Today there is no MulticastRemoteObject class, but it should
appear in some JDK 1.2 release. There is, however, enough
support in JDK 1.1 to allow you to write your own
MulticastRemoteObject class to support multicast remote clients.
Notice how the file defined above implements mathCalc, the
remote interface definition that was defined earlier. Each method
in the implementation file that is going to be externalized needs
to throw a remote exception.
Object Serialization


The amortizeSchedule() method prints a
message on the server and instantiates a
new local schedule object that is returned to
the client. The schedule object is a local
object that will be serialized and marshaled
into a data stream to be sent back to the
client.
Now the serialization of remote objects is the
next step. To begin that, the
schedule.java.local class is presented below.
/***************************************************
* * module: schedule.java
************************************************
****/
import java.lang.*;
import java.util.*;
import java.io.*;
class schedule implements Serializable {
void print() {
float totalLoanAmt;
System.out.println("Schedule Created.");
float usrAmmount;
System.out.println("Calculation information based on:");
float interestRate;
System.out.println(" Rate [%" + interestRate + "]" );
int loanDuration;
System.out.println(" Ammount [$" + usrAmmount + "]" );
System.out.println(" Duration [ " + loanDuration + "]" );
schedule( float rate, float ammount, int
System.out.println(" Total Loan [$" + totalLoanAmt + "]" );
duration )
int couponNum = 0;
{ interestRate = rate;
float balanceRemaining =
usrAmmount = ammount;
totalLoanAmt; float monthlyPayment = 0;
loanDuration = duration;
System.out.println();
totalLoanAmt = ammount + (ammount / rate);System.out.println( "Payment Monthly Payment Ammount
Balance Remaining");
}
System.out.println( "------- ----------------------- -----------------");
while( balanceRemaining > 0 )
{ couponNum++; monthlyPayment = totalLoanAmt/loanDuration;
if( balanceRemaining < monthlyPayment )
{ monthlyPayment = balanceRemaining;
balanceRemaining = 0; }
else {
balanceRemaining = balanceRemaining - monthlyPayment;
}
System.out.println( couponNum + " "
+ monthlyPayment + " “ + balanceRemaining );
} /while
} /print
}
Object Serialization






If you are passing local objects around through remote interfaces, you have to
make the defining local class serializable.
Notice that the schedule class implements the serializable interface, but it does
not have to provide any code.
This is because Java manages the serialization of serializable interfaces for you.
If we were to implement externalizable instead of serializable, then the
schedule.java class would have to provide the serialize/deserialize methods.
This would require the schedule class to serialize and deserialize its own data. If
you try to pass a local object that has not implemented the
serializeable/externalizeable interface,
Java will throw a marshaling exception on the server/client. Note: Be careful
when marking a class serializable, because Java will try to "flatten" everything
related to that cl., inheritance classes, instances within the class, etc.).
As an example, I would not recommend trying to serialize anything like the root
drive on your disk. There is also a lot of overhead involved in the
serialization/deserialization process. Use serialization with care.
Creating the Stubs/Skeletons






Now that the interface and implementation files have been created, you need to generate
the stubs and skeleton code.
This is done by using the rmic compiler provided by the JDK.
The following command will generate the stub and skeleton .class files, but it will not create
the .java files.
If you want to see the Java-generated code, use the -keepgenerated option.
This will leave the .java files files around, but don't try to modify these files.
The rmic compiler also has a -show option that runs it as an AWT application.
Command Line > rmic mathCalcImp



After running the rmic compiler, you should see mathCalcImp_Skel.class and
mathCalcImp_Stub.class.
These classes are where your references to the remote objects will resolve to in the client's
address space.
The RRL will manage the mapping of these objects to the server's address space.
Creating the Client











Now we need to create the client-side application that will use the remote objects.
The client code imports the java.rmi package along with the
java.rmi.RMISecurityManager.
The first thing the client needs to do is register a security manager with the system.
The RMI package provides an RMI security manager, but if you like writing security
managers, you can register your own.
If a security manager is not registered with the system, Java will only allow
resolution of classes locally.
If you are writing an applet instead of an application, the security manager has
already been registered for you by the browser. You cannot register another
security manager for the applet.
Once you have registered the security manager, you need to create a URL string
that is comprised of the server name and remote object name you are requesting.
This will enable the client to look up the remote object on the server via the
rmiregistry.
Your client code will call the Naming.lookup method that makes a request to the
server to return a remote object reference.
Notice the object returned from the Naming.lookup method is cast to the actual
interface class.
This is because the lookup call returns a reference of type Object, an abstract type
that needs to be casted to a concrete class (e.g., the interface definition file,
mathCalc).
The
sample code for calcClient.java.
/**************************************************** *
module: calcClient.java
****************************************************/
import java.util.*;
import java.net.*;
import java.rmi.*;
import java.rmi.RMISecurityManager;
public class calcClient {
public static void main( String args[] )
{
mathCalc cm = null;
int i = 0;
System.setSecurityManager( new RMISecurityManager());
try
{
System.out.println("Starting calcClient");
String url = new String( "//"+ args[0] + "/calcMath");
System.out.println("Calc Server Lookup: url =" +
url);
cm = (mathCalc)Naming.lookup( url );
if( cm != null )
{
String testStr = "Requesting Current Interest
Rate...";
// Print Current Interest Rate from the server
cm.printRate();
// Amortize a schedule using the server
interest // rate.
float amount = (float)10000.50;
int duration = 36;
schedule curschd = cm.amortizeSchedule(
amount, duration );
// Print the schedule
curschd.print();
}
else {
System.out.println("Requested Remote
object is null.");
}
}
catch( Exception e )
{ System.out.println("An error occured");
e.printStackTrace();
System.out.println(e.getMessage());
}
}
}
The URL name lookup format for an RMI object via the registry
looks like this:
rmi://www.myserver.com/myObject or
//www.myserver.com/myObject
 If the client is successful in retrieving the remote reference, it can
invoke remote methods on the remote object at this point.
 The example makes a call to print the interest rate on the server,
and it makes a request to amortize a schedule.
 If the amortize schedule is successful, the client gets a local copy
of the schedule object.
 Then the client can call routines in the schedule object, modify
the object, etc.
 This is the client's private copy of the object, and the server has
no knowledge of any changes to this object made by the client.
Local objects are by copy, and remote objects are by reference.

Creating the Server
The server has very simple code that is similar to the
client.

Below is the calcServ.java code for the server:
/**************************************************** * module:
calcServ.java
****************************************************/
import java.util.*;
import java.rmi.*;
import java.rmi.RMISecurityManager;
public class calcServ {
public static void main( String args[] )
{ System.setSecurityManager( new
RMISecurityManager());
try { System.out.println("Starting calcServer");
mathCalcImp cm = new mathCalcImp();
System.out.println("Binding Server");
Naming.rebind("calcMath", cm );
System.out.println("Server is waiting");
}
catch( Exception e )
{ System.out.println("An error occured");
e.printStackTrace();
System.out.println(e.getMessage()); }
}

Creating the server













The server has the same requirements as the client has regarding the security manager.
Once the server has registered properly with the security manager, the server needs to
create an instantiation of the mathCalcImp implementation objec
This is the actual remote object the server exports. Since the server uses the rmiregistry,
you must bind (i.e., alias) an instance of the object with the name that will be used to look
up the object.
Note: The server sample uses rebind instead of bind.
This is to avoid the following problem with bind; i.e.,
if you start your server and bind an object to the registry then later start a newer version of
the server, the bind will not take place because a previous version already exists.
When your client references the server, it will get the original reference to the object and
not the latest.
Also, when the client tries to reference the remote object, the server will throw an exception
because the object is no longer valid.
If you instead use rebind, then each time you start a new server, it will bind the latest object
for the name lookup and replace the old object.
You can export as many objects as you like. For the sake of simplicity, the example only
exports one object.
Additionally, you can have a factory class that returns object references or you can use the
registry to look up multiple names of objects.
You normally only need one registry running, but Java does not preclude running multiple
registries on different ports.
The client needs to use the correct lookup method to gain access to the correct registry on
a port number.
If you are looking at this server application and wondering how it continues to run after it
Building the Sample

You need to compile the client and the server code by doing the following:
javac calcClient.java javac calcServ.java

Starting the Sample

Now you are ready to run the sample RMI application. The first thing to do is to start the
rmiregistry on the server. Ensure that your CLASSPATH is set up so that the registry can
find your server classes in its path. Start the rmiregistry as follows:
start rmiregistry (optional port : default port 1099 )
[The optional port number can be left out, in which case it defaults to 1099. If this is not the
desired port, specify one as in "start rmiregistry 1095". Ed.]

Next, start the server as follows:
start java calcServ

The server will start and print a message that it is waiting for requests.

Now you are ready to start the client application as follows:
start java calcClient www.myserver.com

At this point you should see a request come into the server to print the interest rate and
request a remote object reference. The client will then display the contents of the schedule
object returned from the server.
