Java Network Programming (3rd ed) [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Java Network Programming (3rd ed) [Electronic resources] - نسخه متنی

Harold, Elliotte Rusty

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید








18.2 Implementation


Most
of the methods you need for working with remote objects are in three
packages:
java.rmi, java.rmi.server, and
java.rmi.registry. The java.rmi
package defines the classes, interfaces, and exceptions that will be
seen on the client side. You need these when you're
writing programs that access remote objects but are not themselves
remote objects. The java.rmi.server package
defines the classes, interfaces, and exceptions that will be visible
on the server side. Use these classes when you are writing a remote
object that will be called by clients. The
java.rmi.registry package defines the classes,
interfaces, and exceptions that are used to locate and name remote
objects.


In this chapter and in Sun's documentation, the
server side is always considered to be
"remote" and the client is always
considered "local". This can be
confusing, particularly when you're writing a remote
object. When writing a remote object, you're
probably thinking from the viewpoint of the server, so that the
client appears to be remote.


18.2.1 The Server Side


To create a new remote object,
first define an interface that extends the
java.rmi.Remote interface.
Remote is a marker interface that does not
have any methods of its own; its sole purpose is to tag remote
objects so that they can be identified as such. One definition of a
remote object is an instance of a class that implements the
Remote interface, or any interface that extends
Remote.

Your subinterface of Remote determines which
methods of the remote object clients may call. A remote object may
have many public methods, but only those declared in a remote
interface can be invoked remotely. The other public methods may be
invoked only from within the virtual machine where the object lives.

Each method in the subinterface must declare that it throws
RemoteException.
RemoteException is the superclass for most of the
exceptions that can be thrown when RMI is used. Many of these are
related to the behavior of external systems and networks and are thus
beyond your control.

Example 18-2 is a simple interface for a remote
object that calculates Fibonacci numbers of arbitrary size.
(Fibonacci numbers are the sequence that begins 1, 1, 2, 3, 5, 8, 13
. . . in which each number is the sum of the previous two.) This
remote object can run on a high-powered server to calculate results
for low-powered clients. The interface declares two overloaded
getFibonacci( ) methods, one of which takes an
int as an argument and the other of which takes a
BigInteger. Both methods return
BigInteger because Fibonacci numbers grow very
large very quickly. A more complex remote object could have many more
methods.

Example 18-2. The Fibonacci interface


import java.rmi.*;
import java.math.BigInteger;
public interface Fibonacci extends Remote {
public BigInteger getFibonacci(int n) throws RemoteException;
public BigInteger getFibonacci(BigInteger n) throws RemoteException;
}

Nothing in this interface says anything about how the calculation is
implemented. For instance, it could be calculated directly, using the
methods of the java.math.BigInteger class. It
could be done equally easily with the more efficient methods of the
com.ibm.BigInteger class from
IBM's alphaWorks (http://www.alphaworks.ibm.com/tech/bigdecimal).
It could be calculated with ints for small values
of n and BigInteger for large
values of n. Every calculation could be performed
immediately, or a fixed number of threads could be used to limit the
load that this remote object places on the server. Calculated values
could be cached for faster retrieval on future requests, either
internally or in a file or database. Any or all of these are
possible. The client neither knows nor cares how the server gets the
result as long as it produces the correct one.

The next step is to define a class that implements this remote
interface. This class should extend
java.rmi.server.UnicastRemoteObject, either
directly or indirectly (i.e., by extending another class that extends
UnicastRemoteObject):

public class UnicastRemoteObject extends RemoteServer

Without going into too much detail, the
UnicastRemoteObject provides a number of methods that make
remote method invocation work. In particular, it marshals and
unmarshals remote references to the object.
(Marshalling is the process by which arguments
and return values are converted into a stream of bytes that can be
sent over the network. Unmarshalling is the
reverse: the conversion of a stream of bytes into a group of
arguments or a return value.)

If extending UnicastRemoteObject
isn't convenientfor instance, because
you'd like to extend some other classyou can
instead export your object as a remote object by passing it to one of
the static UnicastRemoteObject.exportObject() methods:

public static RemoteStub exportObject(Remote obj) 
throws RemoteException
public static Remote exportObject(Remote obj, int port) // Java 1.2
throws RemoteException
public static Remote exportObject(Remote obj, int port, // Java 1.2
RMIClientSocketFactory csf, RMIServerSocketFactory ssf)
throws RemoteException

These create a remote object that uses your object to do the work.
It's similar to how a Runnable
object can be used to give a thread something to do when
it's inconvenient to subclass
Thread. However, this approach has the downside of
preventing the use of dynamic proxies in Java 1.5, so you need to
manually deploy stubs. (In Java 1.4 and earlier, you always have to
use stubs.)

There's one other kind of
RemoteServer in the standard Java class library,
the
java.rmi.activation.Activatable class:

public abstract class Activatable extends RemoteServer // Java 1.2

A UnicastRemoteObject exists only as long as the
server that created it still runs. When the server dies, the object
is gone forever. Activatable objects allow clients
to reconnect to servers at different times across server shutdowns
and restarts and still access the same remote objects. It also has
static Activatable.exportObject( ) methods to
invoke if you don't want to subclass
Activatable.

Example 18-3, the FibonacciImpl class, implements
the remote interface Fibonacci. This class has a
constructor and two getFibonacci( ) methods. Only
the getFibonacci( ) methods will be available to
the client, because they're the only ones defined by
the Fibonacci interface. The constructor is used
on the server side but is not available to the client.


Example 18-3. The FibonacciImpl class


import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import java.math.BigInteger;
public class FibonacciImpl extends UnicastRemoteObject implements Fibonacci {
public FibonacciImpl( ) throws RemoteException {
super( );
}
public BigInteger getFibonacci(int n) throws RemoteException {
return this.getFibonacci(new BigInteger(Long.toString(n)));
}
public BigInteger getFibonacci(BigInteger n) throws RemoteException {
System.out.println("Calculating the " + n + "th Fibonacci number");
BigInteger zero = new BigInteger("0");
BigInteger one = new BigInteger("1");
if (n.equals(zero)) return one;
if (n.equals(one)) return one;
BigInteger i = one;
BigInteger low = one;
BigInteger high = one;
while (i.compareTo(n) == -1) {
BigInteger temp = high;
high = high.add(low);
low = temp;
i = i.add(one);
}
return high;
}
}

The FibonacciImpl( ) constructor just calls the
superclass constructor that exports the object; that is, it creates a
UnicastRemoteObject on some port and starts it
listening for connections. The constructor is declared to throw
RemoteException because the
UnicastRemoteObject constructor can throw that
exception.

The getFibonacci(int
n) method is trivial. It simply returns the result
of converting its argument to a BigInteger and
calling the second getFibonacci( ) method. The
second method actually performs the calculation. It uses
BigInteger throughout the calculation to allow for
arbitrarily large Fibonacci numbers of an arbitrarily large index to
be calculated. This can use a lot of CPU power and huge amounts of
memory. That's why you might want to move it to a
special-purpose calculation server rather than performing the
calculation locally.

Although getFibonacci( ) is a remote method,
there's nothing different about the method itself.
This is a simple case, but even vastly more complex remote methods
are not algorithmically different than their local counterparts. The
only differencethat a remote method is declared in a remote
interface and a local method is notis completely external to
the method itself.

Next, we need to write a server that makes the
Fibonacci remote object available to the world. Example 18-4 is such a server. All it has is a main(
)
method. It begins by entering a try
block that catches RemoteException. Then it
constructs a new FibonacciImpl object and binds
that object to the name "fibonacci"
using the Naming class to talk to the local
registry. A registry keeps track of the available objects on an RMI
server and the names by which they can be requested. When a new
remote object is created, the object adds itself and its name to the
registry with the Naming.bind( ) or
Naming.rebind( ) method. Clients can then ask for
that object by name or get a list of all the remote objects that are
available. Note that there's no rule that says the
name the object has in the registry has to have any necessary
relation to the class name. For instance, we could have called this
object "Fred". Indeed, there might
be multiple instances of the same class all bound in a registry, each
with a different name. After registering itself, the server prints a
message on System.out signaling that it is ready
to begin accepting remote invocations. If something goes wrong, the
catch block prints a simple error message.


Example 18-4. The FibonacciServer class


import java.net.*;
import java.rmi.*;
public class FibonacciServer {
public static void main(String[] args) {
try {
FibonacciImpl f = new FibonacciImpl( );
Naming.rebind("fibonacci", f);
System.out.println("Fibonacci Server ready.");
}
catch (RemoteException rex) {
System.out.println("Exception in FibonacciImpl.main: " + rex);
}
catch (MalformedURLException ex) {
System.out.println("MalformedURLException " + ex);
}
}
}

Although the main( ) method finishes fairly
quickly here, the server will continue to run because a nondaemon
thread is spawned when the FibonacciImpl object is
bound to the registry. This completes the server code you need to
write.


18.2.2 Compiling the Stubs


RMI
uses stub classes to mediate between local objects and the remote
objects running on the server. Each remote object on the server is
represented by a stub class on the client. The stub contains the
information in the Remote interface (in this
example, that a Fibonacci object has two
getFibonacci( ) methods). Java 1.5 can sometimes
generate these stubs automatically as they're
needed, but in Java 1.4 and earlier, you must manually compile the
stubs for each remote class. Even in Java 1.5, you still have to
manually compile stubs for remote objects that are not subclasses of
UnicastRemoteObject and are instead exported by
calling UnicastRemoteObject.exportObject( ).

Fortunately, you don't have to write stub classes
yourself: they can be generated automatically from the remote
class's byte code using the
rmic utility included with the JDK. To generate
the stubs for the FibonacciImpl remote object, run
rmic on the remote classes you want to generate
stubs for. For example:

% rmic FibonacciImpl
% ls Fibonacci*
Fibonacci.class FibonacciImpl_Stub.class FibonacciServer.java
FibonacciImpl.class Fibonacci.java
FibonacciImpl.java FibonacciServer.class

rmic reads the .class file
of a class that implements Remote and produces
.class files for the stubs needed for the remote
object. The command-line argument to rmic is the
fully package-qualified class name (e.g.,
com.macfaq.rmi.examples.Chat, not just
Chat) of the remote object class.

rmic supports the same command-line options as
the javac compiler: for example,
-classpath and -d. For
instance, if the class doesn't fall in the class
path, you can specify the location with the
-classpath command-line argument. The following
command searches for FibonacciImpl.class in the
directory test/classes:

% rmic -classpath test/classes FibonacciImpl


18.2.3 Starting the Server


Now you're ready to
start the server. There are actually two servers you need to run, the
remote object itself (FibonacciServer in this
example) and the registry that allows local clients to download a
reference to the remote object. Since the server expects to talk to
the registry, you must start the registry first. Make sure all the
stub and server classes are in the server's class
path and type:

% rmiregistry &

On Windows, you start it from a DOS prompt like this:

C:> start rmiregistry

In both examples, the registry runs in the background. The registry
tries to listen to port 1,099 by default. If it fails, especially
with a message like "java.net. SocketException:
Address already in use", then some other program is
using port 1099, possibly (though not necessarily) another registry
service. You can run the registry on a different port by appending a
port number like this:

% rmiregistry 2048 &

If you use a different port, you'll need to include
that port in URLs that refer to this registry service.

Finally, you're ready to start the server. Run the
server program just as you'd run any Java class with
a main( ) method:

% java FibonacciServer
Fibonacci Server ready.

Now the server and registry are ready to accept remote method calls.
Next we'll write a client that connects to these
servers to make such remote method calls.


18.2.4 The Client Side


Before a regular Java object can call a
method, it needs a reference to the object whose method
it's going to call. Before a client object can call
a remote method, it needs a remote reference to the object whose
method it's going to call. A program retrieves this
remote reference from a registry on the server where the remote
object runs. It queries the registry by calling the
registry's lookup( ) method. The
exact naming scheme depends on the registry; the
java.rmi.Naming class provides a URL-based scheme
for locating objects. As you can see in the following code, these
URLs have been designed so that they are similar to
http URLs. The protocol is
rmi. The URL's file field
specifies the remote object's name. The fields for
the hostname and the port number are unchanged:

Object o1 = Naming.lookup("rmi://login.ibiblio.org/fibonacci");
Object o2 = Naming.lookup("rmi://login.ibiblio.org:2048/fibonacci");

Like objects stored in Hashtables,
Vectors, and other data structures that store
objects of different classes, the object that is retrieved from a
registry loses its type information. Therefore, before using the
object, you must cast it to the remote interface that the remote
object implements (not to the actual class, which is hidden from
clients):

Fibonacci calculator = (Fibonacci) Naming.lookup("fibonacci");

Once a reference to the object has been retrieved and its type
restored, the client can use that reference to invoke the
object's remote methods pretty much as it would use
a normal reference variable to invoke methods in a local object. The
only difference is that you'll need to catch
RemoteException for each remote invocation. For
example:

try {
BigInteger f56 = calculator.getFibonacci(56);
System.out.println("The 56th Fibonacci number is " + f56);
BigInteger f156 = calculator.getFibonacci(new BigInteger(156));
System.out.println("The 156th Fibonacci number is " + f156);
}
catch (RemoteException ex) {
System.err.println(ex)
}

Example 18-5 is a simple client for the Fibonacci
interface of the last section.


Example 18-5. The FibonacciClient


import java.rmi.*;
import java.net.*;
import java.math.BigInteger;
public class FibonacciClient {
public static void main(String args[]) {
if (args.length == 0 || !args[0].startsWith("rmi:")) {
System.err.println(
"Usage: java FibonacciClient rmi://host.domain:port/fibonacci number");
return;
}
try {
Object o = Naming.lookup(args[0]);
Fibonacci calculator = (Fibonacci) o;
for (int i = 1; i < args.length; i++) {
try {
BigInteger index = new BigInteger(args[i]);
BigInteger f = calculator.getFibonacci(index);
System.out.println("The " + args[i] + "th Fibonacci number is "
+ f);
}
catch (NumberFormatException e) {
System.err.println(args[i] + "is not an integer.");
}
}
}
catch (MalformedURLException ex) {
System.err.println(args[0] + " is not a valid RMI URL");
}
catch (RemoteException ex) {
System.err.println("Remote object threw exception " + ex);
}
catch (NotBoundException ex) {
System.err.println(
"Could not find the requested remote object on the server");
}
}
}

Compile the class as usual. Notice that because the object that
Naming.lookup( ) returns is cast to a
Fibonacci, either the
Fibonacci.java or
Fibonacci.class file needs to be available on
the local host. A general requirement for compiling a client is to
have either the byte or source code for the remote interface
you're connecting to. To some extent, you can relax
this a little bit by using the reflection API, but
you'll still need to know at least something about
the remote interface's API. Most of the time, this
isn't an issue, since the server and client are
written by the same programmer or team. The point of RMI is to allow
a VM to invoke methods on remote objects, not to compile against
remote objects.


18.2.5 Running the Client


Go
back to the client system. Make sure that the client system has
FibonacciClient.class,
Fibonacci.class, and
FibonacciImpl_Stub.class in its class path. (If
both the client and the server are running Java 1.5, you
don't need the stub class.) On the client system,
type:

C:\>java FibonacciClient rmi://host.com/fibonacci 0 1 2 3 4 5 55 155

You should see:

The 0th Fibonacci number is 1
The 1th Fibonacci number is 1
The 2th Fibonacci number is 2
The 3th Fibonacci number is 3
The 4th Fibonacci number is 5
The 5th Fibonacci number is 8
The 55th Fibonacci number is 225851433717
The 155th Fibonacci number is 178890334785183168257455287891792

The client converts the command-line arguments to
BigInteger objects. It sends those objects over
the wire to the remote server. The server receives each of those
objects, calculates the Fibonacci number for that index, and sends a
BigInteger object back over the Internet to the
client. Here, I'm using a PC for the client and a
remote Unix box for the server. You can actually run both server and
client on the same machine, although that's not as
interesting.


/ 164