Enterprise developers everywhere are lining up to find out more about distributed object
computing and how best to make use of it. Distributed objects have been a long time coming, with
more than five years of buzz about Object Management Group's CORBA, but presently two distinct
alternatives to CORBA for the Java developer are coming on strong: RMI (a product of Sun
Microsystems, Inc.) and now Voyager (ObjectSpace, Inc.) both offer viable approaches to
implementing distributed objects. Each system has, or plans to offer, support for the widely
publicized CORBA protocol called IIOP, as well.
What is it about distributed object computing that is so compelling? And how can a newcomer
to network programming get started in this exciting but somewhat daunting field? I'll answer those
questions while offering a gentle introduction to building real distributed object systems. The
latest developments in distributed object systems are proving to be the most powerful and user-friendly.
So for this introduction we will take a close look at the newest and sportiest distributed object
system out there right now: ObjectSpace Voyager. After you've taken a look at this productwhich
is free for commercial useyou may not want to look back.
From Client/Server To Distributed Object Computing
In the Beginning
With the advent of TCP/IP and UDP/IP sockets on the UNIX platform in the early 1980s client-server
computing began to heat up. A common style of network programming quickly came about in which "client"
programs could communicate and interact with distant "server" programs by exchanging text-based messages
of varying complexity. The precise agreement between these programs on message format, meaning, and
content has come to be known as a network, or wire protocol. The early wire protocols
were simple two-way conversations, usually where the client would send a request, expecting the server
to respond with one out of a handful of simple responses.
While network programming and network protocols have advanced considerably over the years,
ironically, the original work in the field still forms the basis for most of what we do on the Internet
today. In fact, all of the predominant protocols underlying the Internet were written according to the
same basic recipe used to create the TCP/IP-based protocols from the early 1980s.
The Formative Years
Fast forward to the late 1980s. While the wire protocols were easily understood and could be formally
documented in a concise manner, they proved difficult to implement by your average programmer. Developers
faced a semantic gap between calling local functions and using remote functionality of a server program.
In a distributed program the semantics for calling, or in this case hollering out to some function on a
remote machine, were unnatural and ultimately problematic to the developer.
New ideas to provide remote function call semantics, or remote procedure call (RPC) as it came to
be known, would hopefully come to the rescue and make network programming simpler and more widespread.
Up until the advent of RPC, client/server programming remained one of the deep, dark secrets of highly
specialized, low-level software developers. It was hoped that RPC, which later became ground zero for
a standards battle, would spark a proliferation of new and as yet unimagined distributed applications.
The First Circle
While RPC did live up to its promise in function, its unwieldy form failed to make life easier for
developers. In a finished program, there would no longer be a semantic gap between calling a local
function and calling a remote function; both calls would look identical. This was, indeed, progress
in distributed computing. But to get to the finished product, application developers were sidetracked
and intimidated by issues of binary data byte ordering, tedious functional specifications, and costly
Meanwhile, object-oriented computing and C++ were becoming mainstream. The focus for
the next decade of work in the distributed computing world would shift from remote procedure call
to remote method (or member function) invocation, the object-oriented equivalent of RPC. But the wait
would be long, and in the interim, the past failure of RPC to deliver caused many developers to take a
backward glance at the proven protocols of yester-year. In fact, the utterly simple and now ubiquitous
HTTP protocol was created in the same style as the vintage protocolsalmost as though remote
method invocation and RPC had never existed.
Distributed Objects Come of Age
Today, most of the lessons of RPC have been learned, and still other problems posed by RPC have
managed to creep back into some distributed object systems, such as the need for detailed functional
specifications and expensive tools that generate copious amounts of source code. But what may have
prevented early distributed object systems from overcoming the limitations of RPC is the programming
languages on which these systems were based. CORBA (for Common Object Request Broker Architecture),
for example, started out with language bindings for the languages COBOL, C, and C++.
Now, with the advent of Java and its modern language features like threads, reflection, serialization
and garbage collection, it is possible to build robust distributed object systems that are simple to
learn and use. Sun RMI and ObjectSpace Voyager are two examples of this new breed, and the shortcomings
of CORBA for Java are gradually being ironed out as well. All of the distributed object systems
mentioned will eventually run over the IIOP protocol, which could help bring about a transition from
HTTP to IIOP on the Internet, as was forecast by Marc Andreessen of Netscape Communications, Inc. But
as we enter this new era of possibility there are strong inertial forces holding back the advancement
predicted by Andreessen. Only time will tell whether the simplicity of HTTP or the power of IIOP will
prevail as the protocol of choice for the Internet.
Example: Implementing Remote Objects
In large part, the choice between HTTP-based and IIOP-based technology will be made by the developers
who design the future vehicles we will drive out onto the Internet, and who always look for better ways
to program. To that end, I turn your attention to an illustrative example of distributed objects. This
quick test drive will show how to create remote objects and work with the Voyager system. With a simple
working example in hand we will pop open the hood and look inside the example to see how this technology
works its apparent magic.
Defining the Problem
Let's say you're designing a program to keep users at your corporate headquarters informed about
the weather conditions outside (because most of them probably sit in cubicles with no windows). Your
program will rely on a single PC outfitted with special weather monitoring equipment. Let's say this
unique "WeatherPC" can report the following conditions: temperature, humidity, barometric pressure,
wind speed, and direction. Furthermore, let us assume that the WeatherPC takes a reading once a minute,
and you would like to make this up-to-date information available to everyone in the office who has a
Here is how we might proceed to build the system using distributed objects. First, let's design a
Java interface to represent the functionality we would like to extract from the weather station. It
might look like what you see in Listing 1.
Next, we'll create a class that implements this functionality. Leaving out the details of how the
class actually interacts with the
weather monitoring equipment, we'll simply delegate that job to a class called WeatherSniffer (not shown).
The implementation class, called WeatherServiceImp is shown in
This is simple enough, though you may be wondering why go through the trouble of using a Java
interface in this situation? While it is not really necessary, using an interface here will help to
illustrate the inner workings of the example in the next section. Before jumping into distributed
objects, let's look at how the code we have so far could be used to build a stand-alone weather
program intended to run on the WeatherPC.
A Local Object Solution
If we were concerned only with writing a program to display weather conditions on the WeatherPC itself,
it might suffice to stop right here. The short program of Listing 3 has the makings of what we would need.
The WeatherWatcher1 program is straightforward. It creates a WeatherServiceImp object and invokes
methods on that object through the WeatherService interface. Bear in mind that WeatherServiceImp
delegates all of the work to a class called WeatherSniffer, which we understand has access to
special equipment onboard the WeatherPC. This program would be sufficient if we were content to
display the current weather on a single PC. But what about those poor cubicle-bound employees
who also would like to know what the weather is like outside? If they were to run the program
on their own computers, it wouldn't work because the WeatherSniffer class needs access to the
special equipment of the WeatherPC. To provide for the other users, surprisingly, requires few
modifications to the program you see in Listing 3.
A Remote Object Solution
The ObjectSpace Voyager 2.0 system is capable of taking an existing Java class and "treating" it to
create a new class with an identical interface. This special helper class, in Voyager terminology, is
called a "proxy" class. It is created in the image of another class and seems just like the original
to anyone who uses it. But there's a catch: a proxy object does no real work by itself. Instead, a
proxy object uses network communications to create and remotely control an instance of the real class
it represents (you'll see how this works a little later). In other words, the proxy object acts like
a mediator between the caller and the real object. It is through proxies that all remote methods
calls are made.
In a previous release, Voyager 1.0 provided a command line utility for generating proxy classes,
but with version 2.0 the utility is no longer needed. Instead, Voyager uses Java reflection to
inspect a class and create the byte codes of a proxy class for it at runtime. This is a remarkable
improvement over the earlier approach, because it obviates the need for any source code generation
at all, and eliminates a tedious precompilation step. Whenever Voyager needs a proxy class, it
generates byte codes for it on the fly and uses the Java class loader to bring the new code into
the Java VM. In fact, the new classes need not even be written to disk!
As far as the developer is concerned, creation of proxy objects is fairly simple. One need only
ask the Voyager API to "construct" a proxy object using a regular object as a template. I've modified
the WeatherWatcher program to construct a proxy object for the WeatherServiceImp class. The new
version of the program is shown in Listing 4. As you can see, the program is substantially unchanged except for references to the Voyager system on two lines: an import statement, and the WeatherService declaration.
This minor change to the program makes a world of difference in how it works. But before stepping through
its new behavior, there is one more part of the Voyager system you should know about. To support the
creation and "housing" of remote objects requires that a Java virtual machine (JVM) be running on
each actual machine involved. To provide this, Voyager comes with a server program built right in
along with its other classes. The server is written in Java, although no source code for the server
is provided. It plays the same role as the ORB (object request broker) does in CORBA systems:
creating and managing remote objects on behalf of the programs that use them over a network.
The Voyager server can be run this way:
$ java com.objectspace.voyager.system.Main 8000
The "8000" argument used above is a port number through which the server can be contacted by
external programs. Connecting to a server always requires a port number. Your Web browser usually
connects to Web servers on port 80, without your needing to specify the port number, but
occasionally you will see URLs with port numbers attached, for example, http://www.yourcompany.com:8080.
Back to the example program. Let's say the WeatherPC is known as weather-pc.yourcompany.com.
Further, assume the Voyager server is running on the WeatherPC with port 8000, and the WeatherWatcher2
program will run on an employee's desktop computer. Now we can step through the WeatherWatcher2
program to see how the remote object version behaves. Here again is the modification made in the
WeatherService service =
Instead of creating our WeatherServiceImp object with "new", this time we delegate the job to
Voyager's construct() method. The construct() method needs to know the name of the class to create,
and the server on which to create it remotely. This statement accomplishes quite a bit: it not only
creates a remote object, but gives us a way to interact with that object by creating a proxy object
for it, too. Instead of returning a WeatherServiceImp object, the construct()_method returns a
proxy object instead. The proxy is a regular local Java object, which representsor stands
in for as a surrogatea remote WeatherServiceImp object created and running in a completely
different JVM. Every action carried out on the surrogate will be passed along over the network to
the real object on the remote computer. With the remote object created, let's see what happens a
little bit later in the program. Consider the line that follows:
System.out.println("Temperature: " + service.getTemperature();
The method getTemperature() in the local proxy is what is being called here, but the proxy doesn't
do the actual work of the method. Instead, the proxy takes the argumentsif any, but in this
case there are noneand passes them across the network to the actual WeatherServiceImp object
on the remote machine. Then the proxy remotely invokes the real getTemperature() method on the
remote object. Finally, the proxy gathers the return value of the remote method from the network
connection and returns it back in the local program. In other words, the statement above, though
coded to look just the same as in the first program, can transparently call on a remote object to
accomplish its job.
Recall the use in both programs of the WeatherService interface. Using an interface to represent
classes that may either be local (as is the case with WeatherServiceImp) or remote (when a proxy is
being used) cuts down on the amount of code that must change between the WeatherWatcher1 and
WeatherWatcher2 programs. Moreover, the use of an interface illustrates how distributed objects
can, at some level, be made transparent even to the program that uses such objects. In a larger
program it is likely that the WeatherService interface would be passed around to many different
classes. Those classes would never need to know whether WeatherService represented a local object
(WeatherServiceImp) or a remote object (WeatherServiceImp's proxy).
The second version of the WeatherWatcher program, while barely different from the first, is no
longer what it appears. It makes the unique features of the WeatherPC accessible from any other
computer on its network. Now, the weather conditions outside of the office can be monitored by
any employee right from his or her own computer. In practical terms, this overall approach has
enormous power to transform the way we think about programs, because the object-oriented code you
write can now have far-reaching effects across a network.
This example covered quite a bit of new ground. It demonstrated the ability to make a unique
resource available to other computers on a network. We saw how, almost as an afterthought, a stand-alone
Java program could be converted into a distributed application utilizing a remote object through the
Voyager server. Some subtle questions about the program still linger, though. For example, how is it
that creating a WeatherServiceImp proxy object can result in the remote creation of a WeatherServiceImp
object? And second, the method getWindSpeed() returns an integer, while getWindDirection() returns a
String object. How can these different data types be returned from the remote object to the local
surrogate without special programming?
How It Works
Sockets, Reflection, Serialization, Threads
Right out of the box, the Java classes make working with TCP/IP sockets relatively easy. Setting up
a network connection between two programs is just a matter of enlisting two built-in Java classes: Socket
and ServerSocket. With a connection established, Java makes input and output over that connection easy by
using the same stream classes you would use for file I/O. For the designers of products like Voyager,
these tools are a godsend. Needless to say, standard C or C++ have never offered anything comparable.
In addition, Java Reflection makes it possible to programmatically discover the member variables and
methods of a class at runtime. Reflection can even be used to create objects and invoke methods dynamically
using the special classes Constructor and Method. This new language feature makes it possible for Voyager
to inspect the methods of an arbitrary class, generate a proxy class to match, and load up the byte codes
for the proxy class.
Serialization is the process of taking the member data of an object and representing it as a serial
stream of bytes, usually for the purpose of storing the data in a file or database. Serialization, when
combined with a socket connection can also be used to transmit the state of objects from one place to
another. What's more, objects endowed with serialization capability can read in their state from a serial
data stream, too. And best of all it doesn't take any programming to get the benefits of serialization.
Finally, Java is a threaded language, meaning that lots of things can be going on concurrently within
a single process. Threads relieve programmers from tricky input/output situations where multiple socket
connections are involved, like when reading and writing need to take place simultaneously. All of these
language elements taken together pave the way to distributed object computing tools like Voyager, which
can be seamlessly integrated into your programs.
Putting Them All Together
Given these four essential tools; sockets, reflection, serialization, and threads, we can begin to
put the pieces together to see how distributed objects in Voyager are made to work. First, the creation
of a remote object will be described, step-by-step, followed by the operations involved in remote method
Remote Object Creation
1. A proxy object is created locally, given a class name, and the host and port number of a
Voyager server. In our example, this looks like:
2. The construct() method calls on the Voyager class library to create a socket connection to
the Voyager server named in the constructor (unless a connection to that address already exists).
3. With a socket connection established, the Voyager server dynamically loads the byte codes
of the named class, in our case WeatherServiceImp.
4. After the WeatherServiceImp class is loaded, Java reflection is used to construct an
instance of the class in the remote JVM of the server.
5. A unique identifier for the remote object is created by the Voyager server and a reference
to the object itself is stored in a table on the server along with the identifier.
6. The unique identifier is sent back over the socket to the proxy object, which keeps a
copy of the name on hand, creating a means of connecting, or binding, the surrogate object to the actual
7. The local proxy object can then be used to refer to and remotely invoke methods on an
object that exists within the remote Voyager server.
Remote Method Invocation
1. In a local, proxy object-say an instance of WeatherServiceImp's proxy-a method is called. For
the purpose of this discussion, let's say it is the getWindDirection() method.
2. The socket connection established during the creation of the remote object is used again,
this time to send along the unique identifier of the object on the other side of the connection.
3. In addition to identifying the object of interest on the Voyager server, it is necessary
to identify which method should be invoked remotely. This information is sent to the server as well.
4. And, if the method requires arguments, such arguments (as passed to the surrogate method)
are written out across the socket connection using serialization.
5. After deserializing the method arguments, if any, and turning that serial data into either
Java primitives or objects, the Voyager server uses reflection to invoke the desired method.
6. The return value of the method is first obtained by the Voyager server which then uses
serialization to send the value back over the socket connection to the proxy object.
7. Finally, having read the return value data from the socket and deserialized it, the proxy
object gives the return value (again, either a built in type or an object) over to the caller.
It is crucial to note that serialization can be done on any data type imaginable, from a simple integer
to a height-balanced tree. Serialization makes it possible to use remote objects and their methods just as
one would use local objects, with almost no restrictions on the form or structure of the data types
You may be wondering where threads come into play. To support multiple distributed objects and multiple
remote clients, the Voyager libraries employ numerous threads on both sides of every network connection
with specialized roles like listening for connections, keeping in touch with active objects, and determining
when objects are no longer in use. That last task is called distributed garbage collection. Because in Java
we don't explicitly free objects from memory, the task of memory management is taken care of for you in
Voyager as well, though Voyager also allows for objects to have a specified life span.
This walk-through should give you a better grasp of what goes on behind the scenes with a typical remote
object in the Voyager system. Other tools for implementing distributed objects work along similar lines,
but significant differences do exist. For example, with a CORBA product you won't be able to convert existing
classes into remote objects on the fly at runtime. Nor will you be able to easily take advantage of
serialization to toss complex objects back and forth across a network.
If you're eager to try out distributed object computing on your own, I encourage you to download
Voyager from the ObjectSpace Web site and give it a whirl. It has good documentation with interesting
example programs to stir up your imagination. Even if you don't always have access to more than one
computer, you can use Voyager on a single machine by running separate programs concurrently-these
programs will still communicate with TCP/IP sockets if you set your computer name to "localhost."
It's like having a network in a computer, and it's an easy way to get started with distributed objects.
Another easy path of entry into the distributed object's world is through RMI, which you probably already
have if you program with Sun's Java JDK. Finally, certain CORBA products are available free for evaluation
purposes. Diving right in with distributed object computing can be a lot of fun, and it might just make
your next project a whole lot easier.
Russ Ethington lives and works in New York City, where he writes distributed computing software on Wall Street. He can be contacted at [email protected].