Cover | Table of Contents | Colophon
java.net and
java.io packages could be used to do basic
networking between agents. In this chapter we take a more detailed
look at the networking support in Java, as the foundation for
distributed systems. The topics we'll cover include:URL, URLConnection, and
ContentHandler classesClassLoader as an object distribution schemeURL, URLConnection, and
ContentHandler classes; finally, the
ClassLoader, which, when coupled with the
others, offers the ability to transmit actual Java classes over the
wire.java.net
package provides an object-oriented
framework for the creation and use of
Internet Protocol (IP) sockets. In this section,
we'll take a look at these classes and what they offer.java.net
package provides an object-oriented
framework for the creation and use of
Internet Protocol (IP) sockets. In this section,
we'll take a look at these classes and what they offer.InetAddress
class represents an IP address. You can
query an InetAddress for the name of the host
using its
getHostName()
method, and for its numeric address using
getAddress()
. Notice that, even though we can uniquely
specify a host with its IP address, we do not necessarily know its
physical location. I look at the web pages on
www.javasoft.com regularly, but I don't
know where the machine is (though I could guess that it's in
California somewhere). Conversely, even if I knew where the machine
was physically, it wouldn't do me a bit of good if I
didn't know its IP address (unless someone was kind enough to
label the machine with it, or left a terminal window open on the
server's console for me to get its IP address directly).java.net
package, in addition to object-oriented
representations of IP sockets, also provides objects that support the
HTTP protocol
for accessing data in the form of addressable documents. HTTP is
really an extension of the underlying IP protocol we discussed
earlier, designed specifically to provide a way to address different
kinds of documents, or pieces of data, distributed on the network. In
the rest of this book, we'll see numerous examples of
distributed applications whose agents use customized or standard
communications protocols to talk to each other. If there is an HTTP
server "agent" available on one of the hosts in our
distributed application, then we can use the classes discussed in
this section to ask it for data documents using the standard HTTP
protocol.URL
class, which is constructed with a given protocol, host, port, and
document filename. Once the URL object is
constructed, it allows the user to make the necessary requests to
connect to the HTTP server of the data object, query for information
about the object, and download the object. The content of the object
can be accessed using the getContent(),
openConnection(), or
openStream() methods on the
URL object. Of these three methods,
openStream()
is simplest. The openStream() method returns an
InputStream that can be used to read the data
contents directly.openConnection()
on a URL object, you get a
URLConnection
in return. You can use the
URLConnection to query the data connection's
header information for the data object's length, the type of
data it contains, the data encoding, etc. You can also control
aspects of the data connection that determine when the data object
can be pulled from a local cache, whether input or output is to be
done over the data connection, and when unmodified data should be
read from the server.
import
statement is encountered. The
referenced class or package of classes is loaded from files in
bytecode format, using the CLASSPATH environment variable to locate
them on the local file system.java.lang.ClassLoader class allows the user to
define custom policies and mechanisms for locating and loading
classes into the runtime environment. The
ClassLoader is an abstract class. Subclasses
must define an implementation for the
loadClass()
method, which is responsible for
locating the class based upon the given string name, loading the
bytecodes comprising the class definition, and (optionally) resolving
the class. A class has to be resolved before it can be constructed or
before any of its methods can be called. Resolving a class includes
finding all of the other classes that it depends on, and loading them
into the runtime as well.ClassLoader is an important element of the
network support in the Java API. It's used as the basis for
supporting Java applets in most Java-enabled web browsers, for
example. When an HTML page includes an APPLET
tag that references a Java class on the HTTP server, a
ClassLoader instance within the browser's
Java runtime is used to load the bytecodes of the class into the
virtual machine, create an instance of the class, and then execute
methods on the new object. Note that this is different from the
concept of distributing objects using RMI or
CORBA. Rather than creating an object on one host and allowing a
process on a remote host to call methods on that object, the
java.net and java.io
packages, gives us easy access to the network and key network
protocols. They also let us layer application-specific operations on
top of the network pretty easily. It seems like all that we'd
need to do is extend these packages to allow objects to invoke each
other's methods over the network, and we'd have a basic
distributed object system. To get a feeling for the complexity of
distributed object systems, let's look at what it would take to
put together one of our own using just the core Java API, without
utilizing the RMI package or the object input/output streams in the
java.io package.
Solver
, is shown in Example 3.1. The Solver acts as a
generic compute engine that solves numerical problems. Problems are
given to the Solver in the form of
ProblemSet objects; the
ProblemSet
interface is shown in Example 3.2. The ProblemSet holds all
of the information describing a problem to be solved by the
Solver. The ProblemSet also
contains fields for the solution to the problem it represents. In our
highly simplified example, we're assuming that any problem is
described by a single floating-point number, and the solution is also
a single floating-point value.package dcj.examples;
import java.io.OutputStream;
//
// Solver:
// An interface to a generic solver that operates on ProblemSets
//
public interface Solver
{
// Solve the current problem set
public boolean solve();
// Solve the given problem set
public boolean solve(ProblemSet s, int numIters);
// Get/set the current problem set
public ProblemSet getProblem();
public void setProblem(ProblemSet s);
// Get/set the current iteration setting
public int getInterations();
public void setIterations(int numIter);
// Print solution results to the output stream
public void printResults(OutputStream os);
}
package dcj.examples;
public class ProblemSet
{
protected double value = 0.0;
protected double solution = 0.0;
public double getValue() { return value; }
public double getSolution() { return solution; }
public void setValue(double v) { value = v; }
public void setSolution(double s) { solution = s; }
}java.rmi.Remote
interface. The
Remote interface doesn't introduce any
methods to the object's interface; it just serves to mark
remote objects for the RMI system. Also, all methods in the interface
must be declared as throwing the
java.rmi.RemoteException
. The RemoteException is the base class for many
of the exceptions that RMI defines for remote operations, and the RMI
engineers decided to expose the exception model in the interfaces of
all RMI remote objects. This is one of the drawbacks of RMI: it
requires you to alter an existing interface in order to apply it to a
distributed environment.java.rmi.server.UnicastRemoteObject
class.
UnicastRemoteObject is an extension of the
RemoteServer
class, which acts as a base class for
server implementations of objects in RMI. Subclasses of
java.io package, we
can implement an agent-based system where clients subclass and
specialize an Agent interface, set the operating
parameter values for the agent, and then send the object in its
entirety to a remote "sandbox" server, where the object
will act in our name to negotiate on some issue (airline ticket
prices, stocks and bonds, order-fulfillment schedules, etc.). The
remote server only knows that each agent has to implement an
agreed-upon interface, but doesn't know anything about how each
agent is implemented, even though the agent is running on the server
itself. In CORBA, objects can never really leave their implementation
hosts; they can only roam the network in the virtual sense, sending
stub references to themselves and to clients. We don't have the
option of offloading an object from one host to another.java.lang.Thread and
java.lang.Runnable. They allow you to define
threads of control in your application, and to manage threads in
terms of runtime resources and running state.java.lang.Thread
represents a thread of control. It offers methods that allow you to
set the priority of the thread, to assign a thread to a thread group
(more on these in a later section), and to control the running state
of the thread (e.g., whether it is running or suspended).java.lang.Runnable interface represents the
body of a thread. Classes that implement the
Runnable interface provide their own
run()
methods that determine what
their thread actually does while running. In fact,
run() is the only method defined by the
Runnable interface. If a
Thread is constructed with a
Runnable object as its body, the
run() method on the
Runnable will be called when the thread is
started.Thread class or
implementing the Runnable interface with your
application objects is sometimes not an obvious one. It's also
usually not very important. Essentially, the difference between the
two classes is that a Thread is supposed to
represent how a thread of control runs (its
priority level, the name for the thread), and a
Runnable defines what a
thread runs. In both cases, defining a subclass usually involves
implementing the java.lang.Thread and
java.lang.Runnable. They allow you to define
threads of control in your application, and to manage threads in
terms of runtime resources and running state.java.lang.Thread
represents a thread of control. It offers methods that allow you to
set the priority of the thread, to assign a thread to a thread group
(more on these in a later section), and to control the running state
of the thread (e.g., whether it is running or suspended).java.lang.Runnable interface represents the
body of a thread. Classes that implement the
Runnable interface provide their own
run()
methods that determine what
their thread actually does while running. In fact,
run() is the only method defined by the
Runnable interface. If a
Thread is constructed with a
Runnable object as its body, the
run() method on the
Runnable will be called when the thread is
started.Thread class or
implementing the Runnable interface with your
application objects is sometimes not an obvious one. It's also
usually not very important. Essentially, the difference between the
two classes is that a Thread is supposed to
represent how a thread of control runs (its
priority level, the name for the thread), and a
Runnable defines what a
thread runs. In both cases, defining a subclass usually involves
implementing the run() method to do whatever
work you want done in the separate thread of control.Runnable
interface. With a Runnable subclass, you can use
the same object with different types of Thread
subclasses, depending on the application. You might use your
implementation of Runnable inside a standard
Thread in one case, and in another you might run
it in a subclass of Thread that sends a notice
across the network when it's started.Thread can
make your classes slightly easier to use. You just create one of your
Thread subclasses and run it, instead of
creating a Runnable subclass, putting into
another Thread, and running it. Also, if your
application objects are subclasses of Thread,
then you can access them directly by asking the system for the
current thread, or the threads in the current thread group, etc. Then
you can cast the object to its subclass and call specialized methods
on it, maybe to ask it how far it's gotten on whatever task you
gave it.Runnable and extend Thread
to make an object that executes in an independent thread. We'll
return to our Solver example, making it usable
in a multithreaded agent within a distributed system. The examples in
this section will use fairly basic network communications, based on
sockets and I/O streams, but the concepts extend pretty easily to
distributed object scenarios.
synchronized
statement. A method or block of code is synchronized on a class,
object, or array, depending on the context of the
synchronized keyword. If you use the
synchronized modifier on a static method of a
class, for example, then before the method is executed, the Java
virtual machine obtains an exclusive "lock" on the class.
A thread that attempts to enter this block of code has to get the
lock before the code in the synchronized block is executed. If
another thread is executing in this critical section at the time, the
thread will block until the running thread exits the critical section
and the lock on the class is released.synchronized,
then the virtual machine obtains a lock on the object on which the
method is invoked. If you define a synchronized block of code, then
you have to specify the class, object, or array on which to
synchronize.Solver interface in Example 4.1
shows how multithreaded servers can be implemented in Java. This
allows our server to respond to clients asynchronously and to service
their requests in parallel, which can reduce the amount of time a
client has to wait for a response. The alternative is to have a
server with only one thread servicing clients on a first-come,
first-serve basis. So if client A is the first client to make a
request, the server begins processing it right away. If client B
makes a request while the server is processing client A's job,
then B will have to wait for the server to finish A's job
before its job can be started. In fact, client B won't even get
an acknowledgment from the server until client A's job is done.
With the multithreaded server, an independent thread can listen for
client requests and acknowledge them almost immediately (or as soon
as the thread scheduler gives it a CPU time slice). And with the jobs
being allocated to separate threads for processing, the CPU resources
will be spread out between the two jobs, and B's job will
potentially finish sooner (though client A's job might finish
later, since it is now getting less than 100% of the CPU).Solver servers could be accessed by intruders
acting as legitimate clients, and used to solve their numerical
problems; or a hostile party could flood the server with
ProblemSets to be solved, rendering the server
useless to the legitimate users. Clients of the
Solver are also vulnerable, since a hostile
party could set up an imposter Solver meant to
steal the problem data submitted by clients, or they could purposely
generate erroneous results. If the attacker manages to figure out
that the clients are trying to solve for stress levels in a
finite-element model, for example, then the imposter server could be
set up to return results that indicate abnormally high or low stress
levels in critical sections of the model. This could cause someone to
design a bridge that will collapse, or that's too expensive to
build.java.security package.KeyPairGenerator myGen = KeyPairGenerator.getInstance("DSA");
KeyPairGenerator myGen =
KeyPairGenerator.getInstance("DSA", "MY_PROVIDER");
KeyPairGenerator in our example, implementations
of these interfaces will use algorithm-specific subclasses. If you
need to give details about the algorithm and its specific parameters,
then you can access these algorithm-specific interfaces for the
objects you create by casting:Identity
class represents an agent within the
Security API. Identity implements the
Principal
interface, which is a generic
representation of a person, group, or other named entity. An
Identity has a name, which it inherits from the
Principal interface, and other information that
verifies the identity of the agent (a public key and assorted
certificates, for example). A Signer is a
subclass of Identity that also includes a
private key that can be used to sign data. We'll discuss public
and private keys and how they are created in more detail later in the
chapter.Identity is created using a name for the agent
being represented:Identity fredsID = new Identity("Fred");
PublicKey fredsKey = ... // Get Fred's key Certificate fredsCert = ... // Get Fred's certificate Certificate fredsRSACert = ... // Get another certificate for Fred fredsID.setPublicKey(fredsKey); fredsID.addCertificate(fredsCert); fredsID.addCertificate(fredsRSACert);
Signer object for him:Signer signingFred = new Signer("Fred");
PrivateKey fredsSigningKey = ... // Get Fred's private key
PublicKey fredsPublicKey = ... // Get Fred's public key
signingFred.setKeyPair(new KeyPair(fredsPublicKey, fredsSigning Key));;
java.security.acl
package includes interfaces that let you
define specific access rights for individual agents or groups of
agents. In the same style as the rest of the Security API, this
package defines an API for access-control lists, with few of the
interfaces actually implemented in the package. Sun has provided a
default implementation of the ACL package in their
sun.security.acl
package. We'll use classes from
Sun's implementation to demonstrate ACLs.