Remote Methode Invocation
Remote Methode Invocation
Preface
Chapter 1. Introduction to RMI
Section 1.1. In this chapter
Section 1.2. Java and RMI
Section 1.3. Architecture of RMI systems
Section 1.4. Syntax of RMI
Section 1.5. First principles—remote method invocation
Section 1.6. Baby's first words
Section 1.7. Exercises
Chapter 2. Characteristics of RMI
Section 2.1. In this chapter
Section 2.2. Syntax
Section 2.3. Semantics
Section 2.4. Semantics of local method invocation
Section 2.5. Semantics of remote method invocation
Section 2.6. Summary
Section 2.7. Exercises
Chapter 3. Serialization
Section 3.1. In this chapter
Section 3.2. Introduction
Section 3.3. Essentials
Section 3.4. Serialization in depth
Section 3.5. The serialization process
Section 3.6. The Serializable interface
Section 3.7. The Externalizable interface
Section 3.8. MarshalledObject
Section 3.9. Class versioning
Section 3.10. Serial Version UID
Section 3.11. Alternative approaches to versioning
Section 3.12. Advanced facilities in Serialization
Section 3.13. javadoc and serialization
Section 3.14. Improving the performance of Serialization
Section 3.15. Exercises
Chapter 4. Remote interfaces
Section 4.1. In this chapter
Section 4.2. Introduction
Section 4.3. Proxies
Section 4.4. Dispatchers
Section 4.5. Exercises
Chapter 5. RMI clients
Section 5.1. In this chapter
Section 5.2. Introduction
Section 5.3. Remote failure
Section 5.4. Partial failure
Section 5.5. Latency
Section 5.6. Applets
Section 5.7. Exercises
Chapter 6. Naming I—RMI registry
Section 6.1. In this chapter
Section 6.2. Purpose
Section 6.3. How it works
Section 6.4. Names in the registry
Section 6.5. The Naming class
Section 6.6. The Registry interface
Section 6.7. Registry exceptions
Section 6.8. Names and URLs
Section 6.9. Registry setup
Section 6.10. Registry configurations
Section 6.11. Utilities
Section 6.12. Alternative naming services
Section 6.13. Exercises
Chapter 7. Servers I—unicast servers
Section 7.1. In this chapter
Section 7.2. Introduction
Section 7.3. Writing the server
Section 7.4. Implementing remote interface methods
Section 7.5. Threads, sockets, and ports
Section 7.6. The Unreferenced interface
Section 7.7. Building the server
Section 7.8. Foundation classes
Section 7.9. Serialization
Section 7.10. Alternative server classes
Section 7.11. Exercises
Chapter 8. Security I—execution
Section 8.1. In this chapter
Section 8.2. Introduction
Section 8.3. RMI and security managers
Section 8.4. Applets
Section 8.5. Clients
Section 8.6. Servers
Section 8.7. System properties—security
Section 8.8. Policy files
Section 8.9. Granting AllPermission
Chapter 9. Mobile code
Section 9.1. In this chapter
Section 9.2. Outline
Section 9.3. How code mobility works
Section 9.4. Uses of code mobility
Section 9.5. Security considerations
Section 9.6. Setup
Section 9.7. HTTP servers
Section 9.8. Other protocols
Section 9.9. Deployment
Section 9.10. Downloading the client
Chapter 10. Servers II—activation
Section 10.1. In this chapter
Section 10.2. Introduction
Section 10.3. First principles—activation
Section 10.4. How it works
Section 10.5. Writing an activatable server
Section 10.6. Registration
Section 10.7. Building an activatable server
Section 10.8. Runtime setup
Section 10.9. Activation and the Unreferenced interface
Section 10.10. Which servers should be activatable
Section 10.11. The activation system as an RMI registry
Section 10.12. Debugging
Section 10.13. Activation groups in Win32
Section 10.14. Activation clients
Section 10.15. Remarks on the Activation Package
Section 10.16. Exercises
Chapter 11. Socket factories
Section 11.1. In this chapter
Section 11.2. Purpose
Section 11.3. Server socket factories
Section 11.4. Client socket factories
Section 11.5. Factory equality—the equals method
Section 11.6. Uses of socket factories
Section 11.7. Remarks
Chapter 12. Agents and patterns
Section 12.1. In this chapter
Section 12.2. Introduction
Section 12.3. Mobile agents
Section 12.4. Callbacks
Section 12.5. Mobile servers
Section 12.6. Agents and design patterns
Section 12.7. Adapter
Section 12.8. Proxy
Section 12.9. Clientserver patterns
Section 12.10. Singleton
Section 12.11. Remote factory
Section 12.12. Abstract remote
Section 12.13. Session
Section 12.14. Exercises
Section 12.15. Remarks on the examples and exercises
Chapter 13. Naming II—JNDI and Jini
Section 13.1. In this chapter
Section 13.2. JNDI
Section 13.3. JNDI operations
Section 13.4. JNDI providers
Section 13.5. Examples
Section 13.6. Other features and setup
Section 13.7. Other JNDI service providers
Section 13.8. Jini naming
Section 13.9. Exercises
Chapter 14. Servers III—RMI/IIOP
Section 14.1. In this chapter
Section 14.2. Introduction
Section 14.3. RMI/IIOP and CORBA
Section 14.4. RMI/IIOP and Enterprise Java Beans
Section 14.5. PortableRemoteObject
Section 14.6. Writing the server
Section 14.7. Building the server
Section 14.8. Java/IDL tool
Section 14.9. Supporting both JRMP and IIOP
Section 14.10. Restrictions
Section 14.11. Implementing the service in another language
Section 14.12. IIOP clients
Section 14.13. Implementing the client in another language
Section 14.14. Exercises
Chapter 15. RMI through firewalls
Section 15.1. In this chapter
Section 15.2. Firewalls
Section 15.3. SOCKS
Section 15.4. HTTP tunnelling
Section 15.5. Firewalls and RMI
Section 15.6. GIOP Proxies
Section 15.7. The RMI Proxy
Section 15.8. A note on callbacks
Section 15.9. A note on firewall implementations and RMI
Chapter 16. Security II—the conversation
Section 16.1. In this chapter
Section 16.2. Identity
Section 16.3. Integrity
Section 16.4. Privacy
Section 16.5. Secure Sockets Layer
Section 16.6. LDAP authentication
Section 16.7. JAAS
Section 16.8. RMI Security Extension
Section 16.9. Exercises
Chapter 17. Servers IV—beyond unicast
Section 17.1. In this chapter
Section 17.2. Datagrams
Section 17.3. Multicast
Section 17.4. Broadcast
Section 17.5. Clients
Section 17.6. Exercises
Chapter 18. Selected further topics
Section 18.1. In this chapter
Section 18.2. Distributed garbage collection
Section 18.3. Logging
Section 18.4. Debugging
Section 18.5. Testing RMI in a single machine
Section 18.6. Performance
Section 18.7. RMI and JDK versions
Section 18.8. Exercises
Appendix A. Exceptions in RMI
Section A.1. Class hierarchy
Section A.2. Exceptions in RMI servers
Section A.3. Exceptions in RMI clients
Section A.4. Alphabetic list of exceptions
Section A.5. Remarks on exceptions in RMI
Appendix B. System properties
Section B.1. RMI system properties
Section B.2. Implementationdependent system properties
Appendix C. References
Section C.1. Books
Section C.2. Papers
Section C.3. Web resources
Appendix D. Glossary
Foreword
Before the Java language was available, distributed systems engineering was greatly concerned with
the problem of heterogeneity—the differences between CPUs, OSes, languages, and data formats. The
challenge was to create architectureneutral formats and protocols which could be adapted to any
architecture without burdening the programmer. RPC and CORBA IIOP represent two such
approaches.
After the Java language was mooted as a universal language, an interesting question was raised: how
could a distributed system design be simplified by the assumption that every host is Javacapable? The
Java Remote Method Invocation (RMI) API is at once a working demonstration of the answer and a
popular tool for building realworld Javabased distributed systems.
The Java RMI API was originally designed by Ann Wollrath, Roger Riggs, and Jim Waldo at Sun
Microsystems Laboratories, East Coast Division. It was designed with the twin aims of simplicity
(ease of use) and naturalness (being a good fit with the language).
RMI is not just a Java RPC. Its distributed object model allows the Java software engineer to build a
complex distributed system in an efficient, maintainable, and objectoriented way. In particular, the
Java RMI semantics have been designed for, and assumed by, the Jini technology unveiled by Sun
Microsystems. The RMI development team is a subgroup of the Jini team, and the same careful design
philosophy applies to RMI as to Jini technology.
The main deficiency of the Java RMI technology has been its documentation. Design documents were
hard to find and occasionally hard to understand. Error messages were unhelpful to the uninitiated.
Learning RMI became much easier when the Java RMI tutorials were written by professionals, and
successive releases have made the code more intuitive; however, in order to find RMI esoterica and
arcana, not to mention bug workarounds, there was no alternative but to consult the RMIUSERS
discussion group, or to buttonhole Ann, Jim, or Peter at a JavaONE talk.
This book collects an astonishing range of Java RMI material, suitable for any Java programmer
regardless of experience with RMI. The RMI novice will be able to run RMI programs without
excessive hairpulling; the accomplished RMI programmer will find enough techniques and
explanations to improve the performance, capability, or aesthetic quality of his or her code; and the
RMI expert will be able to perform great RMIbased hacks without the JDK source code. Finally, the
RMIUSERS denizen will be able to answer most questions by pointing to a single source.
So, if you're looking at this book because I directed you to it on the list, then, yes, you've found the
right one.
Adrian Colley
Sun Microsystems Ireland
February 2001
Preface
About this book—How it is organized—Prerequisite knowledge—Terminology and
conventions—Examples—Exercises—Colophon—Acknowledgements
"There can be nothing intermediate between that which undergoes and that which causes
alteration."
—Aristotle, Physics, Book VII, § 2.
How it is organized
The first two chapters introduce RMI and set it within the context of Java and objectoriented
programming. While this book assumes that you are familiar with Java and distributed computing,
these first chapters can help to reacquaint you with some relevant concepts. Along with a brief
introduction to the principles of RMI, Chapter 1 also introduces a simple RMI service which is used
throughout the book for illustrative purposes.
Chapters 3 to 9 provide the fundamental building blocks for getting up and running with RMI. They
discuss what you need to know about serialization, remote interfaces, clients, the RMI registry,
servers, security, and mobile code.
Chapters 10 to 14 build on the topics discussed in earlier chapters. These chapters move toward more
advanced concepts in RMI such as activation, socket factories, and design patterns. These chapters
may be especially useful to those already familiar with RMI and its implementation.
The remaining chapters provide additional information for developers looking for alternatives to the
standard RMI implementation, or extensions to it, such as JNDI naming services, Jini, CORBA/IIOP,
and SSL. These chapters also discuss peripheral issues which may interest those wanting to go deeper
into RMI—including firewalls, performance, conversational security, and debugging.
In the appendices we have listed all the exceptions that can arise in RMI, and all the system properties
which can modify its behaviour. Further appendices list our references and other resources, and
provide a comprehensive glossary of terms.
Prerequisite knowledge
We have assumed that you have a reasonable knowledge of the Java programming language
(specifically the concepts class, abstract, object, interface, method, parameter, argument, result, and
exception), and of the basic principles of objectoriented programming (inheritance and
polymorphism). All this can be found in the standard reference—Arnold, Gosling, and Holmes, The
Java Programming Language, 3rd edition, AddisonWesley, 2000.
We have also assumed that you have an elementary understanding of networking in Java: specifically
the java.net package (in particular the classes Socket and ServerSocket), and of the
standard Java exception classes.
Throughout this book, we have used the terms "base class" and "derived class", rather than the
counterintuitive terms "superclass" and "subclass".
Examples
All the source code examples in this book have been compiled and executed on Windows and Solaris
platforms, and in many cases have interoperated between both platforms.
Exercises
The exercises are all feasible, but vary in difficulty from trivial upwards. Some few are of a more
theoretical nature.
The examples and exercises are designed to promote understanding of the concepts, rather than to
provide "liftout" code which can be immediately deployed.
Colophon
This book is set in Times New Roman PS MT produced by Adobe Systems Incorporated, and Arial
MT produced by Monotype Corporation. The book was written in Adobe FrameMaker on Apple and
PC platforms, and typeset with Adobe FrameMaker, Distiller, and Acrobat, and submitted to the
publisher in Adobe Portable Document Format (PDF). Thanks to Adobe for making these tools.
The Almanac was produced by javadoc and Sun's unsupported MIF Doclet: thanks to the people who
mind these tools, Doug Kramer and his team, for assistance and advice, and especially for
implementing some of our requests in time for our deadlines.
Acknowledgements
This book could not have been written without the assistance of Sun Microsystems Inc, firstly in
producing Java and the RMI package to provide us with our subject, and secondly in offering much
valuable assistance and advice.
Thanks especially to:
• Helen Maclean, our typesetter extraordinaire
• our colleagues at Pearson Education: our supervising editor Alison Birtwell, for the
opportunity, and our production editor, Sally Carter
• many respected colleagues on the RMI Mailing List, especially Brian Maso and Adrian Colley
• the Melbourne office of Sun Microsystems Australia, for supplying Solaris test facilities for
the sample programs, and especially Michael Geisler.
Thanks finally to our anonymous reviewers who diligently scrutinized our draft manuscript and made
many valuable comments and suggestions. Any errors that remain are of course our responsibility.
Sun, Java, and many Javarelated terms are trademarks of Sun Microsystems Incorporated, Santa
Clara, California. Any other trademarks referred to in this book remain property of their respective
owners.
From Kathy
Thank you Esmond for knowing so much about RMI.
Thanks to Andra Bruveris for directions in style and to Gavin Arndt for his neverending technical
support.
To my boys Pete, Frank, and Roy—thanks for your love and patience. To the little one for waiting until
my deadline was met.
Kathy McNiff
Northcote, Victoria, Australia, August 2000
From Esmond
Thanks Kath for being everengaging and unfailingly helpful, and for subtly projectmanaging this
book.
Thanks to my valued professional colleagues: to John Marquet for providing the initial conditions
which made this book possible; to Neil Belford for clear thinking, encouragement, wit, and advice;
and to Gavin Arndt for all the amusement.
Finally, thanks to Tilly and to all my family for their understanding and support.
Esmond Pitt
Great Ocean Road, Lorne, Victoria, Australia, July 2000
Chapter 1. Introduction to RMI
Java and RMI—Architecture of an RMI system—Syntax of RMI—First principles—
Baby's first words—Exercises
Figure 1.2 illustrates a more advanced RMI system, including RMI Activation—a system which
allows servers to be activated on demand. The RMI Activation system is discussed in detail in Chapter
10.
This diagram also illustrates that a web server providing an RMI codebase service may also be present
in an RMI system. A codebase is a global location for Java class files and Java Archive (JAR) files,
accessible to RMI clients and servers. You can find out more about using codebases in Chapter 9.
Figure 1.3 illustrates RMI over IIOP.In RMI/IIOP, the COS Naming service, accessed via the Java
Naming and Directory Interface (JNDI), is used instead of the RMI registry. In RMI over IIOP you
cannot use RMI Activation, which is only supported under the Java remote method protocol (JRMP).
Find out more about these topics in Chapter 13 and Chapter 14.
RMI reduces the complexities of distributed computing—such as locating the server, network
connections, data transfer, synchronization, and propagating errors—to a simple method call and
exception handler in the client, as shown in Example 1.1.
try
{
result = remoteInterface.method(args);
}
catch (RemoteException ex)
{
// handle a remote exception...
}
class EchoServer
{
public Object echo(Object object) { return object;}
}
class EchoClient
{
public static void main(String[] args) throws Exception
{
EchoServer echo = new EchoServer();
System.out.println(echo.echo("O che bon eccho"));
}
}
We can improve on this, by decoupling the server from the client—specifying an intermediate
interface—and by using a ServerFactory for creating server objects. Such a version might look
like Example 1.3.
class EchoFactory
{
public static Echo getEcho() { return new EchoServer();}
}
class EchoClient
{
public static void main(String[] args) throws Exception
{
Echo echo = EchoFactory.getEcho();
System.out.println(echo.echo("o che bon eccho"));
}
}
An RMI version of the echo service is only a slight modification of Example 1.3, as shown in
Example 1.4.
Code View: Scroll / Show All
class RemoteEchoClient
{
public static void main(String[] args) throws Exception
{
In the RMI version, we made the following changes:
• we imported the RMI packages
• the Echo interface now extends java.rmi.Remote, and its methods now throw
RemoteException
• the EchoServer class now extends java.rmi.server.UnicastRemoteObject
• the EchoServer class now has a main procedure so it can execute in its own JVM
• the EchoFactory now obtains its Echo object via the RMI registry instead of directly
instantiating an EchoServer object
• various exceptions can be thrown; we have shown this by just declaring the main methods to
throw Exception, for brevity, but in practice you will want extensive try ... catch
exception handling.
That's it!
The original version and both client/server versions can only execute in a single Java Virtual Machine
(JVM). The RMI version can execute in a single JVM, in two JVMs on the same computer, or in two
JVMs in two machines connected by a network.
1.7. Exercises
1:Using the Echo service as a template, design and write a date/time service which returns the
current date at the server, and a client which displays the remote date. Since we have not
discussed the RMI runtime environment yet, you do not need to execute the exercise at this
point.
Chapter 2. Characteristics of RMI
Syntax—Semantics—Local method invocation—Remote method invocation—Summary
—Exercises
2.2. Syntax
Syntax is the set of rules governing the arrangement of words. Semantics is the set of
rules concerned with their meaning. In computer languages, the semantics of a statement
define (a) additional compilation rules not forming part of the syntax and (b) how a
statement is executed.
As we saw in the previous chapter, the syntax of a remote method invocation is identical to the syntax
of a local method invocation:
try
{
result = remoteInterface.invoke(arguments);
}
catch (RemoteException ex)
{
// handle a remote exception ...
}
2.3. Semantics
The semantics of local (normal) method invocation are as follows:
• arguments of the proper number and type must be provided so that exactly one matching
method can be found in the class of the object reference[1]
[1]
The Java Programming Language, § 6.9.1.
• a method may or may not be declared to throw an exception
• the caller must catch any checked exception declared to be thrown by the method, except those
which the caller is itself declared to throw
• arguments of object type are passed by reference; arguments of primitive type are passed by
value
• any result of object type is returned by reference; any result of primitive type is returned by
value
• any exception thrown is returned by reference
• if the method returns normally, the method has been invoked exactly once
• if the method throws an uncaught exception to the caller, the method has been invoked exactly
once up to the point where the exception was thrown
• local objects are subject to local garbagecollection via a technique which detects cycles of
garbage.[2]
[2]
ibid., § 12.
Remote method invocation has the same semantics except as follows:
• a method can only be invoked as a remote method via a remote interface which declares it
• a remote method must be declared to throw a remote exception
• clients of remote methods must catch and deal with remote exceptions
• arguments of object type to a remote method invocation are passed by deep copy, not by
reference
• any result of object type of a remote method invocation is returned by deep copy, not by
reference
• any exception thrown by a remote method invocation is returned by deep copy, not by
reference
• an exported remote object is passed or returned by remote reference, not by deep copy
• the semantics of java.lang.Object are specialized for remote objects and remote
references to them
• the RMI system assures that when a remote invocation returns (normally or via an exception),
the remote method has been invoked "at most once"
• remote objects are subject to distributed garbagecollection via a referencecounting technique,
prior to local garbagecollection.[3]
[3]
RMI specification, § 3.3.
2.5.3. Networks
When programming network communications, a number of conditions may arise that don't normally
have to be dealt with when programming entirely locally. Data transmission times over computer
networks are several orders of magnitude slower than within the memory of a single computer. Due to
network failure, data may never arrive at all. TCP/IP connections can fail in ways that cannot be
detected by either party to the connection. For these reasons, a "wait with timeout" operation should
be performed at the client after dispatching a remote method invocation, rather than an indefinite wait.
2.6. Summary
We have seen that although local method calls and remote method calls share the same syntax, they
cannot share the same semantics, and that the differences have a great impact on how RMI systems
must be designed and built.
2.7. Exercises
1:Is it possible to detect at runtime whether method arguments are passed by value or by
reference?
Chapter 3. Serialization
Introduction—Essentials—Serialization in depth—Serialization process—Serializable
interface—Externalizable interface—MarshalledObject—Class versioning—Serial
version UID—Alternative approaches to versioning—Advanced facilities—javadoc and
serialization—Performance—Exercises
3.2. Introduction
In order to execute a remote method, RMI has to transmit the method's arguments from the client to
the server, and transmit the result in the reverse direction. The process of encoding arguments and
results for transmission is called marshalling; the process of decoding them on receipt is called
unmarshalling. RMI performs marshalling and unmarshalling via Java Serialization.
Serialization is the action of encoding an object into a stream of bytes. Deserialization is the action of
decoding such a stream of bytes to reconstruct a copy of the original object. The term "serialization"
is also often used informally to include both processes.
Java Serialization is defined in the Java Serialization Specification supplied with the JDK (all
versions).
3.3. Essentials
Any object to be marshalled and unmarshalled by RMI must be serializable. To satisfy this condition,
it must obey the following simple rules:
1. It must implement the Serializable interface, or its extension the Externalizable
interface discussed later in this chapter.
2. It must have a base class which either (a) is serializable or (b) has a default ("noargs")
constructor which is accessible to the serializable class.
3. Any member field of object type must either (a) refer to an object which is serializable
according to all these rules recursively, (b) be null, or (c) be declared as static or
transient.
4. It need not provide any methods: Serializable does not declare any member functions; it
is just a marker interface.
5. For performance reasons, it should declare a serialVersionUID member as provided by
the serialver program in the JDK: this also enables serialization's automatic classversioning
feature.
If a nonserializable class is encountered during an RMI call, a NotSerializableException is
thrown. This exception manifests itself at the client as a MarshalException or
UnmarshalException, with the detail member containing the original exception.
For most classes, the principles outlined in this section are sufficient. The remainder of this chapter is
an indepth discussion of serialization. To explore these topics, or if you want a deeper understanding
of what is going on, read on: otherwise, skip to the next chapter.
3.4.1. Motivation
Writing the contents of an object to a foreign medium such as a file or a network is a fairly trivial
exercise. However, doing it so as to be able to reconstruct an identical object from the medium,
possibly on a different computer or even a different type of computer, is a nontrivial exercise. Before
Java, when carrying this out in C and C++, we had to cope with a number of issues:
• byteordering differences: "bigendian", "littleendian"[1]
[1]
The terms originate from the dispute between Lilliput and Blefescu over how to
eat a boiled egg in Jonathan Swift, Gulliver's Travels, Part I Chapter IV, Motte,
London, 17267. In computer hardware design, "bigendian" refers to the practice
of placing the most significant byte of a 16bit word at the lower address, and
"littleendian" to placing it at the higher address.
• type alignment differences (e.g. whether 32bit quantities need to be aligned on 32bit
boundaries or may be aligned on 16bit boundaries)
• type size differences (e.g. the actual size of an integer—16 or 32 bits? 64?)
• handling embedded pointers/references to other objects, which may in turn contain further
embedded pointers/references, …
• transmitting the type of the object being written, or at least recognizing it at the receiving end.
Serialization solutions for C and C++ include XDR (eXternal Data Representation, a component of
Sun RPC), or the IDLs (Interface Definition Languages) and supporting infrastructures of heavyduty
distributedcomputing environments such as Distributed Computing Environment (DCE) and
Common Object Request Broker Architecture (CORBA).
Java eliminates several of these issues by using fixed byteordering, type alignment, and type size,
regardless of hardware platform.
The issues of encoding the object type and embedded pointers (references in Java) are addressed by
Java Serialization.
When encoding embedded pointers or references, we are really encoding the nodes reached while
traversing an object graph.
After an object has been serialized, the shape of the object graph, including any multiple references or
cycles, should be preserved when deserializing. This requires "memory" in the encoding process, to
know when an object is reached which has already been serialized, to avoid encoding it again as a
different instance.
For example, if we serialized and deserialized the object graph rooted at A labelled "Indegree of C >
1" in Figure 3.1, we want to get back an object graph of the same shape (one where C has the same in
degree), rather than the object graph in Figure 3.2.
Otherwise our receiving program
is going to get duplicate objects
where it should get the same
object, and possibly end up
updating one copy but reading the
unchanged copy: this could lead
to some very surprising—
incorrect—results.
The object graph in Figure 3.2 contains n identical copies of C, where n is the original indegree of C.
This can arise in serialization systems which don't specially handle an object they have already
serialized.
Similarly, we would like to be able to serialize the cyclic object graph in Figure 3.1 without putting the
serialization system into an infinite loop.
A writeObject method should normally first call defaultWriteObject on the
ObjectOutputStream parameter provided. The defaultWriteObject method performs the
default serialization for the class. The writeObject method should then call ObjectOutput or
DataOutput methods on the stream to encode any other desired state. Extra data written explicitly
by the writeObject method are referred to as the optional data.
The readObject method allows a class to control the deserialization of its own fields:
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException;
A readObject method should normally first call defaultReadObject on the
ObjectInputStream parameter provided. The defaultReadObject method performs the
default deserialization for the class. The readObject method should then call ObjectInput or
DataInput methods on the stream to decode any optional data—in the same order as the
corresponding writes in the writeObject method.
If the readObject method doesn't read all the optional data written by the writeObject
method, an OptionalDataException will be thrown. If the readObject method attempts to
read more optional data than was actually written by writeObject, an EOFException will be
thrown: this can be caught inside the readObject method; otherwise it will terminate the entire de
serialization.
The readObject and writeObject methods do not need to concern themselves with state
belonging to base classes or derived classes, nor with any state which is handled adequately by the
default serialization mechanism.
The readObject and writeObject methods of a class may omit the initial call to
defaultReadObject and defaultWriteObject respectively if (a) they do so
symmetrically, and (b) they are prepared to assume complete control over the state of base
classes and serialization of the current class, e.g. for encryption purposes. This technique
is only useful when the immediate base class is not serializable, or when all serializable
base classes provide mutators for—or protected access to—all their serializable data. The
technique is occasionally used when encryption is required in the serialized form.
By the rules of Java, a class which implements this interface must provide these methods, either
directly or by inheritance. These methods completely replace the default serialization of
Serializable, and place all the responsibility for serialization and deserialization of the class in
the implementor's hands, including responsibility for encoding and decoding the state of all base
objects.
Typically, the readExternal and writeExternal methods of an Externalizable class
whose base class is also Externalizable will start by calling super.readExternal and
super.writeExternal respectively.
3.8. MarshalledObject
To serialize a remote reference other than implicitly via RMI (e.g. to a file) you must use the
java.rmi.MarshalledObject class. A marshalled object is a container for an object that can
be passed as a parameter or returned as a result in an RMI call, but whose actual deserialization is
deferred until explicitly requested via a MarshalledObject.get operation.[3]This allows an
object to be passed along a chain of programs without all the intermediaries in the chain needing the
object's class definition. As part of this mechanism, a marshalled object also preserves the codebase
of the contained object: codebases are discussed in Chapter 9. Marshalled objects are used extensively
in the Activation system discussed in Chapter 10.
[3]
RMI specification, §7.4.9.
The object contained in a marshalled object can be a serializable object, a remote object, or an entire
object graph of serializable and remote objects.
That's what you can't do. What's left? These rules do permit rearranging existing fields, and adding
new fields. In addition, the types of nonprimitive members can be changed in typecompatible ways:
for example, the type of a String member can be changed to Object, because objects of type
String can be assigned to objects of type object.[8]
[8]
Note however that this may break the code of the class in other ways. We are only
concerned in this section with compatibility under serialization.
3.9.3. Limitations—RMI/IIOP
RMI over IIOP (see Chapter 14) does not use default serialization as a lowerlevel object transport. Its
versioning properties are not specified by Sun.
Of the relevant specifications, the CORBA 2.3 specification § 10.6.2[9] merely states: "When an orb
run time receives a value [which has been versioned], it is free to raise a bad_param exception. It
may also try to resolve the incompatibility by some means. If it is not successful, then it shall raise the
bad_param exception" (emphasis added).
[9]
http://cgi.omg.org/cgibin/doc?formal/981201
However, the CORBA 2.3 JavatoIDL Language Mapping specification § 28.3.5.6 states: "If the class
does not implement java.io.Externalizable but does have a writeObject method, then
… all the semantics of java.io.ObjectOutputStream and
java.io.ObjectInputStream supported by RMI over jrmp are supported over IIOP".[10]
[10]
http://www.omg.org/cgibin/doc?orbos/980310
In other words, you will get automatic versioning of a class over IIOP if the class (a) implements
Serializable but not Externalizable and (b) provides an writeObject method.
Normally this would be the trivial implementation:
private void writeObject(ObjectOutputStream out) throwsIOException
{
out.defaultWriteObject();
}
Both the Sun and the IBM Java ORBs are intended to support class versioning even
without a writeObject method, although this fact was not documented at the time of
writiing.[11] However, a simple experiment with the JDK 1.3 RMI/IIOP implementation
shows that, without a writeObject method, the only versioning feature supported by
RMI/IIOP is rearrangement of fields; sending a class version with added or deleted fields
causes an exception to be thrown at the receiver. JDK 1.3.1 contains a correction to this
problem, although "there will be interoperability problems when J2SDK 1.3.1 tries to
send any evolved classes to J2SDK 1.3".[12] See Bug Id 4365188 in the Java Developer
Connection Bug Parade for details.
You should test your own version of the JDK before relying on any form of class
versioning over IIOP.
[11]
Technology Architect, IBM Java Technology Centre, private
communications, 1315 December, 2000.
[12]
JDK 1.3.1 (beta) Release Notes.
If later we need to add a data field—the addressLine3 field—in a backwardscompatible way, it
might look like Example 3.2. Note that we can add a field anywhere, not just at the end, and note that
we didn't update the suid field.
A further revision might look like Example 3.3. We have added two more fields: homePhone and
fax. Note that again we didn't disturb the original suid.
Finally, note that, apart from defining the suid in the original version, we didn't have to write a single
line of code anywhere to implement automatic versioning across these three versions of the class. We
didn't have to provide readObject and writeObject methods; if we had provide them in the
correct form (i.e. starting with defaultReadObject or defaultWriteObject calls
respectively), we wouldn't have to modify them for each revision.
In contrast, externalization—implementing the Externalizable interface—leaves all provision
for class versioning in the hands of the implementor. It is up to you.
This mechanism also ensures that data cannot be reordered between transaction versions.
This constraint is unnecessary from the point of view of serialization, which can cope
with reordered data fields.
Versioning by derivation also allows new code to be introduced into a transactionversion class chain
without disturbing prior versions. In particular, this means without disturbing—or even recompiling—
the prior source code, or having to reinstall it. This has obvious benefits in software development
environments where there are strong sourcecode control régimes. It also reduces installation or
"rollout" costs and changemanagement risks. It is also a good use of the objectoriented properties of
Java, specifically the feature of polymorphism: "an object of an extended class can be used wherever
the original class is required".[14]
[14]
The Java Programming Language, § 3.
You might well use this technique in combination with code mobility.
Before actually serializing an object, its writeReplace method, if any, is called. This method
returns an Object to be serialized. By default, if no writeReplace method is present, this is the
same object. However, a writeReplace method may substitute a surrogate object in the output
stream.
After deserializing an object, its readResolve method, if any, is called:
ANY-ACCESS-MODIFIER Object readResolve()
throws ObjectStreamException;
This method also returns an Object representing the result of the deserialization. By default, if no
readResolve method is present, this is also the same object as was just deserialized. However, a
readResolve method may substitute a surrogate object in the input stream.
Note
If the original class's writeReplace method returns an object of a different class, the
readResolve method of the different class will be called on deserialization, not the
readResolve method of the original class. To use this technique successfully, either the
writeReplace and readResolve methods must return objects of the same class as the original
object being serialized, or the class of the object returned by writeReplace must implement a
readResolve method which returns an object of the original class.
A class which replaces itself on serialization is shown in Example 3.6.
The replacement class—the class which is actually serialized—is shown in Example 3.7.
The writeReplace method is also useful as an opportunity to adjust the values of member fields
prior to serialization. Suppose that for some reason you wanted to adjust the values of your member
fields before they are serialized. However, when writeObject is called, the serialization system
has already decided which fields will be serialized by defaultWriteObject, and their values, so
it is too late to modify their values in the writeObject method. However, it is not too late to do so
in the writeReplace method.
A class which rewrites itself prior to serialization is shown in Example 3.8. If you use this technique,
obviously the writeReplace method should return this.
Example 3.8. Class which rewrites itself prior to serialization
3:Show the indegree and outdegree of every object in the object graphs pictured in this chapter.
4:Can an object graph contain an object whose indegree and outdegree are both zero?
5:Can an object graph consist of a single object whose indegree and outdegree are both 1? 2? N?
Chapter 4. Remote interfaces
Introduction—Proxies—Dispatchers—Exercises
4.2. Introduction
RMI clients invoke methods in remote objects via remote interfaces. A remote interface defines a
remote service. A remote interface is a Java interface which extends java.rmi.Remote.
The interface java.rmi.Remote extends no interfaces and exports no methods. It is a marker
interface which distinguishes remote interfaces from nonremote interfaces. It appears in the
signatures of various methods in the RMI application programming interface (API), and it is
significant to the RMI stub compiler rmic.
A remote interface must satisfy the following conditions:
• it must extend java.rmi.Remote
• every method which it exports—either explicitly or by inheritance—must declare that it
throws RemoteException, or one of its base classes, in addition to any other exception it
may throw[1]
[1]
This rule is not enforced by the Java language, but by the RMI stub compiler
rmic (to be encountered in § 7.7).
• when a remote object can be marshalled as a parameter or result of a remote method, it must
be declared as its remote interface, not its actual implementation class
This last condition is a frequent "trap for young players". You don't receive an exported remote object
via a parameter or result of a remote method call—even if one was sent. What is received is a
surrogate or proxy—another object, of a different class, which implements the same remote interface.
In RMI this object is known as the proxy object discussed immediately below.[2]
[2]
Proxy is a design pattern, discussed in § 12.8.
For this reason, the type signature of the associated parameter or return value must be the only thing
the remote object and its stub have in common, i.e. the remote interface they both implement. For
example, in the RemoteEchoFactory class of Chapter 1, the type of the result of
Naming.lookup is RemoteEcho, not RemoteEchoServer.
This rule also applies to any other remote objects reachable during marshalling of results or
parameters; in other words, to any remote objects which are members of the object graph of a remote
method parameter or result.[3]
[3]
Object graphs are discussed in § 3.4.2.
This rule is enforced at runtime, not by the Java language. If you break it, a
ClassCastException is thrown.
Marshalling is the process of packing up the parameters to a remote method at the client
prior to sending the call package to the server, or the process of packing up the result at
the server prior to returning the result package to the client.
4.3. Proxies
An RMI proxy is an object which acts at the client as an implementation of a remote interface, and
which communicates with the real remote object over the network. RMI proxies are usually remote
stubs. They are substituted for remote objects automatically by RMI when marshalling parameters to
remote method calls, or results from them.
The class definition for a remote stub is generated from the corresponding remote server class by the
RMI stub compiler rmic, discussed in Chapter 7.
4.4. Dispatchers
RMI Proxies are a mechanism on the client side. The corresponding mechanism on the server side is
called the dispatcher. A dispatcher mediates between the RMI runtime and the corresponding remote
object (which is local in the server JVM): it receives the call packet and dispatches the call to the
remote object. JDK 1.1 uses skeletons as dispatchers; JDK 1.2 can also use an internal mechanism
using Java Reflection.[4]
[4]
For information on Java Reflection see the online JDK documentation: package
java.lang.reflect.
A class definition for an RMI skeleton is generated if the v1.1 or vcompat (default) parameters to
rmic are used. The skeleton is required by JDK 1.1 server environments, and by servers whose clients
are using the JDK 1.1 stub protocol. In the 1.2 stub protocol, the internal dispatcher is used.
4.5. Exercises
1:Which of the following are valid remote interfaces, and what is wrong with the ones which are
not?
Code View: Scroll / Show All
import java.io.*;
import java.rmi.*;
import java.util.Date;
interface MyFirstRemote
{
void remoteMethod();
}
5.2. Introduction
RMI clients are rather simple things. They barely differ from clients of local objects — i.e. normal
selfcontained Java programs. RMI clients acquire objects, invoke methods on the objects, use the
results, and catch exceptions thrown by the methods. RMIspecific programming only arises when
acquiring remote objects from a naming service, and this is really namingservice programming, not
RMI programming.
Otherwise, programming an RMI client is just a matter of programming to one or more remote
interfaces. Each method exported by a remote interface is declared to throw a remote exception, and
this must be caught somewhere in the client — like any other declared Java exception. As we saw in §
1.6, RMI at the client reduces to this:
try
{
result = remoteInterface.method(args);
}
catch (RemoteException ex)
{
//...
}
This all sounds good when you say it fast, and it is, except for the issue of remote failure.
5.5. Latency
Clients of remote objects must deal with latency: the client must wait — be latent — for the server to
execute. How long this should take, and how long is too long, depends on a number of factors,
including:
• network bandwidth
• network load (fraction of nominal bandwidth currently available)
• server capacity (CPU speed, disk speed, database performance, and so on)
• server load (queue lengths, paging degradation, and so on)
• expected time the remote method takes to execute, measured at the server
• invocation delays due to RMI in general
• invocation delays due to RMI Activation: in particular, the delay in starting up a new
activation group.
By default, an RMI client waits forever for a remote method to complete. A timeout can be set on the
client socket by using a custom socket factory: see Chapter 11. If a remote method takes longer than
the timeout you have specified via a client socket factory, an InterruptedIOException will be
thrown, which will appear at the client with an UnmarshalException wrapped around it.
5.6. Applets
A Java applet can be an RMI client. An applet does not generally have sufficient permission to be an
RMI server.
Applets always run under the control of a Java security manager. This topic is discussed in Chapter 8.
For the present, we just note that a security manager checks practically all interactions with the
outside world against a predefined set of permissions, and causes a
java.lang.SecurityException to be thrown if the action is not permitted.
Applets don't have many permissions, but they do generally have permission to communicate with the
host from which they were loaded. This means that applets can communicate with RMI servers and an
RMI registry at that host without special permissions having to be configured.
Consult the JDK documentation, and browser documentation, for further information on how to
construct an applet, and how to configure extra permissions for an applet.
5.7. Exercises
1:Construct an applet version of the unmodified RemoteEchoClient and run it via the Java
appletviewer.
2:Modify the RemoteEchoClient to set a default socket factory — via
RMISocketFactory.setSocketFactory — which returns client Sockets with a 100
ms timeout (via Socket.setSoTimeout). Execute the client a few times at various intervals,
and describe the results. (For information on socket factories see Chapter 11.)
3:Experiment with timeout period of the previous exercise to determine the shortest practical client
timeout on a busy LAN.
Chapter 6. Naming I—RMI registry
Purpose—How it works—Names in the registry—Naming class—Registry interface
—registry exceptions—Names and URLs—Setup—Configurations—Utilities—
Alternative naming services—Exercises
6.2. Purpose
"The Registry is a remote object that maps names to remote objects."[1]
[1]
Online documentation, JDK 1.3.
The RMI registry is a naming service: it provides a nametoaddress lookup service like the white
pages in a phone book. An RMI server is listed under a name. The listing for the server contains its
RMI address, an equivalent of a phone number. Like the phone book, the registry is a set of {name,
address} pairs as illustrated in Figure 6.1
You are not obliged to use the registry in an RMI application. You must either use some naming
service: either the RMI registry discussed in this chapter, a JNDI or Jini service as discussed in
Chapter 13; or else use activatable stubs exclusively, as discussed in Chapter 10, with a
MarshalledObject bootstrapping technique.
where:
• the optional rmi names the protocol, which may be omitted but must be "rmi" if present
• the optional host is the host on which the desired registry is running
• the optional port is the port it is listening on (default 1099)
• name is the registry name to be bound, unbound, or looked up: name is omitted when calling
the list method.[4]
[4]
Actually all the syntax is optional, as long as some part of it is specified. Try
the ListRegistry program presented later in the chapter with the following
arguments: (a)"rmi:", (b)"//", (c)":1099".
These methods all throw RemoteException: see § 6.7
6.8.1. Recommendation
Even though Naming takes URL arguments, the protocol and hostname parts of the URL have
default values, so arguments to the Naming.bind, Naming.rebind, and Naming.unbind
methods can just be boundnames. Using this technique eliminates a possible source of error. The
Naming.bind, Naming.rebind, and Naming.unbind methods can only be executed on the
same host as the registry. In other words, the only valid hostname in the Naming URL is the current
hostname or localhost, which is also the default host supplied by Naming. The rmi: protocol is also
the default.
6.9.3. Deployment
If you are using the RMI registry as the naming service, at least one RMI registry is required per host
in which RMI servers are running. The reason for this is that the registry only permits bind, rebind,
and unbind calls originating from the same host, for security reasons. Other naming services may
require different configurations.
This statement may seem too broad, but it isn't. Without using complicated techniques
such as serializing remote objects via MarshalledObjects, only a Java object in the
same JVM can obtain a reference to an unbound RMI service, and that object can only
bind the reference to a registry in the same host as itself. Usually—but not invariably—
the server object binds itself.
6.11. Utilities
Code View: Scroll / Show All
import java.rmi.*;
import java.util.*;
/**
** List the names and objects bound in RMI registries.
** Invoke with zero or more registry URLs as command-line arguments,
** e.g. "rmi://localhost", "rmi://localhost:1099".
*/
public class ListRegistry
{
public static void main(String[] args)
{
System.setSecurityManager(new RMISecurityManager());
for (int i = 0; i < args.length; i++)
{
try
{
String[]list = Naming.list(args[i]);
System.out.println("Contents of registry at "+args[i]);
for (int j = 0; j < list.length; j++)
{
Remote remote = Naming.lookup(list[j]);
System.out.println((j+1)+".\tname="+list[j]
"\n\tremote="+remote);
}
}
catch (java.net.MalformedURLException e)
{
System.err.println(e); // bad argument
}
catch (NotBoundException e)
{
// name vanished between list and lookup - ignore
}
catch (RemoteException e)
{
System.err.println(e); // General RMI exception
}
}
}
}
You can test this program by starting the RMI activation daemon rmid and then running
ListRegistry with the argument rmi://localhost:1098.
6.13. Exercises
1:Enhance the ListRegistry program to show the codebase associated with each remote
object found in the RMI registry. The codebase information is found from the Class of the
remote object.
2:To implement persistence in an RMI registry, we could use two simple Java programs: one to
dump a registry, and one to reload it from the dump. Write a program to dump an RMI registry
to a persistent store in a file, using serialization.
3:Write a program to restore an RMI registry from the file produced by the previous exercise.
Chapter 7. Servers I—unicast servers
Introduction—Writing the server—Implementing remote interface methods—Threads,
sockets, and ports—the Unreferenced interface—Building the server—Foundation
classes—Serialization—Alternative server classes—Exercises
7.2. Introduction
In objectoriented programming, any object can be considered to be a server. In RMI, remote objects
are remote servers. An RMI server is a remote object which:
• implements a remote interface
• is exported to the RMI system.
RMI provides several base classes which can be used to define server classes. These classes form an
inheritance chain shown in Figure 7.1.
RemoteObject provides basic remote object semantics suitable for servers and stubs.
RemoteServer provides getClientHost and getLog methods for use by servers.
UnicastRemoteObject supports simple transient pointtopoint RMI servers.
A unicast remote object is a server object whose characteristics are as follows:[1]
[1]
RMI specification, §5.3.
• references—remote stubs—to such objects are valid only for, at most, the life of the process
that creates the remote object
• communication with the remote object uses a TCP transport
• invocations, parameters, and results use a stream protocol for communicating between client
and server.
"Unicast" indicates pointtopoint communications (as opposed to broadcasting or multicasting: these
advanced topics are discussed in Chapter 17). A unicast remote object is a TCP/IP server which listens
at a TCP/IP port.
import java.rmi.*;
import java.rmi.server.*;
In this case, your server inherits remote object semantics from RemoteObject. Its behaviour under
cloning and nonRMI serialization is up to you. Other than inheriting remote object semantics and
various public static methods, such a server is identical to a server constructed as discussed in the
following section.
As RemoteServer only exports static methods, there is little to choose between
extending RemoteServer or RemoteObject.
If your remote class doesn't directly or indirectly extend RemoteObject, its remote object
semantics and its behaviour under cloning and nonRMI serialization are all up to you.
7.3.4. Exporting
We saw above that a remote object which extends UnicastRemoteObject is automatically
exported on construction. By contrast, a remote object which does not extend
UnicastRemoteObject must be explicitly exported by one of the static
UnicastRemoteObject.exportObject methods. The object is exported as a transient point
topoint server.
Such an object may export itself. Alternatively, it can be exported by whoever constructs it, or can be
exported by some (local) recipient of the constructed object. These alternatives allow the interesting,
possibly very confusing, option of defining a class with both local and remote behaviour. If an object
of such a class is exported, it is available for RMI and local use; otherwise it functions purely locally.
UnicastRemoteObject provides several static exportObject methods, corresponding to its
various constructors used by remote objects not derived from UnicastRemoteObject. The
various forms of this method allow derived classes to choose between (a) exporting on a default port
chosen at runtime, (b) exporting on a specified port, and (c) exporting on a specified port with
specified client and server socket factories (discussed in Chapter 11).
7.3.5. Unexporting
A remote object is automatically unexported—deregistered from the RMI system—when it is
garbagecollected locally. Local garbage collection can only occur after any clients of the remote
object have had their remote references to it garbagecollected in their own local JVMs—an event
which is notified by RMI's Distributed Garbage Collection (DGC) subsystem. This topic is discussed
in §18.2.
When using a shared TCP port, the listening port is closed when all remote objects
exported via that port have been unexported.
Normally it is safest to allow remote servers to be unexported automatically, for reasons discussed in
§7.6. However, conditions can arise in which a remote object must be forcibly unexported regardless
of the consequences to clients. For this purpose, UnicastRemoteObject provides a static
unexportObject method, allowing a remote object to be "manually" unexported from the RMI
system. (This method is not present in JDK 1.1.) The unexportObject method provides a force
parameter, which can be set to true if the object should be forcibly unexported regardless of whether
or not remote calls are currently in progress, or false otherwise. The unexportObject method
throws a NoSuchObjectException if the object is not currently exported at the time of the call
(regardless of the value of the force parameter); otherwise, the return value indicates whether or not
the call was successful, i.e. if force was false, whether remote calls were in progress. (Obviously
if force is true and the object is currently exported, the call always succeeds and returns true.)
7.3.6. Summary
The above information on writing the server in summarized in Table 7.1.
extends
extends extends
RemoteServer or
UnicastRemoteObject other
RemoteObject
However, a remote method implementation which calls other remote methods is still liable to throw a
RemoteException, which therefore must still be declared. The Java compiler will detect this case.
7.5. Threads, sockets, and ports
When a remote object is exported, it is registered with the RMI runtime system. This implies a
number of "behindthescenes" activities:
• a listening TCP port—a ServerSocket—and a listening thread (listener) are created if
necessary
• the remote object is associated with the listening port and listener
• when an incoming connection is accepted by the listener, a connection to the client is formed
• when an incoming RMI call is received on a connection, it is dispatched to the object and
method concerned, on a thread determined by the implementation
• clients may make a new connection or reuse an existing connection for any RMI call.
7.5.1. Threads
The RMI specification says "A method dispatched by the RMI runtime to a remote object
implementation may or may not execute in a separate thread. The RMI runtime makes no guarantees
with respect to mapping remote object invocations to threads. Since remote method invocation on the
same remote object may execute concurrently, a remote object implementation needs to make sure its
implementation is threadsafe." [6]
[6]
RMI specification, §3.2.
In other words, the RMI implementation is free to dispatch an incoming RMI call on any thread. It
may dispatch them all consecutively to the same thread; concurrently to many threads; or any other
way it likes. There is no implication that there is—or is not—one specific dispatch thread per
exported object. There is no guarantee about mapping clients, or client threads, to server threads.
There are no guarantees and you can make no assumptions. Your implementations of remote methods
must be threadsafe. It is up to you.
This information is most important when doing concurrency analysis.
This interface is typically used for two purposes: to schedule idletime activity of the server, and to
allow a remote server to exit when idle.
Option Comment
v1.1 Generates stubs which can be used by clients running under JDK 1.1 or later; also
generates skeletons. This generates the same kinds of stubs and skeletons as the JDK 1.1
rmic, which does not support the v option at all.
v1.2 Generates stubs which can only be used by clients running under JDK 1.2 or later.
vcompat Generates "compatible" stubs, which behave like 1.2 stubs if JDK 1.2 is installed,
otherwise like 1.1 stubs, at a slight cost in code size and class initialization time. If no v
option is specified, this is the default.
rmic is invoked with the command syntax:
rmic [options] class ...
where options is one or more of the supported options, and class is the Java qualified name of a
remote class: a class which implements a remote interface. For each class XXX, rmic generates a stub
class whose class file is named XXX_Stub.class. If the v1.1 or vcompat option is specified, or if you
are using JDK 1.1, it also produces a skeleton class whose .class file is named XXX_Skel.class. You
must avoid defining classes with names of these forms, so as to avoid clashing with classes generated
by rmic.
The stub and skeleton classes generated by rmic are defined in the same package as the server class.
Up to and including JDK 1.2.2, these class files are produced in the current directory. From JDK 1.3,
the class files are produced in the same directory as the source class. In either case, if the behaviour is
not what you want, specify the directory with the d directory option. In our experience the JDK 1.3
behaviour is what you want, and indeed this is why it was changed.
Consult the JDK documentation for full details on rmic.
[a]
This case shouldn't arise, because the stub must be generated by the JDK which is used
to support the server. JDK 1.1 always generates 1.1style stubs. If the situation is
constructed artificially, a runtime protocol error may result.
[b]
This case shouldn't arise, for a similar reason to the above: JDK 1.1 can't generate a
1.2style stub.
If you are not deploying servers or clients with JDK 1.1, you should use the v1.2 parameter to rmic,
which does not generate skeleton classes—one less thing to install at the server.
7.8.1. RemoteObject
java.rmi.server.RemoteObject is the abstract base class for the standard RMI server and
stub classes. It implements remote object semantics by overriding the methods for equals,
hashCode, and toString. The hashCode and equals methods are implemented to allow
RemoteObjectreferences to be stored in hashtables and compared:
• the equals method returns true if two RemoteObjects refer to the same remote object
• the hashCode method returns the same value for all remote references that refer to the same
underlying RemoteObject (because references to the same object are considered equal)
• the toString method "is defined to return a string which represents the remote reference of
the RemoteObject".[8]
[8]
RMI specification, §5.1.1.
Equality of two RemoteObjects is not tested by comparing the contents of the objects,
because (a) that would require two remote method calls, which would be slow, and (b)
these method calls could throw a remote exception, which the equals method can neither
deal reasonably with nor throw.
Objects that require these remote semantics may extend RemoteObject, typically via
RemoteServer, UnicastRemoteObject, or Activatable. RemoteObject also provides
a getRef method which returns the remote reference for the object.
A remote reference is an internal handle for a remote object, used by remote stubs to carry
out remote method invocations to the remote object, or by remote servers to export
themselves or get the client's hostname.
7.8.2. RemoteServer
java.rmi.server.RemoteServer is the abstract base class for the classes
UnicastRemoteObject and Activatable. It provides a getClientHost method, which
returns the hostname of the current client (or throws a ServerNotActiveException if not
called within a remote method invocation), and getLog and setLog methods, for controlling server
logging.[9]
[9]
All these methods are static. Up to JDK 1.3, the JDK documentation described
RemoteServer as also providing abstract methods which a concrete implementation class
such as UnicastRemoteObject must implement: this statement is incorrect in all
versions of Java we have seen.
7.9. Serialization
If an exported remote object is also serializable, it is still passed by reference. In other words the
recipient receives a remote reference to the original remote object, rather than a deserialized copy of
it. The fact that the object is exported takes precedence over the fact that it is serializable.
This statement applies whether or not the remote object extends UnicastRemoteObject.
However, if a UnicastRemoteObject is serialized, either in an unexported state or other than via
RMI,[10] all its nonstatic nontransient state is serialized as usual, including the
UnicastRemoteObject's own port and socket factory settings and any serializable data of the
derived class.[11] When the resulting stream is deserialized, the resulting
UnicastRemoteObject is automatically exported to the RMI system—on the same port and with
the same socket factories, if any, that it had when serialized, or the defaults for these—so that it may
receive remote calls. If the export fails for some reason, the deserialization will fail with an
exception.
[10]
Strictly, other than via a MarshalOutputStream.
[11]
The RMI specification, §5.3.4 says that "information contained in
UnicastRemoteObject is transient and is not saved if an object of that type is
written to a userdefined ObjectOutputStream". This specification conflicts with
Sun's implementation. Indeed, the "export upon deserialization" feature as implemented
relies on these fields being nontransient.
This facility allows RMI servers to be written to files and recovered from them as active entities. It
also allows an inactive RMI server to be passed as a parameter or returned as the result of a remote
method call, whereupon the RMI server is exported at the target. This provides a sophisticated form of
code mobility—see Chapter 9 and §12.3.
However, no method is provided for specifying the required port and socket factories to be
used when exporting the object on deserialization. These parameters are taken from the
serialized object, and cannot be controlled other than by actually exporting the object. If
the object had never been exported, or had been exported with default port or socket
factory settings, the object will be exported using default port and socket factories. If you
need to avoid this, you must ensure that the object has been exported with these
parameters set as required, and then unexported, prior to serializing it.
7.11. Exercises
1:The ExtendedUnicastRemoteObject sample class provided in Example 7.2 has a "no
args" constructor. Can you remove it and let the Java compiler supply it? Explain. (You may use
the Java compiler to experiment.)
2:The following remote interface defines a remote date/time service, which returns its current date
and time to clients. Write an RMI server class which implements this service. (You can use any of
the three techniques described in §7.3. The server must export itself.)
3:Add a main procedure to the date/time server of the previous exercise. The main procedure
should (a) create an instance of the server, (b) register it with the RMI registry under an
appropriate name, and (c) catch and display all exceptions encountered.
4:Write a client for the date/time server which displays the local and remote date and time. Test the
entire system and show the output.
5:Modify the date/time server to export itself on port 1100. Retest with the client and show the
output.
6:Modify the date/time server to implement the Unreferenced interface and trace all calls to the
unreferenced method. Retest with the client and show the output. Is the unreferenced
method called after the client exits? How long should you wait? Now, unbind the server from the
registry with the reg.jar utility described in Chapter 6. Does the unreferenced method
get called after this? Explain.
7:Modify the date/time server to unbind itself from the registry when the remote method has been
called 10 times. Retest with the client and show the output. The server should exit after the client
has been run 10 times, after the expiration of the interval measured in the previous exercise.
Chapter 8. Security I—execution
Introduction—RMI and security managers—Applets—Clients—Servers—System
properties—Policy files—Granting AllPermission
8.2. Introduction
The Java Security Model constrains the actions permitted to Java code according to its source—where
the code came from. The Security Model is activated by installing a "security manager", either an
instance of java.lang.SecurityManager or a derivation of it; for example:
System.setSecurityManager(new SecurityManager());
This statement is usually located early in the main procedure of an application: the same effect can be
obtained by specifying the -Djava.security.manager option on the Java command line. Once
a security manager is installed, it checks all the external actions of the JVM, and a large number of
internal actions, to see if the code which invoked the action has permission to perform it. If the check
fails, a java.lang.SecurityException is thrown.
8.4. Applets
A Java applet, whether running in a browser or in the Sun appletviewer, runs under the control of a
security manager. In browsers whose Java version is prior to Java 2 (JDK 1.2), permissions are
configured in a browserdependent way which is beyond the scope of this book. In browsers
compatible with Java 2, permissions must be granted by a policy file, which can be configured by the
Java policytool.
Applet viewers and Web browsers impose the following restrictions on an applet:
• it cannot load libraries or define native methods
• it cannot ordinarily read or write files on the host that is executing it
• it cannot make network connections except to the host that it came from, and sometimes not
even to that host
• it cannot start any program on the host that is executing it
• it cannot read certain system properties
• windows that it brings up look different from windows that an application brings up.
This list is reproduced from information on Sun's Java Web site and is not intended to be definitive.
Consult the documentation of your target browser(s) for definitive information on applet security.
These restrictions mean that an applet can only be a client of RMI servers located at the host from
which the applet was loaded, and that an applet can usually not be an RMI server.
8.5. Clients
A standalone RMI client must run under a security manager if it is to acquire code from a codebase.
8.6. Servers
RMI servers need not run under a security manager unless they upload client code or are activatable.
8.6.1. Non-activatable servers
From the point of view of RMI, a nonactivatable server doesn't really need to run under a security
manager unless it intends to upload code from codebases other than its own, i.e. codebases provided
by clients or other servers. However, you may find it desirable for application design reasons to install
a security manager, even if this condition does not apply.
The value "access,failure" causes a stack
trace and domain dump before throwing any
SecurityException.
For a list of all possible values for this property, use
java.security.debug=help.
Security exceptions and the actions that lead to them can be "debugged" by setting the system
property java.security.debug=access,failure. This causes a trace to be output on any
SecurityException showing exactly what required permission had not been granted, along with
a stack trace and a dump of the applicable security domain (to be discussed later in this chapter).
Note that SecurityException is a runtime exception, so the Java compiler won't force you to
catch it, and it can be thrown by all sorts of operations in the Java Class Libraries. In a security
managed environment, you should make arrangements to catch this exception at appropriate places: at
the least, in the main procedure of an application, or the init, start and stop methods of an
applet; and in the run methods of all Threads and Runnables, or an override of
ThreadGroup.uncaughtException.
A sample policy file is shown in Example 8.1.
a custom ObjectInputStre
ObjectOutputStream.
[a]
.This permission is intended to distinguish applet windows from nonapplet windows;
however, JDKs 1.2.2 and 1.3 display the warning on nonapplet windows as well as applet
windows unless this permission is granted. Applying this behaviour to nonapplets
appears to be a bug.
[b]
You should grant these permissions very sparingly, i.e. only as widely as required. If
possible, you should design your application so that it only reads files within its own
directory structure.
[c]
.If your application modifies RMI system properties—or indeed any system properties
—after installing a security manager, it will also need permission for the "write" action.
Instead of granting these permissions, you may prefer to read or modify the properties
before installing the manager.
[d]
.After a connection has been accepted, but before the resulting Socket has been
returned to the caller of ServerSocket.accept, a check is made to see if the caller
has permission to accept from the remote host/port pair to which the socket is connected.
This is in addition to the prior check on the local host/port pair, which is done when the
socket entered the "listening" state (i.e. when the ServerSocket was constructed).
[e]
.On Unixlike systems, the ports below 1024 are only available to privileged programs
A privileged block is a block of code executed via the AccessController.doPrivileged
method. Such a block asserts that its own codebase alone should be used to compute the permissions
granted to it, rather than its entire classloading context. For further information, see the JDK
documentation.
9.2. Outline
An RMI server or client can be configured with a codebase—a list of one or more URLs, each of
which specifies a global location for Java class files. RMI transmits the codebase associated with each
transmitted class. The receiving end can automatically use this information to retrieve any classes it
doesn't have installed locally.
Figure 9.1 illustrates classes being downloaded dynamically from an HTTP server on request by a
client.
The codebase associated with a class is (a) the value, if any, of the URL from which it was loaded by
RMIClassLoader.loadClass, otherwise (b) the value, if any, of the Java system property
java.rmi.server.codebase in the JVM which last loaded it from its CLASSPATH.
9.3. How code mobility works
As we have seen in earlier chapters, RMI uses Java Serialization to transport method parameters from
the client to the server, and to transport return values and exceptions from the server to the client. The
process of packaging at the sender, prior to actual transmission, is called marshalling, and the process
of unpacking at the receiver, on receipt of a transmission, is called unmarshalling.
When marshalling an object—specifically, when RMI's implementation of
ObjectOutputStream.annotateClass is called—RMI annotates the marshal stream with
the codebase information for the class, if any. When unmarshalling an object, if its class is not
available locally and if the stream has been annotated with codebase information for that class, RMI
attempts to use the codebase information to download the class dynamically.
where:
• protocol is the URL protocol, usually http for RMIcodebases
• host specifies the location of the resource within the network
• the optional port specifies the TCP/IP port at which the host service is listening, and defaults
to the standard port for the protocol (e.g. 21 for FTP, 80 for HTTP)
• file specifies the location of the resource within the host.
When a class file, say rmibook.quicktour.RemoteEcho, can't be found locally on the
CLASSPATH, the fully qualified class name is converted to a class filename, say
javarmi/quicktour/RemoteEcho.class. What happens next depends on the form of file in
the codebase URL.
1. If file ends with a "/", it is assumed to be a directory under which class files (and other class
resources) are located as individual files in directories corresponding to their package names.
The class filename is appended to the codebase URL to form a lookup URL. The RMI class
loader tries to connect to the lookup URL; if this succeeds, the data is read and the class can
be loaded.
2. If file does not end with a "/", it is assumed to name a JAR file. If it hasn't already happened,
the JAR is retrieved and cached locally, and the local copy is searched for the class filename; if
this succeeds, the class can be loaded.
In either case, if the process fails, the next URL in the codebase is tried. If all codebase URLs fail, a
ClassNotFoundException is thrown.
Sometimes a NoClassDefFoundError is encountered. This is a symptom of a
previous ClassNotFoundException, and usually indicates a stale RMI registry. See
§A.4.11.
A server which implements RemoteService can return any implementation of
InterfaceKnownToClient it likes from RemoteService.method. If the class of the result
is not known to the client, and if the correct codebase conditions exist at the server, the
implementation class will be downloaded to the client.
This technique allows the server to vary the implementation class unbeknownst to the client, for
applicationspecific reasons. It also allows the implementation class itself to evolve without having to
be redeployed to all clients, and without being constrained by the versioning rules of serialization.
The same technique can be used in the reverse direction, allowing the client to supply the
implementation class to the server:
public interface InterfaceKnownToServer { ... }
9.6. Setup
This section discusses the steps needed to allow RMI to download classes from the codebase. RMI
clients can be allowed to do this if an RMI server returns an object whose actual type is not known at
the client. RMI servers can be allowed to do this if a client sends a object in a remote method call
whose actual type is not known at the server.
In the above, "returns an object" and "sends an object" includes both the actual object sent
as a parameter or returned as a result and any object reachable from that object in the
serialization process. "Returns an object" also includes exceptions thrown by remote
method invocations.
Regardless of the server's codebase settings, if the RMI registry is able to load the stub class from its
CLASSPATH it will do so, and if it doesn't have java.rmi.server.codebase set, it won't
annotate the class when it has done so. The result is an unannotated RMI stub class in the registry.
Now, when a client calls Naming.lookup or Registry.lookup, a
ClassNotFoundException will be thrown when the client unmarshals the result, and this will
be wrapped in an UnmarshalException at the client.
Example 9.2 shows a client initialization.
9.8.4. Authentication
HTTP and HTTPS servers (or their proxies) can be set up to require a client to provide authentication
before access to the requested resource is granted.
Normally in a Java HTTP or HTTPS client this will cause an authentication failure. You can provide a
java.net.Authenticator to respond to any such authentication process. In this way the
application can provide the authentication required itself. This information could be obtained from:
• a "login" dialog poppedup during the Authenticator callback
• a prior applicationspecific login process
• a local configuration or profile
• data hardwired into the application.
9.9. Deployment
The simplest way to deploy an RMI application with mobile code is with a single codebase server
serving all clients, as shown in Figure 9.2.
In a larger
intranet,
especially one
with large
numbers of
RMI clients,
interior
firewalls,
and/or slow network segments, a more sophisticated deployment is indicated. Such a deployment
would use a tree of HTTP caching proxy servers to propagate class traffic and reduce the load on the
root HTTP server, as shown in Figure 9.3.
In this kind of configuration, the network is divided into "enclaves", reflecting locality or firewall
configuration. Each enclave has a local HTTP caching proxy server, through which all HTTP requests
are directed.[3] Each such proxy communicates once with the "root" HTTP server to obtain each class
file as requested by a client, and caches the result locally. Thereafter, the proxy is able to satisfy class
file requests for that file from its local cache. This continues until the cache expires or a newer version
of the file requested is observed at the root.
[3]
by setting the Java system properties http.proxyHost and http.proxyPort.
In this way, the traffic to the root server is reduced from that generated by n clients to that generated
by m proxy servers, where n and m can be in any desired ratio.
In a truly enormous deployment, or one with a large number of slow network segments, you might
introduce further intermediate layers of HTTP proxy servers.
Provided the actual class implementing this interface is not available on the client's CLASSPATH, it
will be downloaded from the codebase.
Three variants of this scheme are possible:
• bootstrapping via RMIClassLoader
• bootstrapping via an RMI call
• bootstrapping via a MarshalledObject.
In all cases, a security manager must be installed in the bootstrap program.
The network traffic for class loading can be moderated by interposing HTTP caching proxies with
suitable expiry times, as in Figure 9.3 above.
In a realistic application, the codebase and clientClass values would be acquired via the
command line or an external configuration file, and exceptions would be handled more appropriately.
The downloadedclient technique can be used even if the client proper makes no use of RMI. A
similar technique is used by Java to force downloading of classes used by Java applets.
In this case, the client implementation class and all classes reachable from it must be serializable or
remote.
In a realistic application, the bootstrapServer value would be acquired via the command line or
an external configuration file, and exceptions would be handled more appropriately.
Code View: Scroll / Show All
The setup required for this technique is illustrated in Example 9.6.
This technique doesn't require the client to be able to connect anywhere. The MarshalledObject
can contain arbitrary code to locate the appropriate client object or server; indeed, it could perform a
secondary bootstrap via RMI as described above.
Code View: Scroll / Show All
10.2. Introduction
As we have seen, "unicast" RMI servers are based on the UnicastRemoteObject class, or
exported by its static exportObject method. A client's remote reference to such an RMI server
remains valid as long as the server itself remains running, and no longer. Once the server has exited,
or has been unexported, the reference is no longer of any use. It no longer refers to anything. It will
fail if the client uses it to invoke a remote method. It cannot usefully be saved and restored across a
system shutdown. A client of such a server has to go through the process of obtaining a remote
reference (a stub) every time it runs, starting with a registry lookup.
Activation removes this restriction. A remote reference to an "activatable" RMI server remains valid
for as long as the server it refers to remains registered with the RMI activation system. Any time the
reference to the server is used, the server will be automatically restarted by the activation system if it
is not currently running. The remote reference itself can usefully be saved and restored across system
shutdowns.
You can think of a remote reference to an activatable object as a " persistent" reference.
When a client receives a stub for an activatable server, the stub contains a special form of
RemoteRefwhich initially contains a null "real" RemoteRefand an ActivationID for the
remote object. When the stub is first used for a remote method invocation the RemoteRefis null,
so the stub engages in a protocol interaction with the activation system daemon rmid in the remote
host.[1] The result of this protocol interaction is a live RemoteRefwhich the stub can then use to
invoke the remote method.
[1]
To be specific, it calls ActivationID.active, a local method which calls the
remote method Activator.activate in the target host's activation daemon, using a
bootstrap technique similar to that used to contact the RMI registry.
Behind the scenes, the activation system has taken the following steps:
• looked up the ActivationID
• found the ActivationGroup associated with it
• activated the group if necessary
• told the group to activate the activatable server if necessary
• obtained the activatable server's RemoteReffrom the group, and returned it to the client stub.
If any of these steps fails, an ActivateFailedException is thrown to the client.
10.5. Writing an activatable server
As we saw when discussing unicast servers in §7.3, there are essentially three ways to write a server
using the Activatable class:
• extend Activatable
• extend RemoteServer or RemoteObject
• extend some other class, or no class (i.e. implicitly extend java.lang.Object).
The discussions in §7.3 and §7.4 apply equally to activatable servers, with the exception that an
activatable server must export itself on construction. In addition, the following extra steps are required
when defining an activatable server:
1. Define a constructor of a required form in the server class.
2. Have this constructor export the object, by calling the appropriate baseclass constructor or
exportObject method.
3. Register an activation group, or reuse an already registered one.
4. Register the activatable server.
5. Compile the activatable server.
These steps are discussed in detail below.
where ActivatableXXX should be replaced by the name of the class being constructed. This
constructor is used by the activation system to construct the server on demand.
This constructor is required to do one of two things, depending on whether or not the server is derived
from Activatable. These two possibilities are discussed separately below.
By the rules of Java, if the server we are writing extends Activatable directly, this invocation will
be in the server class itself; otherwise it will be in that class in the server's inheritance chain which
does extend Activatable directly.
depending, again, on whether it does or does not want to specify client and server socket factories.
These methods are public and static in Activatable and so are accessible to all classes.
Unlike the export step for UnicastRemoteObject, which can be deferred, the export step for
Activatables must be complete when the constructor exits.
An activatable server can extend RemoteServer or RemoteObject. In this case, the server
inherits remote object semantics from RemoteObject. Its behaviour under cloning and nonRMI
serialization is up to you. Other than inheriting remote object semantics and various public static
methods, such a server is identical to a server which extends a class other than Activatable,
RemoteServer, and RemoteObject.
As RemoteServer only exports static methods, there is little to choose between
extending RemoteServer or RemoteObject.
10.6. Registration
Registration is the process of making an activatable server known to the activation system as an entity
which can be created and activated on demand.
This should not be confused with the functions of the RMI registry, which provides a name
binding/lookup service.
The process of registration is rather complex. This is mainly because of the complications added by
activation groups and initialization arguments. Further reasons are discussed in §10.15.
The basic registration steps are illustrated in Example 10.1.
In this example:
• MyRemote is the name of a remote interface
• MyActivatable is the name of an activatable class which implements the remote interface
• initialization of the variables file, location, and data is up to you
• data may be null.
These registration steps are discussed in detail below.
1. Construct an instance of Properties, in which you should specify the security manager
settings, including the absolute location of the security policy file for the group.
2. Construct an instance of ActivationGroupDesc.CommandEnvironment—usually null,
unless you want to use a nonstandard path or options for the java command executed when the
group is activated.
3. Construct an ActivationGroupDesc from these two items.
4. Register this group and obtains its group ID. This returns an ActivationGroupID, which you
can subsequently use to register objects within the group, and to unregister the group.
5. Save the group ID. This group ID is a precious thing. If you don't want to keep creating groups ad
infinitum, you should save the group ID somehow—typically in a file—via serialization.
Sun's activation tutorials used to state that you should now explicitly create the group in the
current JVM by calling ActivationGroup.createGroup. This is unnecessary and
undesirable: instead, just use the constructor for ActivationDesc that takes a groupID
parameter. See the discussion in §10.15.2.
1. Create an activation descriptor (an ActivationDesc), specifying the activation group ID;[2]
the object's class name as a String, including package qualifiers—typically the result of
Class.getName; a location—a URL from whence the class definition can be loaded when the
object is activated; and either a null or a MarshalledObject containing initial data for the
object.
[2]
Do not omit the groupID—do not use the constructor for ActivationDesc
which omits this parameter. Otherwise, you must do the
ActivationGroup.createGroup step described above.
2. Register this activation descriptor. The result of this method is an object of type Remote: it is a
remote reference—a remote stub for the activatable erver—which you can use in the same way as
the result of a registry lookup.[3]
[3]
Note this asymmetry: registering a group returns its ID, which can be used to
register objects in it, or unregister the group; registering an activatable server returns a
Remote, which can be used to activate it. In another asymmetry,
Activatable.register returns a Remote, but
ActivationSystem.registerObject returns an ActivationID. The
former is called by users, the latter is called by the system. (See also the footnote for
§10.15.6.)
3. Save the remote reference. Like the activation group ID, this remote reference is also a precious
thing.
You can save it in a file (via serialization), or in a persistent naming service if one is available.
You must ensure that the reference is serialized somewhere, possibly by a client, otherwise the
registration is effectively lost. Registration gave you a persistent remote reference, but you must make
it persist somewhere, otherwise the whole exercise has been pointless.
The activation system lacks lookup and list methods for group IDs, activation IDs,
and remote references to activatable servers; otherwise it would be possible to use the
activation system's own database rather than have to save groupIDs and activatable
references yourself. See also §10.12.1.
The saved reference can be retrieved and used, behaving as a "faulting" reference to the activatable
server. The saved reference remains valid after system reboots, and on different host computers
connected to the same network.
You will probably also bind the reference to a name in the RMI registry: perhaps immediately, to
make the activatable server immediately available to clients; or you can arrange a registry
initialization program to "prime" the registry each time it starts up, using serialized remote references
saved as described above. You could also use the persistent registry technique discussed in the
Exercises of Chapter 6.
The activation system provides two other means of registering activatable servers: during
construction and during export. These are briefly discussed in §10.15.
It's rather difficult to unregister an activatable server. The Activatable.getID method has
protected member access, so only the activatable object can unregister itself, unless you add a public
accessor method for the ID to your activatable server. In the case of an activatable server not derived
from Activatable, you must also save the activation ID provided on construction, so that this
accessor can return it.
Code View: Scroll / Show All
The file supports a wildcard syntax as shown. Sadly, the Java policytool doesn't yet know about
ExecPermission and ExecOptionPermission, but you can type in the permission names
yourself, so you can still use this tool to manage the file. See the JDK documentation for rmid for
further information.
10.9. Activation and the Unreferenced interface
An activatable remote object may implement the Unreferenced interface. This is a reasonable and
recommendable thing for activatable remote objects to do.
In §7.6 we saw that the Unreferenced.unreferenced method is invoked by the RMI system
whenever the number of remote clients of a remote object falls to zero. We saw that the callback can
occur when remote clients still exist. We also saw that for "unicast" remote objects, it is quite
dangerous to use this opportunity to unexport the object, because this causes any future uses of
current remote references to it to fail with a NoSuchObjectException in the client.
However, if the remote object is activatable, this difficulty disappears. All remote references to such
remote objects remain valid and usable until the object is actually unregistered from the activation
system.
When an activatable object receives an unreferenced callback, it is perfectly in order to de
activate the object. This implements an "exit when idle" strategy:
• the remote object will be activated when it acquires its first client
• it will remain in existence while it has remote clients
• it will exit when the number of clients falls to zero
• it will be recreated automatically if the number of clients increases beyond zero again.
If on the other hand you never unexport the remote object, it will never exit while the activation
system (i.e. rmid) remains running. Both of these strategies are reasonable: which of them you want is
up to you.
You should call Activatable.inactive rather than Activatable.unexportObject.
The inactive method first unexports the object if necessary, then tells the activation system that
the object is inactive; i.e. that if further references to it are used, it needs to be reactivated first. Don't
just unexport the object yourself without telling the activation system.
The unexport/inactivation process also clears any local references to the object held by the
RMI system: as soon as all other local references held by the application code are released
the object can be garbagecollected.
The return value of inactive is true if the object could be unexported successfully or was already
unexported, otherwise it is false. This is similar to the return value of the various
unexportObject methods.
An implementation of the "exit when idle" strategy is shown in Example 10.3.
Note that the object is not actually garbagecollected until the JVM's garbage collector decides to do
so, which may be never, and which may vary from platform to platform, JDK to JDK, and JVM to
JVM. You can help things along by calling System.gc and System.runFinalization in the
unreferenced method, but this doesn't guarantee anything. On the other hand, the activation
group itself exits when there are no more active activatables in it: this causes the entire JVM devoted
to the group to exit. This is garbagecollection with a vengeance.
Example 10.3. Implementing "exit when idle" in activatable server
Code View: Scroll / Show All
cast to ActivationSystem, throwing an ActivationException if the activation system is
not bound in the local registry at port 1098.
This implementation of ActivationGroup.getSystem is not mentioned in the
RMI specification, although it is described in the JDK documentation. Other
implementations may use a different technique.
10.12. Debugging
You may find it useful to run rmid with the system property
sun.rmi.server.activation.debugExec set to true and monitor its output. This setting
causes the activation system to log all attempts to activate groups and servers, and all exceptions
arising from those attempts.
You may also find it useful to run rmid with -C-Djava.rmi.server.logCalls = true.
The C mechanism transmits the argument following to all activationgroup processes started by rmid.
In this case it enables remote call logging in all activation groups, which you probably want at all
times.
try
{
myRemote.remoteMethod();
}
catch (RemoteException e)
{
// some RMI exception caught
}
However, activation exceptions generally indicate a serious problem at the other end, and you should
give careful consideration to catching them explicitly. In particular you should treat
UnknownGroupException and UnknownObjectException, which both extend
ActivationException, as extremely serious conditions. ActivateFailedException
indicates that the object couldn't be activated for some reason, which may be transient;
UnknownGroupException and UnknownObjectException indicates that the client is using
an invalid activatable reference: one which doesn't refer to any known activatable service at the host.
This is illustrated in Example 10.5.
try
{
myRemote.remoteMethod();
}
catch (ActivationException e)
{
// unknown group or object: permanent error with stub
}
catch (ActivateFailedException e)
{
// some other activation problem, possibly transient
}
catch (RemoteException e)
{
// some other general RMI problem
}
You don't need to know much about this detailed version of the block diagram, or about most of the
Activation package. The following notes discuss the remaining interfaces, classes, and methods. In
general they are of no concern to RMI programmers unless they are implementing Activation
themselves.
10.15.2. Creating the activation group
ActivationGroup.createGroup is called by the activation system. Calling it yourself is not
generally appropriate. The setup programs in Sun's activation tutorial used to create a group, but only
because they used the wrong constructor for ActivationDesc, the one which doesn't take a
groupID parameter, but uses the default activation group—which must exist, otherwise an
ActivationException is thrown.[5] Creating the group in the setup program makes no sense;
using the wrong constructor and then working around its behaviour is the wrong solution. Use the
constructor which does take an activation group ID.
[5]
According to the implementation and the online JDK documentation. This disagrees
with several statements in the RMI specification, §7.4.8. The behaviour described in the
specification was implemented in beta releases of JDK 1.2, but was changed prior to the
release of JDK 1.2FCS. This is probably the source of the confusion in the activation
tutorial.
10.16. Exercises
1:Modify the remote date/time server exercise of Chapter 7 to be activatable. Test the system and
show the output.
2:Modify this server to implement the "exit when idle" strategy.
3:Modify this server to not implement the "exit when idle" strategy, but instead to be forced into
existence each time rmid starts.
4:Study §10.15. Write a public final class called Activation which exports static methods
implementing all the methods in the Activation interface that a programmer really needs in
practice. Exclude all constructors and methods of the classes Activatable,
ActivationDesc, and ActivationGroupDesc, and all the exception classes.
5:Modify the server created in the exercises above to use the class of the previous exercise rather
than call the activation API directly. Retest the system and show the output.
6:Modify the "exit when idle" version of the activatable remote date/time server, to save its date of
last inactivation by resetting its ActivationDesc, and to display this information when activated.
Chapter 11. Socket factories
Purpose—Server socket factory—Client socket factory—Factory equality—Uses of
socket factories—Remarks
11.2. Purpose
A socket factory is a class which enables you to provide your own Sockets or ServerSockets to
RMI. Applications using RMI can provide their own socket factories, which RMI will use when
constructing server and client sockets on behalf of RMI servers and clients. This facility has two
principal uses:
1. To superimpose a custom protocol, e.g. an authenticating or encrypting protocol, over the RMI
transport protocol.
2. To control the Socket or ServerSocket itself: e.g. to set a client timeout, or to use a
specific serverside listening network interface in "multihomed" hosts.
11.2.1. Java 1
In Java 1 (JDK 1.1.x), it was possible to replace the default RMI socket factory, by calling
RMISocketFactory.setSocketFactory. This replacement could only be performed once,
and took effect for the lifetime of the JVM, affecting all objects.
This feature was of limited use. The socket factory had to be installed during the startup of all
affected servers and their clients. This made it impossible to define a different socket factory per
server, and placed the burden of installing factories on clients as well as servers.
11.2.2. Java 2
Java 2 (JDK 1.2 and following) introduces the concept of client and server socket factories. When
exporting an RMI server, it is now possible to supply two "socket factories"—one for server sockets
and one for client sockets.
With this feature, you can now control both the ServerSocket associated with an exported remote
object and the Socket which a client will use to access that remote object. You can vary this per
server if necessary. For instance, you might use some secure authenticating protocol to obtain the
initial remote reference, and then "trust" everybody who has obtained such a reference—using
standard sockets to communicate with further remote servers.
If you create an embedded RMI registry, you can specify its socket factories as well.
The server controls the client's socket factory. It does this by specifying the client socket factory when
exporting itself. The client socket factory is returned to the client as part of the remote stub for the
remote object. In other words, the client socket factory specified by the server is automatically
downloaded to all clients of that server.
The socket factory facility is provided by the constructors and exportObject methods for the
UnicastRemoteObject and Activatable classes which take
RMIClientSocketFactory and RMIServerSocketFactory arguments.
As client socket factories are intended to be downloaded to clients, they must be serializable—they
must implement one of the interfaces java.io.Serializable or
java.io.Externalizable, or be derived from a class which does.
If your socket factory is final or will never be subclassed, you can simplify the above to an
instanceof test, as shown in the examples later in this chapter.
Note that you should not use an implementation like the following, which just tests whether the other
object is a generic RMI socket factory of any kind:
public boolean equals(Object that)
{
return that instanceof RMIClientSocketFactory; //UNSAFE
}
While you might consider your socket factory to be equal to all other socket factories, the other socket
factory may not agree!
11.6.3. Multi-homing
A network host is said to be "multihomed" if it supports more than one network interface—more than
one IP address. Such a host might be used at the junction of two IP subnets to act as a router between
them, or to virtualize two or more "logical" hosts on a single physical host.
By default, a server socket listens at—is bound to—all available network interfaces. You can force a
server socket to listen at a specific network interface by using the constructor for ServerSocket
which takes three arguments: a port, a backlog, and an InetAddress. The InetAddress
specifies the network interface to listen at. You can force RMI to use such a ServerSocket by
providing a server socket factory which constructs such sockets when exporting the server.
This is the only way in which RMI servers running in a host with multiple IP addresses can control
which IP address they appear on. By default, RMI does not specify the network interface a
ServerSocket should listen at; this yields the default behaviour of ServerSocket, which is to
listen at all local interfaces. This may not be what you want, especially if the interfaces are on
different IP subnets and you only want to export your RMI server to one subnet. A server socket
factory for controlled multihoming is illustrated in Example 11.3.
In this case, the factory's implementation of the Object.equals method should return true if the
factory being compared is of the same class and creates sockets which listen at the same network
interface. A simplistic implementation is shown in Example 11.4.
// Constructor
public MultiHomedServerSocketFactory(InetAddress bindAddr)
{
this.bindAddr = bindAddr;
}
ServerSocket treats an unspecified or null bindAddr parameter as indicating "any address".
To "bulletproof" the equality test in the above, we should allow for the possibility that either
bindAddr field is null, to permit a null bindAddr to be supplied to the constructor of the
socket factory, as shown in Example 11.5.
Code View: Scroll / Show All
11.7. Remarks
The socket factory mechanism is overloaded. It is used internally to implement HTTP tunnelling, and
it can be used externally (by you) to implement multihoming, secure sockets, or other superimposed
protocols. These uses mutually conflict.
A facility to "nest" socket factories, or to build layered protocol stacks (rather like a stack of Java I/O
streams) seems to be required to address this issue.
Chapter 12. Agents and patterns
Introduction—Mobile agents—Callbacks—Mobile servers—Agents and design patterns
—Adapter—Proxy—Clientserver patterns—Singleton—Remote factory—Abstract
remote—Session—Exercises—Remarks
12.2. Introduction
An agent acts on your behalf. As in a spy novel, you send an agent somewhere, or leave him behind to
carry out your instructions when you move.
Agents in RMI exploit two features of Java: serialization and polymorphism. Serialization is the
mechanism used to transport objects by value: refer to Chapter 3. Polymorphism, from the ancient
Greek, is the ability to appear in many forms. In objectoriented programming, it refers to the fact
that, as a derived class satisfies the type signature of its base class, it can be used wherever the base
class can be used, even though it may have quite different implementations of the base class's methods
—a different form.
In Java, polymorphism includes implementations of interfaces as well as derivations of
base classes.
Use of agents forms a major part of RMI design and programming.
In RMI, an agent is passed as a parameter to a remote method call, or returned as a result. A client
sends an agent to a server as a parameter. Conversely, a server sends an agent to a client as a result.
There are several interesting kinds of agent in RMI:
• Mobile agents
• Callbacks
• Mobile servers.
Table 12.1 summarizes these agents.
Table 12.1. Agents in RMI
These are discussed individually below.
Code View: Scroll / Show All
12.4. Callbacks
A callback is an agent which is left behind: if you like, an "immobile agent".
A callback is merely an RMI parameter or result which is an exported remote object. Because a
remote object is transmitted by remote reference, it stays behind and acts at the source.
The structure of a callback is illustrated in Example 12.2.
In this example the caller is its own callback, because it implements the callback interface, but the
callback could have been a separate object.
12.4.1. Limitations—deadlocks
Callbacks which work correctly when the callback object is local—i.e. before RMI is introduced—can
encounter deadlocks when the callback object is remote.
To demonstrate this problem, consider what happens in Example 12.2 if both the run and the
callback methods in the CallbackClient class are synchronized.
When receiver is a remote object, such an implementation of the callback pattern will encounter a
deadlock. The reason is that if receiver is a local object, the callback method is called on the
same thread as the run method, and therefore no deadlock arises. If receiver is remote, RMI calls
the callback method on a new thread created in receiver to deal with incoming calls. This
thread blocks on trying to enter the synchronized callback method, because the original thread is
still waiting inside the synchronized run method—so a deadlock occurs.
The same thing can happen more indirectly, if the callback object is separate from the calling object
but eventually attempts to synchronize on the calling object.
12.4.2. Limitations—firewalls
For reasons discussed in Chapter 15, clients behind Internet firewalls cannot export RMI servers
visible outside the firewall. This means that callbacks can only be used in the servertoclient
direction. The server can send a callback to the client as a result, but the client can't send one to the
server as a parameter. The symptom of the latter is a failure when the server tries to execute the
callback.
12.5.1. Limitations—firewalls
For reasons discussed in Chapter 15, clients behind Internet firewalls cannot export RMI servers
visible outside the firewall. This means that mobile servers can only be used in the opposite—client
toserver—direction. The client can send a mobile server to another server as a parameter, but the
server can't send one to the client as a result. The symptom of the latter is a failure when the client
tries to execute the "callforward" into the mobile server.
12.6. Agents and design patterns
Agents are often adapters or proxies. These are design patterns in their own right, discussed in the
following sections. Design patterns "describe simple and elegant solutions to specific problems in
objectoriented software design", according to the standard book on design patterns,[1] often referred
to as the "Gang of Four" book, or even "GoF" for short.
[1]
Gamma, Helm, Johnson, and Vlissides: Design Patterns: Elements of Reusable Object
Oriented Software.
In a way, agents are themselves design patterns. However, as agents can be adapters or proxies, the
discussion can get confusing if structured that way.
12.7. Adapter
The adapter pattern converts "the interface of a class into another interface clients expect".[2] An
adapter class implements an interface, or extends a base class, expected by a client, and delegates all
its methods to an internal object of a different class. The adapter pattern is used where the client and
the real implementing class have different interfaces. This situation arises continually, and for all sorts
of reasons—RMI semantics, development history, JDK changes, and so forth.
[2]
ibid., pp. 139150.
This is illustrated in skeletal Java code in Example 12.3.
interface Service {}
interface ExpectedInterface {}
An adapter retains a reference to the object whose interface it converts, so that—unknown to its own
clients—it can communicate with it.
Serializable adapters to remote services are useful in RMI, as the following example shows.
Why not? There are two problems with this implementation.
Firstly, the semantics of the InputStream.read methods, which we are trying to imitate, are
incompatible with those of remote methods. As we saw in §2.5, arguments to remote calls are passed
by deep copy, and are not returned after modification at the remote end. This means that the byte[]
arguments to the various InputStream.read methods will not receive result data. We have to
organize an intermediate interface.
Secondly, we would like clients to receive a real InputStream, which they can use in the usual
ways, including the ability to layer a BufferedInputStream, and perhaps a
DataInputStream or ObjectInputStream, on top of it. The RemoteInputStream of
Example 12.4 is not typecompatible with InputStream—there is no typerelation between them—
so it can't be used in constructors for other input stream classes. The following client code based on
Example 12.4 won't even compile:
RemoteInputStream rin = ...;// acquired somehow
InputStream in = new BufferedInputStream(rin);
Both these can be solved with a betterdefined remote interface and a serializable adapter, as
illustrated in Example 12.5.
Code View: Scroll / Show All
This solution has the following elements:
• the class we must be typecompatible with—in this case, InputStream
• the intermediate remote interface RemoteInputStream
• the adapter class InputStreamAdapter
• a server which implements RemoteInputStream
• the real InputStream obtained by the server, probably a FileInputStream or an input
stream attached to a socket.
Note that InputStreamAdapter is typecompatible with InputStream, which it extends. This
means that remote methods can be declared to receive or return InputStream objects: their
implementations actually receive or return InputStreamAdapter objects.
Only the adapter and the real RemoteInputStream server know about the
RemoteInputStream interface. Clients and other remote interfaces are written in terms of
InputStream objects.
This solution takes advantage of the fact that RemoteException extends IOException. There
are objections to using this technique: see the discussion in §12.8.2.
To complete the example, we only need to do the following:
• provide a remote service implementation of RemoteInputStream
• add a method to some remote interface which returns an InputStream
• implement this method in a remote server to return an InputStreamAdapter attached to
the RemoteInputStream implementation.
12.8. Proxy
A proxy is a surrogate for another object, with which it is usually typecompatible.[3] That is, a proxy
is usually another implementation of an interface implemented by the object being substituted, or
another extension of one of its base classes.
[3]
Gamma et al., pp. 207218.
This is illustrated in skeletal Java code in Example 12.6.
interface Service {}
A proxy retains a reference to the object being substituted, so that, unknown to its own clients, it can
communicate with it.
The proxy pattern has all kinds of uses in situations where some sort of intermediate control is
required over accesses to the object being substituted.
A number of interesting specializations of the proxy pattern are described below.
Unlike the first attempt at the remote input stream above, this solution will actually work. The remote
input stream's problem with the semantics of the byte[] parameter does not arise, as the data is
being sent in the same direction as the call.
However, as in the case of the remote input stream example above, we would like clients to receive a
real OutputStream, which they can use in the usual ways—including the ability to stack a
BufferedOutputStream and/or a DataOutputStream or ObjectOutputStream on top
of it—but this RemoteOutputStream is not inherited from OutputStream. There is no type
relation between them, so a RemoteOutputStream cannot function polymorphically as an
OutputStream.
To solve this, we need an intermediate class. Unlike the remote input stream example, we aren't
changing the interface, so the intermediate class is a proxy rather than an adapter. The intermediate
class must extend OutputStream, which suggests that it must be serialized to the client and act
there as a Serializable Proxy. Our solution now looks like Example 12.8.
Code View: Scroll / Show All
public void write(byte[] buffer, int offset, int count) throws IOException
{
rout.write(buffer, offset, count);
}
// etc for other write() methods, close(), and flush()
}
This solution has the following elements:
• the class we are trying to be typecompatible with—in this case, OutputStream
• the intermediate remote interface RemoteOutputStream
• the proxy class OutputStreamProxy
• a remote server which implements RemoteOutputStream
• the real OutputStream obtained by the server, probably a FileOutputStream or an
output stream attached to a socket.
Our RemoteOutputStream interface exports all the same methods as OutputStream.
Fortuitously, all the methods of OutputStream already throw IOException, which is a base
class of RemoteException, satisfying the rule that all methods in a remote interface must be
declared to throw RemoteException or one of its base classes.
For the same reason, it was possible for us to change the method signatures in
OutputStreamProxy to add RemoteException to the exceptions already declared to be
thrown by the base class OutputStream. This is really handy, because by the rules of Java we
normally can't do this.[4] It works in this case because all the methods of OutputStream already
throw IOException, which, as we just discussed, is a base class of RemoteException.[5]
[4]
Java Language Specification, §8.4.4.
[5]
Strictly speaking we didn't even have to add RemoteException to the method
declarations, as it extends IOException and so is already implicitly declared. We have
done so as a matter of style.
In general we won't be so lucky—we won't have such a convenient base class to extend. In the general
case, we will have to catch RemoteException in the methods of OutputStreamProxy and
throw an exception acceptable to the rules of Java (possibly—at worst—a RuntimeException).
From the standpoint of purity, one would frown on taking advantage of IOException in this way.
Generally speaking, remote interfaces should be purposedesigned, not inherited via this sort of
trickery.
To complete the example, we only need to do the following:
• provide a remote service implementation of RemoteOutputStream
• add a method to some remote interface which returns an OutputStream
• implement this method in a remote server to return an OutputStreamProxy attached to
the RemoteOutputStream implementation.
The remote output stream example demonstrates a general technique which is useful where clients
expect an instance of some preexisting class which is not already represented by a remote interface.
Looking closely at the RemoteInputStream and RemoteOutputStream
solutions, there is structurally very little difference between them. The difference in fact is
the difference between an adapter and a proxy: the Adapter has a different interface from
the class "behind" it, where the proxy has the same interface. This only arose because we
had to invent an interface for RemoteInputStream, whereas we were able to imitate
an existing interface for RemoteOutputStream.
As a matter of fact we could have invented an interface for RemoteOutputStream
too, in which case both solutions would be adapters. From a purely formal point of view,
this is exactly what we did, because RemoteOutputStream is not actually identical to
—the same thing as—OutputStream, it's just an interface with exactly the same
methods.
When the client acquires an object of type RemoteService, it receives an instance of
ServiceProxy, as opposed to receiving a reference to a ServiceImplementation. The
ServiceProxy is transmitted whole—by serialization—whereas the
ServiceImplementation is transmitted as a remote stub, by the semantics of RMI results.
As usual, the ServiceProxy would hold a reference to the object it is the proxy for, in this case a
ServiceImplementation. This reference is constructed prior to transmission, at the server,
probably on construction of the ServiceProxy.
This pattern is useful where the server needs actions to be performed at the client as well as at the
server. The pattern has obvious security implications, as its implementation is provided entirely by the
server. The client—or its authors—may not even be aware that proxy code is being executed locally.
An RMI stub for an activatable remote object is really itself a smart remote proxy. The Jini Lookup
service also uses this pattern.
12.8.5. Virtual proxy
A virtual proxy "creates expensive objects on demand",[7] where "expensive" means "expensive to
create"—objects which consume a lot of memory, say, or which take an appreciable time to initialize.
[7]
Gamma et al., p. 208.
RMI activation is an example of this pattern. An activatable stub is a virtual proxy for an activatable
server, which takes appreciable time to be activated.
RMI activation is also an example of the smart reference pattern.[8]
[8]
ibid., p. 209.
12.9.1. Client-server
Clientserver is a pattern of communication between entities which take on different roles. The client
entity initiates the connection and makes a request; the server entity only receives requests and returns
replies. The client normally terminates the connection.
RMI is inherently a clientserver architecture. You must have a client and you must have a server.
It should be observed that in any objectoriented program, every object is a server, except
dataonly objects and the initial object; if it provides callbacks, the initial object is a server
too. "The client/server relationship between objects, however, is not completely useful.
Virtually all objects in an objectoriented system are suppliers of functionality. Objects
that do not serve functionality are called data objects. Because objects tend to be suppliers
as well as consumers, the overall architecture tends to shift from being client/server to
server/server. "[9]
[9]
Thiruvathukal, Thomas, and Korczynski, Reflective Remote Method
Invocation.
12.9.2. Client-dispatcher-server
In the clientdispatcherserver pattern,[10] a "dispatcher" mediates between a client and a server. The
client communicates with the dispatcher to access a service by name, and the dispatcher forwards the
request to the server registered under that name.
[10]
Sommerlad and Stal, "The ClientDispatcherServer Design Pattern", in Vlissides,
Coplien, and Kerth, eds., Pattern Languages of Program Design 2.
This pattern can be used to provide " location transparency", so that clients need not be aware of the
actual network location of servers. It can also be used to implement loadbalancing.
RMI is itself an instance of this pattern. Under the covers, stubs (clients) communicate directly with
the RMI runtime system (dispatcher) which dispatches the call to the appropriate remote objects
(servers) by means of a fixed object name (object ID). In a way, the RMI registry is another example
of this pattern.
12.9.3. Peer-to-peer
Peertopeer is a pattern of communication between entities which are peers of each other—there is no
master/slave or client/server relationship between them. Either peer is entitled to initiate and terminate
the conversation, and either is entitled to submit requests and return replies. We mention this pattern
only to point out that RMI does not support it.
12.10. Singleton
A singleton is a class of which exactly one instance can exist.[11] A singleton is often used to
encapsulate a process which must be sequentialized among multiple users, for example a printer
spooler, or to represent an external resource of which only one instance exists, such as a file system or
a database. java.lang.Runtime is an example of a singleton class built into Java, representing
the Java runtime system itself.
[11]
Gamma et al., pp. 127 ff.
The singleton pattern appears in several important ways in RMI.
12.10.3. Activatables
The result of registering an Activatable—an activatable stub—represents a singleton
ActivationID at the server host. The activatable represented by the ActivationID—the tuple
of {groupId, class, location, initial data}—is only instantiated once per host. Unless you have
deliberately registered the same ActivationDesc more than once, or registered more than one
ActivationDesc for a server class in the same activation group, only one instance of the
Activatable class will execute in the activation group.
The remote factory pattern is useful:
• when implementing polymorphism
• in code mobility situations
• to avoid the classversioning problem
• to avoid the rollout problem when modifying classes used by clients.
The remote factory pattern also provides an answer to the perennial question "what object should be
bound in the RMI registry?"—the factory should be bound.
12.12. Abstract remote
The abstract remote pattern uses an abstract class which implements an associated remote
interface.[13]
[13]
Maso, Re: Different classes that implement the same remote interface.
This is illustrated in skeletal Java code in Example 12.12.
Like all abstract/concrete patterns, this pattern separates the abstract class from its concrete
implementation class(es). The point of the pattern in RMI is that the abstract remote class is entirely
sufficient to be processed by rmic, generating all required stubs (and skeletons, if any). The concrete
implementation class(es) need never be processed by rmic: implementations can be varied arbitrarily,
after which they only need to be recompiled. The stubs (and skeletons, if any) need only be
regenerated when the remote interface changes.
This is a very neat way to simplify the build procedure for an application, and to reduce the amount of
new class code to be reinstalled after a server change. It also provides an opportunity to vary server
implementations. i.e. to use serverside polymorphism.
Note that the abstract remote class need not provide any code except constructors. In particular, it need
not reiterate the remote method declarations of the remote interface.
By the rules of Java, methods declared in an interface are already abstract. In an abstract
class which implements the interface, they continue to be abstract unless explicitly re
declared as nonabstract methods (with implementations).
As a matter of fact, java.rmi.activation.ActivationGroup is itself an instance of this
pattern. It is an abstract class whose actual implementation is elsewhere: the stub is generated from
ActivationGroup.
12.13. Session
A session is the state associated with a series of interactions between a single client and a server. This
can be implemented by allocating a new instance of a server per client; this server can then
accumulate the client's state in its local variables. More generally, an explicit Session object can be
created per client, to act as a dispatcher between the client and a number of servers, with facilities for
the servers to access the session and query or modify its state. Sessions often begin with a login event
and end with a logout or session expiry event.
This is illustrated in skeletal Java code in Example 12.13.
The "Secure Sockets Layer" described in §16.5 implements this pattern, although not as an RMI
subsystem. It provides "secure sessions" manifested by SSLSession objects; these can be expired or
explicitly closed by servers and can accumulate state on behalf of the client.
12.14. Exercises
1:Write a server which implements the RemoteInputStream interface.
2:Write a server which implements the RemoteOutputStream interface.
3:Implement a trivial file retrieval system using the RemoteInputStream and
RemoteOutputStream discussed above and implementations of the following interfaces:
Code View: Scroll / Show All
// I/O accessors
InputStream getInputStream()
throws RemoteException, IOException;
// Controversial ...
OutputStream getOutputStream()
throws RemoteException, IOException;
}
Obviously RemoteFile is a remote adapter for a java.io.File at the server. The interfaces
in this exercise provide a means of obtaining an initial RemoteFile representing some
directory, and of traversing that directory to any depth obtaining RemoteFiles contained in it.
Once a desired file has been found, an InputStream can be obtained for it, and its contents
read. Also, an OutputStream can be obtained for it, and its contents overwritten.
4:Write a client for the previous exercise which recursively traverses RemoteFiles, starting with
one returned by the RemoteFileFactory, until it finds a file of a certain name, and prints out
the contents of that file. For example, the client could print out its own source code.
13.2. JNDI
JNDI provides naming and directory functions to Java applications. It is independent of any specific
naming or directory service implementation. Its architecture consists of an API which provides a
uniform interface to the various supported naming services, and a service provider's Interface (SPI)
which specifies a backend interface to be implemented by the plugin or "provider" for each such
naming service. JNDI ships with service providers for:
• the RMI registry
• the CORBA COS Naming service[1]
[1]
The COS Naming service is specified by the Object Management Group. OMG
specifications are available at http://www.omg.org.
• the Lightweight Directory Access Protocol (LDAP)[2]
[2]
LDAP is specified by IETF RFCs 1777 (LDAP v2) and 2251 to 2256 (LDAP
v3).
• others not discussed in this chapter.
In addition, vendorsupplied JNDI service providers are available for other services such as Novell's
Network Directory Service (NDS) and several other components of NetWare.
The JNDI API is the same regardless of which service provider(s) is/are being actually used. The only
differences that a JNDI client program encounters between different service providers are (a) different
specifications of which service providers is selected, and (b) the possibility that a requested operation
—for example, subdirectory lookup—is not supported by a particular service provider, when a
javax.naming.OperationNotSupportedException is thrown.
JNDI is shipped as part of the Java 2 platform as from JDK 1.3. It is also available separately for use
with JDK 1.1.x and JDK 1.2.x.
13.3.2. Sub-contexts
These operations all act on a JNDI context, which is a name space. In some naming service
implementations, contexts can have subcontexts: these are like subdirectories in a file system, and
allow namespaces to be organized into a hierarchy.
13.3.3. Federation
The JNDI framework supports the ability to federate one or more distinct naming services together, so
that a different parts of a JNDI name can be handled by different JNDI service providers.
Property Value
java.naming.factory.initial com.sun.jndi.rmi.registry.RegistryContextFactory
java.naming.provider.url Set this to point to the RMI registry, e.g. rmi://server. The defaul
value for this property is rmi://localhost:1099.
Property Value
java.naming.factory.initial com.sun.jndi.cosnaming.CNCtxFactory
java.naming.provider.url See the JNDI documentation for the provider.
Property Value
java.naming.factory.initial com.sun.jndi.ldap.LdapCtxFactory
java.naming.provider.url This is complex: see the JNDI documentation for the
provider.
13.5. Examples
A normal RMI client's registry lookup operation looks like Example 13.1.
import java.rmi.Naming;
This operation may throw any of the following exceptions:
• java.rmi.NotBoundException—the name is unknown
• java.net.MalformedURLException—the URL is badly formed
• java.rmi.RemoteException—some remote error.
The same operation using JNDI looks like Example 13.2.
import javax.naming.*;
These JNDI operations may throw a javax.naming.NamingException.
You can avoid coding the initial context factory into the application by setting the Java system
property java.naming.factory.initial to the appropriate value. in the case of the RMI
registry provider for JNDI, the value required is
com.sun.jndi.rmi.registry.RegistryContextFactory. You can set the property
externally in either of two ways:
1. On the command line, by specifying -Djava.naming.factory.initial = value.
2. In a jndi.properties file, which is read by any program which uses JNDI. This file can also
contain settings for other JNDI and provider properties, and indeed any system properties.
Setting the JNDI and provider properties externally makes applications genuinely independent of the
specifics of JNDI service providers.
In Example 13.2, the URL of the RMI registry (e.g. rmi://server or rmi://server:1099)
must have been set externally into the Java system property java.naming.provider.url, or
the program must have set it into the initial context's environment under the name given by
Context.PROVIDER_URL.
If you don't mind embedding both the naming protocol and the registry location into the lookup
string, you can avoid specifying the context factory and URL altogether, as shown in Example 13.3.
Example 13.3. JNDI registry lookup with embedded protocol and registry location
import javax.naming.*;
Again, these operations may throw a javax.naming.NamingException.
13.9. Exercises
1:Adapt the RemoteEcho server example of Chapter 1 to use JNDI with the RMI registry plugin.
2:Adapt the registry dump and restore programs of the exercises in Chapter 6 to use JNDI with the
RMI registry plugin.
3:Modify the RemoteEcho server example to use JNDI with the LDAP plugin.
4:Modify the RemoteEcho server example to use JNDI with the File System plugin.
5:The JNDI API supports a rename operation. The RMI registry does not. Show how you would
implement the JNDI rename in an RMI registry provider. Is this an ideal implementation?
6:[Term project] According to the Jini Lookup Service specification, "although the collection of
service items is flat, a wide variety of hierarchical views can be imposed on the collection by
aggregating items according to service types and attributes".[4] Can you write a JNDI provider
which uses the Jini Discovery and Lookup services as a backend, and provides a variety of
hierarchical views as a namespace? (There are two parts to this exercise: (a) designing the views,
and (b) implementing the provider.)
[4]
Waldo et al., The Jini Specification Second Edition, § LU1.1.
Chapter 14. Servers III—RMI/IIOP
Introduction—CORBA—EJBs—PortableRemoteObject—Writing the server—Building
the server—Java/IDL tool—Supporting both jrmp and IIOP—Restrictions—
Implementing the service in another language—IIOP Clients—Implementing the client in
another language—Exercises
14.2. Introduction
IIOP is the transport protocol adopted by the Object Management Group (OMG) for CORBA. It
provides interoperability with CORBA object implementations in various languages.[1]
[1]
CORBA defines three layered protocols: (a) the Common Data Representation (CDR),
(b) the General InterORB protocol (GIOP), which includes CDR and in addition
specifies GIOP message formats and transport assumptions, and (c) IIOP, which
specializes GIOP to TCP/IP, specifying how agents open connections and use them for
GIOP message transfer.
Normally, the underlying transport for RMI is JRMP, [2] which is only understood by Java RMI
programs.
[2]
"The RMI Wire Protocol", RMI specification, §10. This incorporates the Object
Serialization Stream Protocol, Serialization specification, §6.
RMI over IIOP allows Java programmers to program to the RMI interfaces but use IIOP as the
underlying transport, so as to be interoperable with CORBA, and with any services which are
interoperable with CORBA. Java RMI programmers can therefore participate in CORBA services
networks with services and clients implemented in any of the languages supported by CORBA,
subject to the restrictions defined in § 14.10.
The reason that RMI over IIOP is possible is that, in RMI, the stub implements the
protocol, and ultimately the server provides its own stub. An RMI client has no knowledge
of the protocol used by the stub to communicate with the server. An RMI over IIOP client
is not very different from an RMI over JRMP client: it just obtains the remote reference in
a different way: from a COS Naming service, rather than an RMI registry or a previously
obtained and serialized activatable stub.
14.5. PortableRemoteObject
The javax.rmi.PortableRemoteObject class is provided to help you define "portable
remote objects": RMI servers which communicate over RMI/IIOP. It is a precise analogue of
java.rmi.server.UnicastRemoteObject, discussed in Chapter 7: it provides a
constructor, an exportObject method, and an unexportObject method. The differences
between portable remote objects and unicast or activatable remote objects are as follows:
• portable remote objects communicate via IIOP instead of JRMP
• portable remote objects do not participate in DGC
• portable remote objects must be compiled by rmic with the iiop flag
• portable remote objects do not support explicit port numbers or socket factories.
14.6. Writing the server
In writing portable remote objects, you have three choices.
import javax.rmi.PortableRemoteObject;
import javax.rmi.PortableRemoteObject;
import javax.rmi.PortableRemoteObject;
Code View: Scroll / Show All
import java.rmi.*;
import java.rmi.server.*;
import java.util.Properties;
import javax.naming.InitialContext;
import javax.rmi.*;
14.10. Restrictions
RMI/IIOP is designed as the intersection of RMI and CORBA. It is not intended to contain all the
features—the union—of both.
RMI/IIOP provides the ability to define services (servers) written in Java and export them to CORBA,
to write Java clients for CORBA services, or both. The following restrictions apply to RMI/IIOP as
compared to RMI/JRMP.
1. All the remote interfaces concerned must originally have been defined as Java RMI interfaces.
It is not possible to "import" interfaces previously defined in other languages, or in CORBA
IDL, into the RMI/IIOP scheme.
2. The ORB must support the Objects By Value feature of CORBA 2.3, introduced to support
Java.
3. You must use JNDI with the COS Naming plugin, instead of the RMI registry as the naming
service. Refer to Chapter 13 for more information.
4. Portable remote objects cannot be activatable. The activation protocol is only supported over
JRMP.
5. Remote interfaces must not be cast just by inline Java casts; instead they must be cast by calls
to the PortableRemoteObject.narrow method. A CORBA network operation is
required to perform the cast.
6. Constant definitions in remote interfaces may only be of primitive or String type, and must
be evaluated at compile time.
7. The same method name cannot be inherited into a remote interface from more than one base
remote interface.
8. Java names that conflict with IDL "mangled" names generated by the JavatoIDL mapping
must be avoided, as must method names inherited from more than one remote interface, and
names that differ only in case. The case of a type name and a variable of that type whose name
differs only in case is supported, but most other combinations are not supported.
9. Derivation from UnicastRemoteObject and Activatable, explicit port numbers,
RMI socket factories, and the DGC interfaces are not supported. IIOP does not support DGC,
hence the Unreferenced interface is not supported—you can still implement it, but your
unreferenced method will never be invoked via IIOP.
10. As discussed in § 3.9.3, RMI/IIOP only properly supports the class versioning features of
Serialization from JDK 1.3.1.
11. Runtime sharing of remote object references is not preserved exactly when transmitting object
references across IIOP. Runtime sharing of other objects is preserved correctly. In the
terminology introduced in § 3.4.2, object graphs containing remote references of indegree > 1
are not transmitted correctly, but all other object graphs, including those containing non
remote references of indegree > 1, are transmitted correctly.
12. rmic iiop enforces an undocumented rule that server classes cannot just implement
java.rmi.Remote: they must implement a concrete remote interface which contains
methods. In consequence, a server class cannot be derived from
java.rmi.server.RemoteServer, although it can be derived from
java.rmi.server.RemoteObject.
In RMI/IIOP, a CORBA network operation is required to check and implement the cast, as shown in
Example 14.6.
An RMI/IIOP client for the service of Example 14.4 is illustrated in Example 14.7.
import java.rmi.*;
import java.util.Properties;
import javax.naming.InitialContext;
14.14. Exercises
1:Adapt the remote date/time server exercises of Chapter 7 to use IIOP instead of JRMP, and JNDI
with the COS Naming plugin instead of the RMI registry. Create a new client as well, which
looks up the service in the COS Naming service via JNDI. Run the system and show all output.
2:Adapt the remote date/time server of the previous exercise to use JRMP and IIOP. Write yet
another client for this service which uses JRMP: i.e. which looks up the service in the RMI
registry (or JNDI with the RMI registry plugin), not JNDI/COS Naming. Run the IIOP client
from the previous exercise and show all output.
3:Using, successively, the servers of the previous exercises, run the original date/time client of
Chapter 7, which uses the RMI registry. In each case, does it work? If so, why? If not, why not?
4:Run the server of the previous exercise with the property java.rmi.server.logCalls set
to true. Run the IIOP client and note the output at the server. Run the JRMP client and note the
output at the server. Comment.
Chapter 15. RMI through firewalls
Introduction—Firewalls—SOCKS—HTTP tunnelling—Firewalls and RMI—GIOP
Proxies—A note on callbacks—A note on firewalls
15.2. Firewalls
In order to prevent officewide LANs becoming part of the global Internet, a "firewall" is normally
placed at the gateway between the LAN and the Internet proper. Like a physical firewall, an Internet
firewall's purpose is to provide a high level of security to those on the protected side by preventing
dangerous elements from entering.
Figure 15.1 shows a simple view of a firewall.
Transport firewalls generally restrict outgoing connections to those originated by an application
firewall. The combination of the transport firewall and the application firewalls constitutes the
installation's "total" firewall.
15.6.1. Benefits
Unlike the current RMI/HTTP implementation, the CORBA/Firewall Security proposal takes account
of servers behind one or more firewalls (as well as clients behind firewalls), i.e. true interenclave
GIOP communications across arbitrary numbers of firewalls protecting both clients and servers.
The CORBA/Firewall Security proposal does not use an errorcode fallback strategy to redirect traffic
via the proxy. Instead, clients and servers behind firewalls are expected to be configured with proxy
information. For this reason, the proposal does not rely on firewall properties, unlike RMI, and
therefore does not suffer from the incompatibility between RMI's expectations of firewall errors and
actual implementations of firewalls.
15.6.2. Limitations
The GIOP proxy solution will only be applicable to those RMI users prepared to use the IIOP
protocol, and therefore prepared to lose activation and DGC from their RMI applications, as discussed
in Chapter 14.
The GIOP proxy and bidirectional GIOP solutions will not be usable until:
• CORBA 3.0 is approved
• RMI/IIOP is upgraded to conform to it
• GIOP proxies become available and installed in quantity.
15.7.1. Benefits
The RMI Proxy supports most of the firewall configurations supported by the GIOP Proxy described
above:
• clients behind firewalls
• servers behind firewalls
• clientside callbacks
• nested clientside and serverside firewall enclaves
• clients behind any combination of RMI Proxies and SOCKS servers.
It provides extensive security management, based on the Java 2 Security Model. Access to specific
RMI interfaces can be controlled down to the level of individual methods, via combinations of client
hostname/IP address, codebase, and interface/method name.
Attempts to penetrate the RMI Proxy via protocols other than JRMP do not succeed, and RMI/JRMP
calls which fail the security configuration are not transmitted to the server, but instead cause a
java.rmi.AccessException at the client. As this exception extends
java.rmi.RemoteException, there is no major coding impact on clients or servers.
15.7.2. Limitations
The RMI Proxy is subject to the following limitation:[13]
[13]
at the time of writing (March 2001).
• no support for RMI/IIOP
• no support for RMI socket factories
• no support for secure conversations.
Some of these features are actively planned; some are subject to features in future JDK releases; the
remainder are under review.
Disclosure: The RMI Proxy was conceived, designed, and implemented by Esmond Pitt,
one of the authors of this book, who has a financial interest in this product. For further
information see the RMI Proxy home page at http://www.rmiproxy.com.
16.2. Identity
Security concerns the identity of each party to the conversation, in two distinct ways:
• authentication
• authorization.
16.2.1. Authentication
We would like each end of a conversation to be able to authenticate itself—prove its identity—to the
other end. Ultimately, this has to come down to an exchange of secret information previously known to
both ends, or acquired from a mutually trusted third party. Many techniques exist, including:
• exchange of encrypted information using public and private keys
• X.509 certificate chains leading to a trusted wellknown entity.
Authentication in Java relies on the concepts of subject, principal, and credential.
A subject is a set of related information for a single entity such as a person.
A subject has one or more principals, which are really just alternate names for the subject. Examples
of principals are: full name, driver's licence number, Social Security number (USA), Tax File number
(Australia)—anything which uniquely identifies the subject in some domain. All principals in a
subject refer to the same subject.
A subject may also own securityrelated attributes known as credentials—such as private and public
keys, certificates, Kerberos server tickets, and so on. These are stored in the subject in private or
public credential sets, depending on whether they are or are not sensitive credentials requiring special
protection, such as private cryptographic keys.
16.2.2. Authorization
Once we know reliably who we are talking to, we want to determine that party's authority—whether
they are entitled to send certain messages, make certain requests, and so on. Authorization is a matter
of enforcing applicationspecific policies, based on the other party's identity, or some aspect of it: for
example, their department, or their physical address.
16.3. Integrity
We are next concerned with assuring the integrity of each message received—assuring ourselves that
the received message:
• has been completely and correctly delivered by the transport
• has not been tampered with in transit
• has not been completely forged by a third party.
Message integrity assures the receiver that it has a complete message from an identified and
authenticated source.
The technique commonly used to assure message integrity is the message digest.
On top of all this, there is usually some kind of application protocol, consisting at least of the class
definitions of the objects being transmitted.
It may be thought that a certain level of privacy is assured in RMI by the sheer number of protocols
involved. However, IP, TCP, the Java Object Serialization protocol, and JRMP are all published
protocols. Stripping out the IP and TCP data is a relatively easy exercise, already implemented in
network analysers ("sniffers") and TCP/IP trafficanalysis programs. An eavesdropper would find it
quite easy to understand large amounts of plain text, and reasonably possible to decipher application
specific data with some knowledge of the application data structures. This task is made easier by the
fact that RMI annotates serialized class information with a codebase, allowing the eavesdropper
possible access to the class files associated with the data.
For these reasons, eavesdropping over RMI is actually easier than with some other communications
systems. RMI conversations which require genuine privacy must be encrypted.
16.4.1. Encryption
The purpose of encryption is to make it infeasibly difficult to eavesdrop on the data in transit.
Without going into all the gory details of various encryption techniques, the data to be transmitted is
first encrypted using a secret key. This key does not form part of the message; instead, it has been
separately agreed to by the parties to the communication. The encryption technique completely
obfuscates the message. The original message can be recovered by decrypting with the same key, or,
in public/private key systems, with the private key, after the message was encrypted with the public
key.
The key is chosen so that guessing it, or trying to crack the message by bruteforce enumeration
techniques, is computationally infeasible—would take longer than the lifetime of interest of the
message. The degree of security of the encryption is related directly to the length of the key: this is
why there are 40bit keys, 56bit keys, and so on up to (presently) 128bit keys. The longer the key, the
better[1]
[1]
A comprehensible account of encryption and keydistribution techniques is given in
Singh. The Code Book, Chapter 6.
16.7. JAAS
The Java Authentication and Authorization Service (JAAS) architecture, introduced to supplement
Java 2 JDK 1.3, addresses the issues of authentication and authorization, providing a common
framework into which various kinds of security policies and implementations can be integrated.
The JAAS framework provides for:
• establishing and verifying a user's identity
• performing securityproviderdefined logins
• temporarily assuming an acquired identity, to perform an action on behalf of another.
16.9. Exercises
1:Show how to implement SSL into RMI, by writing adapter classes, say
SSLSocketFactoryAdapter and SSLServerSocketFactoryAdapter, which
implement the RMIClientSocketFactory and RMIServerSocketFactory interfaces
respectively. Don't forget to provide reasonable implementations of the Object.equals
method, as discussed in § 11.5.
Chapter 17. Servers IV—beyond unicast
Datagrams—Multicast—Broadcast—Clients—Exercises
17.2. Datagrams
A datagram is a single transmission which may be delivered zero or more times; whose sequencing
with respect to other datagrams between the same two endpoints is not guaranteed; and which is
subject to size limitations.[2] Datagrams are a peertopeer mechanism, not a clientserver
mechanism. There is no such thing as a "passive" datagram socket in a "listening" state. Endpoints in
a datagram exchange are not explicitly connected or disconnected, and connections are not explicitly
accepted. In the IP protocol suite, datagrams are supported by UDP. In Java, datagrams are
implemented by the java.net.Datagram class, and are sent and received over sockets of the
class java.net.DatagramSocket.
[2]
UDP datagrams are limited by the protocol to 65507 bytes in length, and by most
implementations to 8K bytes. IPv6 has "Jumbograms" at the IP level; this allows UDP
datagram sizes up to 232 –1: see IETF RFC 2675. Practical exploitations of UDP often
restrict messages to 512 bytes—a single IP packet—to avoid fragmentation problems in
network routers.
A datagram server would be a server whose communications use datagrams rather than streams, and
datagram sockets rather than stream sockets (java.net.Socket). In both these respects a
datagram server would differ from all the RMI server types discussed so far in this book.[3]
[3]
Strictly speaking there shouldn't even be datagram clients or servers, just peers.
The difference between stream sockets and datagram sockets is the difference between TCP and UDP,
the protocols which they "speak". TCP implements a reliable connected stream, over which any
number of bytes of data can be sent and received by each end of the connection. The costs of this
reliability are not insignificant, as the underlying implementation has to perform quite a number of
hidden tricks to achieve it:
• window negotiation
• pacing
• sequencing
• acknowledgement
• retransmission on error
• a threeway handshake to establish a connection
• a fourway handshake to close a connection.
TCP is a suitable protocol for largescale, errorfree data transfer, and for multiple
interactions between client and server. In some respects, TCP is not an ideal protocol for
transactions, because of the overheads of setting up and tearing down the connection, and
negotiating transfer parameters. RMI amortizes this overhead over multiple remote
method invocations where possible, by conserving server sockets and client connections:
this has a large effect when multiple calls are made to the same host and port over a short
time interval.
UDP provides datagrams, over datagram sockets. UDP is a simple protocol whose overheads are
insignificant.
UDP, being a "connectionless" protocol, has no connection/disconnection handshake, no
implicit delivery windowing, no pacing, no acknowledgements, and no retransmissions.
17.2.1. Purpose
If the severe message size limitations of UDP can be tolerated, there are theoretical efficiency gains to
be had by using UDP. A datagram client which sends and receives single packets per RMI call doesn't
incur the threeway connect and fourway disconnect of TCP, its windowsize negotiation, or its slow
start behaviour. A UDP client only needs as many datagram sockets as it has simultaneous threads
performing RMI calls; a TCP client needs as many sockets as it has distinct {host, port} pairs to be
connected to.
There can also be distinct design advantages in exposing to the application the error situations handled
by TCP but not by UDP, including especially receipt of a duplicate message by the server, and non
receipt of an acknowledgement by the client. This is particularly so in transactionoriented
communications.[4]
[4]
For a detailed discussion of UDP versus TCP see Stevens, Unix Network
Programming, Volume I, §§2.3, 2.4, and 20, especially §20.4.
17.2.2. Semantics
The normal semantics of an RMI call are that the call has been executed "at most once". At the
transport level, a remote call sent over a datagram may yield zero or more replies, so the semantics of
a datagram remote call are that the call has been executed "at least zero times". This difference is
significant, and it has considerable implications for the design of clients and servers.
It also has a major implication on the RMI design for datagrams. An RMI call to a datagram server
needs to be retried at appropriate intervals if a reply isn't received within the expected time, and
duplicate replies need to be discarded. The number of retries, or perhaps the total elapsed time, should
be bounded.
What the "expected time" might be is another matter: it depends on a number of factors, including
network bandwidth, network load, server load, the time the remote call should take to execute, and so
forth. The "appropriate interval" is yet another matter: strictly speaking, it should be subjected to
TCP's "exponential backoff" (for example, doubled at each failure), rather than just retrying the call at
fixed intervals.
Mechanisms to control all this, preferably on a permethod basis, would need to be available to clients.
These should cater for the special case where the client doesn't want the API retried at all and doesn't
want the reply to be waited for, in other words a pure oneway datagram message or asynchronous
call. The mechanisms might be static methods in a service class, or configuration files.
17.2.3. Example
One example of a service which would function at least as well via UDP datagrams as via TCP
streams is the RMI registry. The registry typically runs in its own process on a dedicated socket, in
which case it doesn't participate much in RMI's conservation of server sockets and client sessions.
Once clients have accessed it (to bootstrap their server references) they typically don't go back to it
again. This "oneshot" access pattern is typical of UDPstyle services.
17.2.4. Implementation
There are at least two ways to implement an RMI datagram server.
One way is to provide implementations of RMI ServerSocketFactory and RMI
ClientSocketFactory which provide instances of ServerSocket and Socket respectively
which are "backed" by DatagramSockets. This is simply an exercise in mapping the Socket and
ServerSocket methods on to the facilities of DatagramSockets, in other words wrapping a
DatagramSocket inside a proxy which appears to be a Socket or ServerSocket.
Another way is to make a number of enhancements to RMI:
• Introduce a new abstract base class, say DatagramRemoteObject. This class would need
a DatagramRemoteObject.exportObject method which exported the object via a
new or existing DatagramSocket.
• Provide a UDP version of the RMI transport. This would create a thread for each distinct
DatagramSocket, to listen at the socket and dispatch remote calls packaged in incoming
datagrams. The UDP infrastructure would also support unmarshalling a remote call from
datagrams received, and marshalling the result or exception of the call to a reply datagram.
The UDP transport would also implement the semantics required of the call—timeout, retry
interval, maximum number of retries or total elapsed time in the presence of failures, as
described above.
• Export a remote stub containing a different kind of RemoteRef, using the UDP transport to
communicate with the target, instead of the TCP transport.
• Design and implement a solution to allow socket factories to be defined which return
DatagramSockets instead of Sockets and ServerSockets. This is left as an exercise
for the reader.
Implementation of DatagramRemoteObject and a UDP transport layer for RMI is
not an unduly difficult exercise, except for an implementation issue in the Sun code which
could initially be resolved by excluding support for DGC.
Support for a datagram server would exclude HTTP tunnelling: HTTP doesn't operate
over UDP.
17.3. Multicast
A multicast is a datagram sent to a multicast group over a multicast socket. A multicast socket is a
datagram socket with additional capabilities for joining "groups" of other multicast hosts. In the IP
protocol suite, multicast is supported by UDP and the Internet Group Management Protocol (IGMP).
A multicast group has a distinct IP address of its own.[5] Datagrams can be sent to individual IP
addresses or to an IP multicast group.
[5]
In IPv4 its first four bits are 1110; in IPv6 its highorder byte is FF.
An RMI multicast server would be a further development of the datagram server described above. It
would communicate via a java.net.MulticastSocket instead of a
java.net.DatagramSocket.
17.3.1. Purpose
Multicast is increasingly used over parts of the Internet for purposes such as mirroring Web sites,
videoconferencing, replicated services, collaborative computing, streaming audio and video, and real
time data delivery.
An RMI multicast object would be a member of a multicast group. It would be capable of receiving
transmissions directed straight at it, or directed at the group. Within the group, a multicast object
would most probably function as a replicated or federated service. Instances of the same multicast
object might execute at each host in the multicast group. All instances would receive all RMI calls
directed at the group. Members of the group may also communicate with each other via the multicast
mechanism. Multicast uses the timetolive (TTL) feature of UDP to limit the scope of each multicast
to a chosen neighbourhood. Scoped intragroup communications may be used for synchronization
purposes.
17.3.2. Semantics
The semantics of a multicast remote call are similar to those of a datagram remote call—the call has
been executed "at least zero times"—except that replies may be received from every member of the
multicast group, and the replies may not all be the same. Some may be successes (return values) and
some may be failures (remote exceptions). Alternatively, a multicast object might suppress failure
replies altogether, like the broadcast servers discussed below.
Clients of multicast objects are in much the same position as clients of a datagram server. They are
more likely to receive multiple replies to a single request, as there is likely to be more than one
member of the multicast group. Without using higherlevel protocols, there is no reliable way of
determining how many members of a multicast group exist. Therefore, multicast clients don't know
how many replies to expect, so there is no way they can implement multicastspecific policies such as
requiring all replies to be received, requiring all replies to be identical, or even the much weaker
policy of requiring at least one reply indicating success. It would make some sense to constrain remote
multicast methods from returning results at all, i.e. to have a return type of void.
This in turn constrains the uses to which RMI multicast objects can be put. The most apparent use
seems to be to provide replicated and federated services. The Jini Discovery Service uses IP
multicasting, but not over RMI.
For a fuller consideration of multicasting see the paper on the Java Reliable Multicast Service (this is
a Sun research project, not a product).[6] This paper contains an interesting discussion covering the
following issues:
[6]
Sun Microsystems Laboratory Technical Report TR9868.
1. Number of senders: is more than one sender to be supported?
2. Late joins: is it permitted to join a multicast group after senders have started sending? If so,
what needs to happen?
3. Realtime: does the application require realtime performance?
4. Consistency: must all data be delivered to all receivers at exactly the same time? or only that at
some time? is it sufficient that some application gets the data some time? or do transactional
checkpoints exist?
5. Ordering: preservation of the order of sends is usually, but not always, required
6. Reliability: most applications require zero data loss, but some (e.g. streaming audio or video)
do not.
Higherlevel protocols layered over IP multicast have been proposed which address a number of the
issues raised in this section. These include TreeBased Reliable Multicast (TRAM), Lightweight
Reliable Multicast Protocol, and the Session Announcement and Description protocols.[7] A realistic
implementation of multicast RMI would allow such protocols to be incorporated into the server's
operation, by means of an extensible superprotocol framework to allow for future multicast protocol
developments, as in the Sun JRMS research project described above.
[7]
Liao, Light weight Reliable Multicast Protocol as an Extension to RTP; Handley and
Jacobson, SDP: Session Description Protocol, RFC 2327; Chiu, Hurst, Kadansky, and
Wesley, TRAM: a Treebased Reliable Multicast Protocol.
The effort that would be needed to shoehorn multicasting into the RMI framework
exposes the limitations of the RMI model: single client, single server, single request,
single response, and synchronous behaviour. This is a limited way to use a network.
Multicasting is not really like calling methods at all. It is much more like sending and
receiving asynchronous events. A realistic application of multicasting would be a server
discovery service replacing the registry, as seen in the Jini Discovery and Lookup Service.
17.3.3. Implementation
As with datagram servers, the implementation of an RMI multicast server could be tackled in two
ways: (a) via socket factories which deliver Socketlike proxies for datagram sockets as above, and
ServerSocketlike proxies for multicast sockets; or (b) by making a number of enhancements to
RMI. This section outlines the second of these techniques.
First, we would need a new abstract base class called, say MulticastRemoteObject. This class
would probably be derived from the DatagramRemoteObject class described above. It would
need an implementation of MulticastRemoteObject.exportObject which exported the
object via a new or existing MulticastSocket. This class would also need an API for joining and
leaving multicast groups: alternatively, perhaps an argument to the exportObject and
MulticastRemoteObject.unexportObject methods might be specialised for this use.
Second, we would need to revise the UDP transport described above for multicast sockets. Actually,
few if any changes would be required to the datagram transport, largely because
MulticastSocket is derived from DatagramSocket.
Third, we would need to create the same kind of remote stub as for the datagram server described
above. (The client of a multicast server uses a DatagramSocket to communicate with it; it doesn't
need to use a MulticastSocket.)
17.4. Broadcast
A broadcast is a datagram sent to a "broadcast address". A broadcast address is an IP address whose
host portion is all 1's, meaning all hosts in the subnet: in a Class C subnet, whose netmask is
255.255.255.0, the broadcast address would be A.B.C.255, where A, B, and C are the Class A, Class
B, and Class C parts of the network address.
An RMI broadcast server would be a small further development of the datagram server described
above.
17.4.1. Purpose
An RMI broadcast server is largely indistinguishable from a datagram server. It would function most
probably as a replicated server in a Class C or smaller subnet. Instances of the same RMI remote
object would be executing at several hosts in the subnet. All instances would receive RMI calls
directed at the subnet.
Obviously UDP broadcasts are cheap to implement but rather drastic in their effect on the network.
The UDP Multicast facilities have largely superseded the functions of UDP broadcasts.
17.4.2. Semantics
The semantics of a broadcast remote call are similar to those of a datagram remote call—the call has
been executed "at least zero times"—except that replies may be received from every member of the
subnet.
Most probably, an RMI broadcast server would adopt the semantics of not replying at all on failure,
only replying on success, following the example of the 1983–1984 Sun RPC package.
17.4.3. Implementation
The only difference between a datagram server and an RMI broadcast server is the fact that the latter
is addressed by a broadcast address rather than a specific host address. All that an implementation
would require would be a way of getting the broadcast address into the stub. The elaboration of this is
left as an exercise for the reader.
17.5. Clients
It is important to note that apart from the addition of some callcontrolling mechanism, the clients of
the servers described in this chapter would be largely oblivious to the server type. The reason for this
is that in RMI "the stub is the protocol". Once a client has acquired a remote stub it just executes
method calls. It doesn't have to be at all aware of the protocol implemented by the stub.
17.6. Exercises
1:Design a DatagramRemoteObject class, including methods to control timeouts and
retries.
2:Design a BroadcastRemoteObject class, including methods to control timeouts and
retries.
3:Design a MulticastRemoteObject class, including methods to control joining and
leaving multicast groups, and to control the TTL of messages.
Chapter 18. Selected further topics
Distributed garbage collection—Logging—Debugging—Testing RMI in a single machine
—Performance—RMI and JDK versions—Exercises
18.2.1. Overview
Consider a client which acquires a remote reference to an RMI server. As we saw in §1.5, a remote
reference is initially obtained from a naming service, typically the RMI registry or a JNDI service.
The client then executes zero or more remote method calls via this reference, and eventually releases
the reference, probably by releasing an object which contains it, allowing the remote reference to be
locally garbagecollected in the client JVM.
At this point in the client's execution, we would like a protocol to notify the remote JVM that this
client no longer has any references to the remote object. This is what DGC does.
18.2.2. Description
RMI implements a DGC protocol which keeps track of remote references—references to remote
objects—and notifies the remote JVM when a remote reference has been released.[1]
[1]
RMI specification, §3.2 and §9.
More precisely, DGC uses reference counting: a remote client notifies a remote server
JVM when the number of remote references to a particular remote server has fallen to
zero, i.e. when the client has completely stopped being a client of that server.
When a client's local garbage collector finalizes a remote reference, it informs the server JVM that the
reference has been released via the DGC.clean method. At this point, if the remote server
implements the Unreferenced interface, its unreferenced method is called. This might be
taken by the server as an opportunity to unexport itself, or to clear all local references to itself. As the
RMI system only maintains "weak" local references to exported objects, when there are no other
references to the server (i.e. the application proper holds no references), the server is free to be
garbagecollected by the local JVM. The RMI system takes care of unexporting objects which are
about to be garbagecollected.
As we state elsewhere, it is normally better to allow unexporting to occur as a result of
garbage collection, rather than explicitly forcing it to occur—unless you have some timely
reason for forcing it to happen immediately, such as an error condition, or an impending
shutdown of the entire system.
DGC relies on an entity called a Lease, which is automatically taken out, and periodically renewed,
by an RMI client. The period of the lease is controlled by the system property
java.rmi.dgc.leaseValue, which defaults to 10 minutes.
18.3. Logging
RMI provides facilities for you to log the actions of an application. These logs can be a useful way of
tracking application performance, and collecting information for debugging purposes.
18.4. Debugging
RMI clients can be debugged using standard Java debugging systems.
Debugging RMI servers is more complicated. You can run your RMI servers under a standard Java
debugging system; however, if you set breakpoints inside remote methods, you may cause the client of
the call to timeout while you are diagnosing your server's behaviour, if a client timeout is in effect. It
would be nice to have an integrated clientserver RMI debugging system. At the present time no such
system is available from Sun; other vendors may have products available.
In the absence of such a product, you may use one or more of the following techniques to debug RMI
servers:
• Run the server as a local object in the client JVM, i.e. bypass RMI altogether, and use a
standard debugging product on the client JVM. This will help you diagnose application logic,
but it won't help with problems which only manifest themselves under RMI.
• Set java.rmi.server.logCalls to "true", and redirect System.err to a log file.
• "Instrument" your RMI server with execution traces, and cause these to be saved to a log file.
• Set one or more of the many sun.rmi.* RMI logging properties to BRIEF or VERBOSE,
and cause these logs to be saved to a log file.
The easiest way to get the "sun.rmi.*" logs to be saved to a log file is to redirect System.err.
18.5.1. Loopback
You can test and use RMI in a single machine which doesn't have a network adapter, if you can install
a "loopback driver". This is a protocol driver which satisfies the TCP/IP protocol stack's requirement
for a MAC (media access layer) driver without needing a physical piece of hardware, and lets the
address 127.0.0.1 work inside the box.
Microsoft Windows 95 doesn't have a loopback driver. One way to get around this is to
configure an unused COM port as a dedicated PPP or SLIP connection; disable DHCP,
and manually configure an IP address, e.g. 192.168.1.1. You should then find that you can
ping yourself from a DOS shell:
ping localhost
18.6. Performance
In this section we discuss the performance of RMI and ways to improve it. In order to introduce the
topic, we start by asking two questions about RMI: is it efficient, and is it scalable?
We don't answer these questions. We just provide some information with which you can make your
own judgement, or, better still, construct your own tests. We then provide some tuning tips gathered
from our own experience and other sources.
18.6.2. Efficiency
Is RMI efficient? This question should most properly be answered by another question: "compared to
what?"
Inefficiency in computer programs—and elsewhere—is too often asserted in comparison with:
• nothing
• an incommensurable alternative—"apples and oranges"
• an impractical or unimplementable ideal alternative, instead of a practical and available one.
We will try to avoid these errors.
There are several sources of overhead when using RMI:
• RMIspecific overheads, consisting of JRMP or IIOP protocol overhead and method
dispatching overhead
• DGC overheads (JRMP only)
• in the case of JRMP, overheads due to Java Serialization; in the case of GIOP, overheads due
to GIOP marshalling and unmarshalling
• serialization overheads specifically due to not declaring a serialVersionUID in a
Serializable class, forcing it to be computed at runtime
• DNS delays due to misconfiguration at the client[10]
[10]
If your host is set up to use DNS (Domain Name Service) for hostname
resolution and you are experiencing long RMI delays, try specifying all the
hostname/address pairs involved in the communication in the local hosts file. On
Unixlike platforms this is located in /etc/hosts; on Windows NT it is in
\winnt\system32\drivers\etc\hosts; on Windows 95/98 it is in
\windows\hosts. The format of a typical hosts file is:
IP-address machine-name
e.g.:
127.0.0.1 localhost
On some Unixlike platforms you may need to "compile" the hosts file after
modifying it: consult your platform documentation for details.
• HTTP tunnelling
• network bandwidth limitations.
Apart from serialization itself, the biggest contributors are the serialVersionUID issue and the
DNS issue, which can both contribute delays of many seconds.
It will be seen that most of these issues are not specific to RMI, and are rather hard to avoid when
programming in Java—once you start transporting Java objects over sockets.
The alternative is to not transport Java objects over sockets: this is an incommensurable
alternative. You must compare like with like. If you want to measure the overhead of RMI
as against local method invocation in a local object, with direct access to parameters and
results, please go ahead, but the results aren't comparable, or even very interesting.
As RMIspecific overhead is independent of data volume, the larger the data volume of each
transaction, the less significant RMI overhead becomes.
As we described earlier, RMI makes several contributions towards being more efficient than a "naive"
selfimplemented socketbased transactional system.
18.6.3. Scalability
How well does RMI scale? As before, we should first answer this question with another: "relative to
what?" In this case, the answer is usually "relative to the numbers of simultaneous clients and
transactions". Secondly, what do we mean by scaling "well" or scaling "badly"?
Scaling "badly" means consuming resources—including memory and time—at a linear, quadratic, or
higher rate. For example, a sequential search of a database is linear: the time consumed is linearly
proportional to the number of records. A bubblesort is quadratic: the time consumed is proportional
to the square of the number of items.
Conversely, "scaling well" means consuming resources—including memory and time—at a lower than
linear rate. For example, an indexed search of a database is typically logarithmic: the time consumed
is proportional to log(N), the logarithm of the number of items in the database. This is "good" scaling
behaviour, because the logarithm of N increases much more slowly than N.[11]
[11]
For a general approach to characterising the performance of programming systems see
Knuth, The Art of Computer Programming, Volume I, §1.1 and §1.2 (all editions).
In these terms, RMI scales reasonably well to large numbers of transactions and clients, mainly
because it conserves connections and threads. By contrast, in a naive implementation, the number of
threads would increase linearly with the number of clients, and the number of TCP connection events
would increase quadratically with (i.e. as the product of) the number of transactions and the number
of clients.[12]
[12]
Anecdotal evidence suggests that RMI/JRMP doesn't scale as well as RMI/IIOP: RMI
Mailing List, passim.
RMI still suffers from the overhead of creating threads between successive executions of
ServerSocket.accept, which limits its ability to accept new connections. This
could be alleviated by using a serverside thread pool.
It will be seen from the tables in Appendix B that a number of configurable timeouts are used within
RMI:
• DGC uses several configurable timeouts to control its operations, most notably
java.rmi.dgc.leaseValue at the server
• in Sun's implementation, RMI clients use a timeout controlled by
sun.rmi.transport.proxy.connectTimeout to control connection attempts
• Sun's clientside connection conservation uses a timeout controlled by
sun.rmi.transport.connectionTimeout to expire idle connections
• Sun's serverside TCP connections use a timeout controlled by
sun.rmi.transport.tcp.readTimeout to detect clients which connect but don't
send any—or enough—data.
The ability to configure these timeouts offers a couple of tuning possibilities:
1. Increasing sun.rmi.transport.connectionTimeout (if supported) may improve
performance of clients which perform remote calls to the same host at long intervals, by
eliminating the intermediate disconnect/reconnect steps. Decreasing it indirectly reduces the
number of active threads on busy servers, by causing the client to close the connection more
quickly, and therefore to release a server thread: obviously, these requirements conflict, and
you must choose between faster clients and not overloading your servers; normally, not
overloading the server would be considered more important.
2. Increasing java.rmi.dgc.leaseValue beyond the value of
sun.rmi.transport.connectionTimeout (if supported) has the effect of allowing
the client to close the connection as above, with benefit to the server. Decreasing it below the
value of sun.rmi.transport.connectionTimeout has the effect of keeping the
client's connection permanently open: this is generally undesirable, especially for dialup
connections.
Note that, by default, no read timeout is imposed at clients, nor is any system property
available to impose one. By default, RMI clients will wait forever for a response from the
server. If you need to impose a clientside read timeout, you must use a custom client
socket factory which sets the timeout on the socket before returning it. In our experience
you should do this: any network client which reads with an infinite timeout will sooner or
later experience an infinite delay. The length of the timeout should of course depend on
how long the remote method should take to execute, and on the network bandwidth and
load: it should probably be set high enough so that any reasonable overload falls within it,
but low enough so that any complete outage in the network between the client and the
server is detected without driving the user of the client completely mad. The old Sun RPC
package defaulted to a 60 second timeout.
For largescale RMI the following recommendations have been made:[13]
[13]
Colley, RMI for largescale applications.
1. Use JDK 1.2.2 or later at the server, to avoid the server lockup problem addressed by
sun.rmi.transport.tcp.readTimeout.
2. Use JDK 1.2.2 or later at the server if you must accept HTTPtunnelled remote calls and you
are using socket factories: otherwise you must reimplement the server side of HTTP tunnelling
yourself.
3. Use JDK 1.2 or later at the client, to avoid the DGC deadlock problem in JDK 1.1.x;
alternatively, use at least JDK 1.1.7 and implement a clientside read timeout via
Socket.setSoTimeout in RMISocketFactory.createSocket.
4. Use JDK 1.2 at both server and client to avoid linear DGC behaviour if you have thousands of
remote objects.[14]
[14]
Colley, How bad is it to have many (thousands) of concurrent remote objects.
(Read the entire thread.) Linear DGC occurs in JDK 1.1.x because the client
renews leases one at a time. As locks are held while leases are being renewed, one
slow server can hold everything up. From JDK 1.2, to avoid this problem, leases
are renewed, in batches, in a parallel thread.
5. Set java.rmi.server.hostname at the server—if possible, to an IP address rather than
a hostname—to avoid DNS lookups.
6. Set java.rmi.dgc.leaseValue at the server to much more than the default of 600000
ms (ten minutes).
7. Set sun.rmi.transport.connectionTimeout at the server to a short value such as
500 ms (for remote calls made by the server, e.g. to the registry or other RMI servers): this
frees up connection resources and server threads, but makes it more expensive for the server to
make several remote calls to the same {host, port} at intervals greater than this timeout.
8. Set sun.rmi.transport.tcp.connectionTimeout at the client below its default of
15000 ms (15 seconds) to free up server resources more quickly: again, this makes it more
expensive for the client to make several remote calls to the same {host, port} at intervals
greater than this timeout.
9. If using rmid, tune sun.rmi.activation.execTimeout (the number of milliseconds
that rmid will wait for a child process to start, default 30000 ms—30 seconds) as required for a
busy system, and tune sun.rmi.rmid.maxstartgroup to reflect the number of
activation groups you are prepared to have being started simultaneously (default 3).
10. Try not to run out of memory: use the -Xmx flag to raise Java's selfimposed memory limit at
the server. When an OutOfMemoryError is thrown, something must fail: this exception can
kill any thread at any time.
11. Call System.gc occasionally: the Unreferenced.unreferenced method is one good
place to do this.
As mentioned in Chapter 3, there are several things you can do to improve the performance of Java
Serialization:
1. Always define a serialVersionUID in every class you intend to serialize, whether via
RMI or otherwise.
2. Write custom writeObject and readObject methods for selected, oftenserialized
classes, which do not call defaultWriteObject and defaultReadObject
respectively, but handle the serializable fields of the class and its base classes directly.
3. Define selected, oftenserialized classes as Externalizable instead of Serializable.
You will have to implement the appropriate readExternal and writeExternal
methods for them.
WARNING: doing (2) or (3) makes the classes harder to maintain and evolve compatibly. Choose
these classes carefully.
18.7.2. Clients
RMI clients should not be deployed on JDKs older than 1.1.7, because of essential corrections
introduced in that release. In addition, all JDKs up to and including 1.1.8 have a deadlock problem in
DGC. This problem can only be solved by implementing a clientside timeout: install an
RMISocketFactory whose createSocket methods returns a Socket on which
Socket.setSoTimeout has first been called, with some nonzero value.
If you have thousands of remote objects, you must use at least JDK 1.2 at the client to avoid linear
DGC behaviour.
18.7.3. Servers
RMI servers should not be deployed on JDKs older than 1.2.2. This release addressed a longstanding
issue with RMI servers locking up and ceasing to accept new connections. This release also made it
possible, for the first time, to combine HTTP tunnelling and server socket factories.
If you have thousands of remote objects, you must use at least JDK 1.2 at the server to avoid linear
DGC behaviour.
18.7.4. Interoperability
It is perfectly possible to deploy systems where servers use a different version of the JDK from clients.
It will be seen from the remarks above that deploying a system whose servers use Java 1 and clients
use Java 2 is not feasible. However, a reliable deployment can be made whose servers use JDK 1.2.2
or 1.3 and whose clients use JDK 1.1.8.
When deploying such a mixed system, it is necessary to observe the following:
• the vcompat or v1.1 flags to rmic must be used when compiling the servers: vcompat is the
default
• all classes made available to clients via the codebase must be compatible with JDK 1.1.
18.8. Exercises
1:This exercise assumes you have completed the date/time server and client of Chapter 7 (all
relevant exercises). Run the server with the property java.rmi.dgc.leaseValue set to 5
minutes. Run the client without this property set and measure the interval between the client
exiting and the server exiting. Repeat this test, this time setting the
java.rmi.dgc.leaseValue property to 5 minutes at the client, not the server. Does the
setting of the java.rmi.dgc.leaseValue property take effect at the server or the client?
2:Verify or refute the statement in §18.2.3 about distributed garbage collection and callbacks. Show
your assumptions and experimental method. Construct a distributed cycle containing four remote
objects and retest, again showing your assumptions and method.
Appendix A. Exceptions in RMI
Class hierarchy—Exceptions in servers—Exceptions in clients—Alphabetic list of
exceptions—Remarks
This appendix discusses some of the issues surrounding exceptions in RMI servers and RMI clients,
followed by an alphabetical listing of the exceptions specific to RMI.
A.4.1. java.rmi.AccessException
This indicates that the caller does not have permission to perform the action requested. To be specific,
it is thrown by the bind, rebind, and unbind methods of Naming and Registry. These
methods can only be invoked by callers on the same host as the registry concerned. It is also thrown
by the methods to register and unregister activation groups and activatable objects. These methods can
only be invoked by callers on the same host as rmid.
A.4.2. java.rmi.activation.ActivationException
ActivationException is the base class for UnknownGroupException and
UnknownObjectException.
A.4.3. java.rmi.activation.ActivateFailedException
This is thrown when activation fails during a remote call to an activatable object. Activation fails if
the object required cannot be activated within its activation group for any reason, including failure to
instantiate the object, failure to activate the group itself, or failure to locate the RMI activation system
daemon rmid on the target host.
This exception typically occurs on the first use of a remote activatable stub by an RMI client.
A.4.4. java.rmi.AlreadyBoundException
This indicates that the name argument to a Naming.bind or Registry.bind call is already
bound. If you really want the bind to succeed, it is normally simplest to use the rebind method,
which does a forced bind. However, if you want to take other actions on detecting an existing
binding, you could catch this exception, do an unbind and whatever other processing your
application requires, and retry.
A.4.5. java.lang.ClassCastException
This is a frequent "RMI beginners'" error, typically caused by one of the following:
• trying to cast the result of Naming.lookup into a remote object implementation class
instead of a remote interface
• declaring a parameter or result of a remote method as a remote object implementation class
instead of a remote interface (or an array of implementations instead of an array of interfaces).
For nonbeginners, it can also be caused by trying to return a nonstatic inner class of a remote object
as the result of a remote method. A nonstatic inner class has a hidden reference to the outer class,
which can't be reconstructed correctly at the receiver. Change the inner class to static and the problem
will disappear. If the inner class needs a reference to the enclosing remote object, you must construct
it yourself, and declare its type as the remote interface type, not the remote object type.
A.4.6. java.lang.ClassNotFoundException
In RMI, this exception usually indicates that an RMI code mobility operation has failed. It is typically
encountered in the following circumstances:
1. When exporting a server: the stub class for the server can't be located at all; possibly it was not
generated, or not installed along with the class for the server object.
2. When a server is being bound to the registry: the registry can't load the stub for the server
from its CLASSPATH and the server hasn't annotated the stub with a valid
java.rmi.server.codebase setting; the solution is to correct one of these conditions,
usually the latter—usually the codebase is missing or incorrect, or the specified HTTP
codebase server is not running or incorrectly configured.
3. When a client is looking up a server in a registry: the client can't load the stub from the server
from its CLASSPATH and the registry didn't transmit any codebase annotation with the stub;
the solution is to correct one of these conditions, usually the latter—usually it means that the
registry was able to load the stub from its CLASSPATH and so didn't transmit any codebase
information for the server, and the solution is to fix the registry's CLASSPATH, probably just
by running it from a different directory.
It is important to understand the difference between these situations. The first is just an installation
error. As for the other two, assuming you don't want the registry and clients to load stubs from the
CLASSPATH, the second means that the codebase was never correctly set; the third means that the
codebase was possibly correct enough originally, but was lost on the way through the registry.
If you're having trouble sorting all this out, try running the registry with the system property
java.rmi.server.logCalls set to true and watch the remote calls and exceptions occurring
within the registry itself. This can be most educational.
Another difficulty is that prior to JDK 1.3 this exception is sometimes masked into a
RemoteException or ServerException.
A.4.7. java.rmi.ConnectException
This indicates that a connection has been refused to the remote host for a remote method call. The
detail member contains the original exception, which is likely to be a
java.net.ConnectionException or java.net.NoRouteToHostException. It is safe
to retry an RMI call which gets this exception and still preserve "at most once" semantics.
A.4.8. java.rmi.ConnectIOException
This indicates that an exception has occurred while making a connection to the remote host for a
remote method call. It differs from ConnectException in that it indicates an I/O problem rather
than a real connection problem. The exception arises when making a logical RMI connection involves
I/O on an existing socket connection, rather than creating a new socket connection (which incurs a
java.rmi.ConnectException on failure). This happens when reusing an existing (cached)
connection, or setting up a multiplexed connection.
The detail member contains the original exception. It is safe to retry an RMI call which gets this
exception and still preserve "at most once" semantics.
A.4.9. java.rmi.server.ExportException
This occurs when exporting a remote object. It indicates that the port requested is already in use,
probably by one of the following:
• another process
• another RMI server in the current JVM with an unequal ServerSocketFactory
• an explicitly created ServerSocket in the current JVM.
RMI servers in the same JVM can share ports with each other if they have null or equal server socket
factories, but they can't share ports with other processes of any kind, even other JVMs.
On Unixlike platforms it may also indicate an unprivileged attempt to use a port number below 1024.
A.4.10. java.rmi.MarshalException
This is thrown if a java.io.IOException occurs when:
• marshalling the remote call header or arguments at the client
• marshalling the remote reply header or result at the server
• the receiver does not understand the protocol version of the sender.
The detail member contains the original exception.
Marshalling of arguments occurs at the client; marshalling of the return value occurs at
the server. Under the covers there is also a remote call header containing among other
things a protocol version, which is marshalled and transmitted by both client and server.
It is not safe to retry a remote method which has incurred this exception, because you can't tell
whether or not the method has executed. It is probably also pointless to retry it, as it tends to indicate a
software or configuration problem such as an nonserializable or nonloadable class.
A.4.11. java.rmi.NoClassDefFoundError
Note that this is an Error, not an Exception. It usually follows a prior
ClassNotFoundException, and often indicates a "stale" registry.
This condition can arise frequently during development: a remote interface and remote server are
defined, compiled, and executed; the server is bound into the registry; the interface and server are
modified; everything is recompiled and reexecuted; but the registry still has an old copy of the
interface and stub. The solution is to restart the registry.
This condition can manifest itself inside a ServerError. See also the discussion of
ClassNotFoundException in §A.4.6.
A.4.12. java.rmi.NoSuchObjectException
This indicates that a remote method call has been attempted using a remote stub to a remote object
which no longer exists in the remote virtual machine. Such a reference is known as a "stale" reference.
The situation can occur if a remote stub has been obtained but never used for a long period, during
which the remote object it refers to has been unexported.
If a network partition (router, bridge, gateway, etc.) is present between a client and a remote server, it
is possible for the server's end of the transport to believe incorrectly that the client has crashed. In this
event, DGC may execute prematurely, i.e. the Unreferenced.unreferenced method may be
called prematurely, which may lead to local unexporting and garbagecollection of the server.
This exception can also occur if the remote object has been forcibly unexported.
As these actions are out of their control, RMI clients with retry semantics should generally be written
so as to cope sensibly with this exception. If this exception is thrown in a client attempting a remote
method call, the call can be retransmitted while preserving "at most once" call semantics. It is safe to
retry an RMI call which gets this exception.
Before retrying, obviously the client should obtain a new remote stub for the remote
object. Whether the call succeeds on retry depends on whether the remote stub used for
the call is valid at the time of the call, just as it did on the first attempt.
This exception can also be thrown by the java.rmi.server.RemoteObject.toStub method,
and by the various static unexportObject methods of the
java.rmi.server.UnicastRemoteObject and
java.rmi.activation.Activatable classes. In these cases it indicates that the remote object
is not currently exported, i.e. has never been exported or has already been unexported.
A.4.13. java.rmi.NotBoundException
This indicates that the name argument to a lookup or unbind method of Naming or Registry
is not currently bound, i.e. that nothing in the registry matches name. It is safe to retry an RMI call
which gets this exception (but probably pointless unless a sleepandretry technique is used while
waiting for a remote server to start up).
A.4.14. java.rmi.RemoteException
This means that something unexpected happened in the communications or at the server end. It
indicates a failure in the RMI system or at the remote server.
Unfortunately, depending on the designer of the remote interface, it may also indicate an application
exception condition. This can arise if the designer used RemoteException, or classes derived
from it, for exception conditions specific to the application.
Such a practice provides the designer with a lazy way of specifying the remote interface, as every
method just needs to declare that it throws RemoteException, but it is poor RMI design.
Application exceptions should be declared separately from RemoteException, and should not
extend it.
There are two reasons for this recommendation.
1. The designer must not avoid thinking about what application exceptions need to exist.
2. The client side of the application is much easier to code if application exceptions and RMI
system exceptions are distinct.
In practice, RemoteException should rarely be caught explicitly. Why not? Because it is not
thrown explicitly by the RMI system:[1] instead, one of the exceptions derived from it is thrown. Each
such exception should be caught and handled separately, as each indicates a different kind of remote
problem. Valid exceptions to this rule include the cases where RemoteException is caught for
observation purposes and then rethrown, or where the application just completely gives up on any
kind of remote error.
[1]
except in the case of the corrupted stream described in §A.4.19, which you should
never encounter.
A.4.15. java.rmi.RMISecurityException
This is thrown by the java.rmi.RMISecurityManager, the default security manager in JDK 1.1
environments, to indicate any security violation. It is deprecated in, and not thrown by, JDK 1.2
environments.
A.4.16. java.lang.SecurityException
This indicates that the code was not granted a permission it required to execute. See Chapter 8.
A.4.17. java.rmi.server.ServerCloneException
This exception is thrown if a remote exception occurs during cloning of a
UnicastRemoteObject. The detail member contains the original exception.
A.4.18. java.rmi.ServerError
This means that the remote server got a java.lang.Error executing the remote method. It
indicates a severe error in the server, either a Java system error, a critical resource shortage, or a
coding error, such as an array subscript out of range. The detail member contains the original
exception.
This exception "wraps" an instance of java.lang.Error inside an exception derived
from java.rmi.RemoteException, so that it can be thrown by a remote method
invocation within the rules of Java.
A.4.19. java.rmi.ServerException
This means that the remote server got a RemoteException while executing the remote method.
The detail member contains the original exception. A ServerException is thrown if the server
encounters:
• a SkeletonMismatchException while dispatching the call
• an UnmarshalException while decoding arguments
• a MarshalException while encoding the result
• any remote exception whatsoever while executing the call. This includes the case where the
server is doing a remote method invocation of its own, e.g. a daisychained or forwarded call.
It also includes the case "method number out of range due to a corrupted stream".
A remote exception encountered during a further remote call can be any of the remote exceptions
encountered by clients:
• ActivateFailedException
• ConnectException
• ConnectIOException
• MarshalException
• NoSuchObjectException
• RemoteException
• SkeletonMismatchException
• StubNotFoundException
• UnmarshalException
• UnknownHostException
The RMI specification is selfcontradictory concerning this exception. §A.3 says it is
thrown in the context of "any exception that occurs while the server is executing a remote
method", and the commentary of §A.3.1 says that "the client will know that its own
remote method invocation on the server did not fail, but that a secondary remote method
invocation made by the server failed". However, table §A.3.1 includes several exceptions
arising out of the execution of the original call, including UnmarshalException
unmarshalling the parameters, and MarshalException marshalling the return value.
Sun's implementation agrees with the tables in these sections of the specification, rather
than the prior commentary in each case.[2]
[2]
In fact, up to at least JDK 1.3, a MarshalException in the server
while encoding the result triggers a design bug leading to an RMI protocol
error and an UnmarshalException at the client. Fixing this bug would
require an API change: adding a
java.rmi.server.RemoteCall.getResultStream method which
can be executed before the success or failure of the call is determined.
A.4.20. java.rmi.server.ServerNotActiveException
This is thrown by RemoteServer.getClientHost if it is called from an execution context—
thread—which is not currently servicing a remote method call (i.e. the main thread or a background
activity of an RMI server).
A.4.21. java.rmi.ServerRuntimeException
This means that the remote server got a java.lang.RuntimeException executing the remote
method. This exception is not thrown from RMI servers executing in Java environments from Java
SDK 1.2 or later; instead the original RuntimeException is propagated to the client.
If this exception, or a RuntimeException, is thrown by the server, the exception propagates to the
client. The server continues to operate.
A.4.22. java.rmi.server.SkeletonMismatchException
This indicates that the class of the skeleton for the remote server didn't match the stub class being
used by the client, or that the skeleton no longer matches the remote interface, due to evolution of the
latter. Skeletons are not required by the JDK 1.2 stub protocol, hence this exception is deprecated in
JDK 1.2, and only thrown by the server if the client is using the 1.1 stub protocol: this occurs if the
stub is generated with rmic v1.1, or if the client is executing in a JDK 1.1 environment.
A.4.23. java.rmi.server.SkeletonNotFoundException
This indicates that the class of the skeleton for the remote server was not found. Skeletons are not
required by the JDK 1.2 stub protocol, hence this exception is deprecated in JDK 1.2, and only thrown
by the server if the client is using the 1.1 stub protocol: this occurs if the stub is generated with rmic
v1.1, or if the client is executing in a JDK 1.1 environment.
For possible causes of this exception see the discussion in §A.4.25, reading "skeleton" for "stub"
throughout; see also §A.5.
A.4.24. java.rmi.server.SocketSecurityException
This indicates that the code exporting a remote object (either by construction or by calling an
exportObject method) does not have permission to create a java.net.ServerSocket on the
port number specified.
This is a typical early hurdle for RMI developers. RMI has extensive security implications: code
mobility is disabled in RMI unless a security manager is installed; and so you tend to always install
one. This has the effect of disabling large parts of the Java runtime system. The solution is to tell the
security manager to reenable the parts required by your application, by providing an application or
installationspecific security policy file, and naming the URL of this file in the
java.security.policy property:
java -Djava.security.policy=URL
To enable socket creation, you must provide the following line in this file:
java.net.SocketPermission "*:1024-","accept,connect,listen,resolve"
(It is already there from JDK 1.3 onwards.) You can, and probably should, be much more restrictive
about which hosts and ports are permitted. See the JDK security documentation for details.
A simpler solution is provided in a number of Sun's RMI tutorials, but, as Sun say, this
solution is for demonstration purposes and should not be deployed in a serious RMI
application.
See Chapter 8 for a fuller discussion.
A.4.25. java.rmi.StubNotFoundException
This is thrown if a valid stub class could not be found for a remote object when it is exported. It may
also be thrown when an activatable object is registered via the
java.rmi.activation.Activatable.register method.
Both cases indicate a problem with the software installation: a codebase problem, or a failure to install
(or generate) the stub for the remote server. The classfile of the stub should be located in the same
directory as the classfile of the remote server, or in the same JAR file under the same path, because it
is generated with the same package name.
A.4.26. java.rmi.UnexpectedException
This indicates that the client of a remote method call has received, as a result of the call, an exception
which is not among the checked exception types declared in the throws clause of the method in the
remote interface. (Checked exception types exclude runtime exceptions and errors.) Essentially, the
server threw an exception unexpected by the stub.
This in turn indicates that the version of the remote interface or stub at the client disagrees with the
version at the server, or that inconsistent versions of the remote interface and remote object exist at the
server. Solution: recompile the client, server, and interface, and regenerate the stub.
A.4.27. java.rmi.activation.UnknownGroupException
This indicates that an invalid ActivationGroupID has been supplied as a parameter to methods
of various Activation classes and interfaces, or as a member of an ActivationDesc object
supplied to those methods.
A valid ActivationGroupID is obtained on registration of an activation group with the
Activation subsystem. It can become invalid if the activation database is corrupted or lost.
A.4.28. java.rmi.UnknownHostException
This is only thrown by the methods of Naming and Registry.[3] It indicates that a
java.net.UnknownHostException occurred while creating a connection to the remote host for
a remote method call. The detail member contains the original exception. It is safe to retry an RMI
call which gets this exception, although it may indicate that there is something wrong with either the
stub or the local network's DNS.
[3]
RMI specification, §4.3, §6.1, §A.4.
Like java.rmi.ConnectException, this exception wraps a nonremote exception
inside a remote exception: an exception derived from java.rmi.RemoteException.
A.4.29. java.rmi.activation.UnknownObjectException
This indicates an invalid ActivationID parameter to methods of various Activation classes and
interfaces.
A valid ActivationID is obtained on registration of an activatable object with the Activation
subsystem. It becomes invalid if it is unregistered, or if the activation database is corrupted or lost.
A.4.30. java.rmi.UnmarshalException
This is thrown if any of the following occurs while unmarshalling the parameters or results of a
remote method call:
• any exception unmarshalling the call header
• the protocol for the return value is invalid (when received by the client); i.e. the client and
server cannot agree on a protocol to use for the call
• a java.io.IOException while unmarshalling parameters or the return value
• a java.lang.ClassNotFoundException while unmarshalling parameters or the return
value
• no skeleton can be loaded at the server: skeletons are required in the JDK 1.1 stub protocol, but
not in the JDK 1.2 stub protocol
• the method hash is invalid (e.g. missing method)
• a failure to create a remote reference object for a remote object's stub when it is unmarshalled.
The detail member contains the original exception if any.
This exception can occur at a server when unmarshalling parameters of a remote method invocation
prior to executing it, or at a client when unmarshalling the result.
Unmarshalling of parameters takes place at the server; unmarshalling of the return value
takes place at the client. Under the covers there is also a remote call header, containing
among other things a protocol version, which is decoded at both client and server.
For further information about remote failures in clients, refer to Chapter 5
80)
java.rmi.activation.security.class Class name of RMI Security Manager to
Default:java.rmi.RMISecurityMa
er.
java.rmi.activation.security.codebase Codebase URL used by RMI class loade
activation.
by RemoteServer.getLog. As the d
value is false, servers are normally silent.
(default) database activity are
printed to System.err
likely to be useful for
applications that use
RMI/HTTP.
C.1. Books
• Arnold, Gosling, and Holmes, The Java Programming Language, 3rd edition, AddisonWesley,
2000
• Campione and Walrath, The Java Tutorial, 2nd edition, AddisonWesley, 1998
• Campione, Walrath, Huml, et al., The Java Tutorial Continued, AddisonWesley, 1999
• Chan, The Java Developers Almanac 2000, AddisonWesley, 2000
• Chan, Lee, and Kramer, The Java Class Libraries, 2nd Edition, Vols I, II, and Supplement,
AddisonWesley, 1998, 1999
• Freeman, Hupfer, and Arnold, JavaSpaces Principles, Patterns, and Practice, AddisonWesley
1999
• Gong, Inside Java 2 Platform Security: Architecture, API Design, and Implementation,
AddisonWesley, 1999
• Gosling, Joy, Steele, and Bracha, The Java Language Specification, 2nd edition, Addison
Wesley, 2000
• Lea, Concurrent Programming in Java, AddisonWesley, 1997
• Lee and Seligman, JNDI API Tutorial and Reference, AddisonWesley, 2000
• Shannon et al., Java 2 Platform, Enterprise Edition: Platform and Component Specifications,
AddisonWesley, 1999
• Waldo et al., The Jini Specifications Second Edition, AddisonWesley, 2001
• Wilson and Kesselman, Java Platform Performance, AddisonWesley, 2000
• Mullender, ed., Distributed Systems, 2nd edition, AddisonWesley, 1993
• Singh, The Code Book: The Secret History of Codes and Codebreaking, Fourth Estate
Limited, 1999
• Stevens, TCP/IP Illustrated, Volume I, AddisonWesley, 1994
• Stevens and Wright, TCP/IP Illustrated, Volume II, AddisonWesley, 1995
• Stevens, TCP/IP Illustrated, Volume III, AddisonWesley, 1996
• Stevens, Unix Network Programming, 2 vols, Prentice Hall, 1998
• Tanenbaum, Computer Networks, 3rd edition, Prentice Hall, 1996
C.2. Papers
C.2.2. Specifications
• Java Object Serialization Specification, Revision 1.43, JDK 1.2, November 1998, distributed
with Java JDK 1.3, or via the Java Software home page
• Java Remote Method Invocation Specification, Revision 1.7, Java 2 SDK, Standard Edition,
v1.3.0, December 1999, distributed with Java JDK 1.3, or via the Java Software home page
• Object Management Group specification (CORBA,IIOP); at http://www.omg.org
• Joint Revised Submission CORBA/Firewall Security, OMG Document orbos/980603 (a
minor revision of orbos/980504), at http://www.omg.org
Appendix D. Glossary
API
Application programming interface
CDR
Common data representation
CGI
Common gateway interface
CORBA
Common object request broker architecture
COS
Naming Common Object Services—Naming
DCE
Distributed Computing Environment
deserialization
decoding a stream of bytes resulting from a serialization, so as to reconstruct a copy of the
original object graph
DGC
Distributed garbage collection
DNS
Domain name service
EJB
Enterprise JavaBeans
FTP
File transfer protocol
GIOP
General InterORB protocol
GUI
Graphical user interface
HTML
Hypertext markup language
HTTP
Hypertext transfer protocol
HTTPS
Secure HTTP
ICMP
Internet Control Message protocol
IDL
Interface definition language
IETF
Internet Engineering task force
IIOP
Internet InterORB protocol
IOR
Interobject reference
IP
Internet protocol
J2EE
Java 2 Enterprise Edition
JAAS
Java authorization and authentication service
JAR
Java Archive
JDK
Java Development Kit
JNDI
Java naming and directory interface
JRE
Java Runtime Environment
JRMP
Java remote method protocol
JSSE
Java secure sockets extension
JVM
Java virtual machine
LAN
Local area network
LDAP
Lightweight directory access protocol
IMAP
Internet message access protocol
marshalling
packing up the parameters to a remote method at the client, or the process of packing up the
result at the server
NDS
Novell directory services
NNTP
Network news transfer protocol
OMG
Object Management Group
ORB
Object request broker
POP
Post office protocol
RFC
Request for comments
RPC
Remote procedure call
RMI
Remote method invocation
rmic
RMI compiler
rmid
RMI activation daemon
RST
reset serialization encoding an object and its dependent object graph into a stream of bytes
SMTP
Simple mail transfer protocol
SNMP
Simple network management protocol
SPI
Service provider interface
SSL
Secure sockets layer
SUID
Serial version unique identifier
TCP
Transmission control protocol
TCP/IP
TCP over IP
TTL
Time to live
UDP
User datagram protocol (not "unreliable datagram protocol")
unmarshalling
decoding the parameters or return value of a remote call at the receiver
URI
Uniform resource identifier
URL
Uniform resource locator
VM
Virtual machine
WAN
Wide area network
XDR
external data representation