O'Reilly logo

Java RMI by William Grosso

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Chapter 4.  The Same Server, Written Using RMI

In this chapter, we continue our discussion by reimplementing the printer server using RMI as a distribution mechanism instead of sockets. As part of doing so, I will introduce the core infrastructure of RMI in a familiar setting. In fact, the application itself will look remarkably similar to the socket-based version. By the end of this chapter, you will have received a glimpse at how an RMI application is structured and the basic sequence of steps required to build one.

The Basic Structure of RMI

In the previous chapter, we covered the basics of implementing a socket-based distributed application. In doing so, we reinforced the lessons of the previous two chapters on streams and sockets. In addition, we discovered that the code necessary for writing a socket-based distributed application falls into five basic categories:

  • Code that actually does something useful. This code is commonly referred to as business logic.[20] An example is an implementation of the Printer interface.

  • User interface code for the client application.

  • Code that deals with marshalling and demarshalling of data and the mechanics of invoking a method in another process. This is tedious code, but it is straightforward to write and is usually a significant percentage of the application.

  • Code that launches and configures the application. We used a number of hard-wired constants (in NetworkBaseClass) to enable the client to initially connect with the server. And we wrote two main( ) methods—one to launch the client and one to launch the server.

  • Code whose sole purpose is to make a distributed application more robust and scalable. This usually involves one or more of the following: client-side caching (so the server does less work); increasing the number of available servers in a way that’s as transparent as possible to the client; using naming services and load balancing; making it possible for a server to handle multiple requests simultaneously (threading); or automatically starting and shutting down servers, which allows the server lifecycle to conserve resources.

In any distributed application, programmers need to write the first and second types of code; if they did not need to write the business logic, the application wouldn’t be necessary. Similarly, the user interface, which enables users to access the business logic, needs to be written for any application. And the fifth type, code that enables the application to scale, can be the most difficult and application-specific code to write.

The third and fourth types of code, however, are different. Most of this code can be automatically generated without much programmer thought or effort.[21] It may seem difficult to write marshalling code if you’ve never done so before. However, by the second time, it’s easy. By the third time, most programmers are flat-out bored by the task.

We will see in this chapter that RMI either already contains—or will automatically generate—most of the code in the third and fourth categories. Indeed, this alone is a compelling reason to use RMI.[22]

Methods Across the Wire

Though convenient, automatically generating marshalling and demarshalling code is mostly a side effect produced in the service of a much more important goal. In a nutshell:

RMI is designed to make communication between two Java programs, running in separate JVMs, as much like making a method call inside a single process as possible.

This is an ambitious goal. How does RMI achieve it?

Recall that in order to communicate with the printer server, we wrote an object, ClientNetworkWrapper, which did three things:

  • It opened a socket.

  • It told an instance of DocumentDescription to write itself to the stream.

  • It read and interpreted information from the input stream associated with the socket.

In addition, we wrote a companion object, ServerNetworkWrapper, which played an analogous role on the server side.

RMI relies on two similar types of objects that are automatically generated by the RMI Compiler from an implementation of the server: stubs and skeletons. A stub is a client-side object that represents a single server object inside the client’s JVM. It implements the same methods as the server object, maintains a socket connection to the server object’s JVM automatically and is responsible for marshalling and demarshalling data on the client side. A skeleton is a server-side object responsible for maintaining network connections and marshalling and demarshalling data on the server side.

Tip

The word stub is actually used to mean two different things. Depending on context, it might refer to a stub class that is automatically generated from a server class object. Alternatively, it might refer to an instance of a particular stub class (that is, to a reference to a specific instance of the server class). Because stubs have such a well-defined role in a distributed architecture, the meaning is usually clear from context. Similarly, skeleton can either refer to the skeleton class or to an instance of the skeleton class.

The basic procedure a client uses to communicate with a server is as follows:

  1. The client obtains an instance of the stub class. The stub class is automatically pregenerated from the target server class and implements all the methods that the server class implements.

  2. The client calls a method on the stub. The method call is actually the same method call the client would make on the server object if both objects resided in the same JVM.

  3. Internally, the stub either creates a socket connection to the skeleton on the server or reuses a pre-existing connection. It marshalls all the information associated to the method call, including the name of the method and the arguments, and sends this information over the socket connection to the skeleton.

  4. The skeleton demarshalls the data and makes the method call on the actual server object. It gets a return value back from the actual server object, marshalls the return value, and sends it over the wire to the stub.

  5. The stub demarshalls the return value and returns it to the client code.

Stubs and skeletons are shown in Figure 4-1.

A basic RMI call with a stub and skeleton

Figure 4-1. A basic RMI call with a stub and skeleton

If this approach seems familiar, it’s because the stub and the skeleton are really automatically generated, object-oriented versions of the objects we created for our socket-based printer server.

Let’s take a close look at this. Here is part of the stub generated for our NullPrinter class:

public final class NullPrinter_Stub extends java.rmi.server.RemoteStub implements 
    com.ora.rmibook.chapter4..Printer, java.rmi.Remote {
   ...
    // methods from remote interfaces
    
    // implementation of printDocument(DocumentDescription)
	public boolean printDocument(com.ora.rmibook.chapter4.DocumentDescription $param
         DocumentDescription_1) throws om.ora.rmibook.chapter4.PrinterException,
         java.rmi.RemoteException {
		try  {
			...
			java.rmi.server.RemoteCall call = ref.newCall((java.rmi.server.
                  RemoteObject) this, operations, 0, interfaceHash);
			try  {
				java.io.ObjectOutput out = call.getOutputStream(  );
				out.writeObject($param_DocumentDescription_1);
			} 
			catch (java.io.IOException e)  {
				throw new java.rmi.MarshalException("error marshalling
                       arguments", e);
			}
			ref.invoke(call);
			boolean $result;
			try {
				'java.io.ObjectInput in = call.getInputStream(  );
				result = in.readBoolean(  );
			}
			 catch (java.io.IOException e) {
				throw new java.rmi.UnmarshalException("error unmarshalling
                       return", e);
			} 
			finally  {
				ref.done(call);
			}
			return $result;

While this may seem a bit more complex than the code we wrote for the socket-based printer server (and the fact that we’re showing only part of the code indicates that stubs are actually quite a bit more complicated than the ClientNetworkWrapper class might have led us to expect), the fact remains: the stub implements the Printer interface, and the implementation of each method in the Printer interface simply pushes data onto a stream, and then reads data from a stream.

Tip

Strictly speaking, skeletons aren’t really necessary. They can be replaced by a more generic framework that uses Java’s reflection API to invoke methods on the server side. We’ll cover this in more detail in Chapter 8. In this book, however, our code uses skeletons.

Passing by Value Versus Passing by Reference

In the first section of this chapter, we stated that RMI automatically generates most of the marshalling and demarshalling code required to build a distributed application. It’s easy to see how RMI could automatically do this for primitive argument types. After all, an int is simply four consecutive bytes. Automatically marshalling and demarshalling objects, on the other hand, is a more difficult task. And, in order to do so correctly, RMI requires us to distinguish between two main types of objects: those that implement the Remote marker interface and those that implement the Serializable marker interface.

Tip

A marker interface doesn’t define any methods; it simply provides information (available by reflection) about other code. In this case, RMI checks to see whether a given object implements either Remote or Serializable and behaves differently in either case.

Remote objects are servers. That is, they have a fixed location and run in a specific JVM on a particular computer somewhere in the network; they are the objects that receive remote method invocations. In RMI, remote objects are passed by reference. That way, if two instances of some remote object type exist, they are logically distinct. For example, in the current application, each Printer is a remote object, and any two instances of Printer are not equal.

Serializable objects, on the other hand, are objects whose location is not important to their notion of identity. That is, while they do have a location, the location is not particularly relevant to their state. Instead, serializable objects encapsulate data and are mobile—they can be passed from one JVM to another. Hence, serializable objects are very much like the primitive datatypes, such as float and int, which are also always passed by value.

Note that if an argument is a remote object (e.g., a server), the skeleton doesn’t send a serialized copy of the server. Instead, it creates a stub that serves as a reference to that object and sends a serialized copy of the stub over the wire. What about arguments that are neither serializable nor remote? Well, if it’s a primitive datatype, it is passed by value as well. But if it’s an object and is neither serializable nor remote, an exception is thrown.



[20] “Business logic” is actually a generic term that refers to the code that justifies the application’s existence (e.g., the code that actually implements the desired functionality).

[21] As a corollary, it ought to be generated automatically. Code that bores the programmer is code that is likely to contain errors.

[22] Or a similar object-distribution framework such as CORBA.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required