Let's look at a simple example of reducing message calls. For later infrastructure comparison, we will use three different distributed-application infrastructures: CORBA, RMI, and a proprietary distribution mechanism using plain sockets and serialization (see the sidebar "Proprietary Communications Infrastructures"). In the example, I present a simple server object that supports only three instance variables and three methods to set those instance variables.
Proprietary Communications InfrastructuresYou can easily create your own communication mechanisms by connecting two processes using standard sockets. Creating two-way connections with Sockets and ServerSockets is very straightforward. For basic communication, you decide on your own communication protocol, possibly using serialization to handle passing objects across the communication channel. However, using proprietary communications is not a wise thing to do and can be a severe maintenance overhead unless your communication and distribution requirements are simple. I occasionally use proprietary communications for testing purposes and for comparison against other communications infrastructures, as I have done in this chapter. In this chapter, I use a simple, generic communications infrastructure that automatically handles remotely invoking methods: basically, a stripped-down version of RMI. I generate a server skeleton and client proxy using reflection to identify all the public methods of the distributable class. Then I copy the RMI communication protocol (which consists of passing method identifiers and parameters from proxies to server objects identified by their own identifiers). The only other item required is a lookup mechanism, which again is quite simple to add as a remotely accessible table. The whole infrastructure is in one fairly simple class, tuning.distrib.custom.Generate, available from this book's catalog page, http://www.oreilly.com/catalog/javapt2/. |
The CORBA IDL definition is quite simple:
module tuning { module distrib { module corba { interface ServerObject { void setBoolean(in boolean flag); void setNumber(in long i); void setString(in string obj); }; }; }; };
The server class implementation for this IDL definition is:
package tuning.distrib.corba; public class ServerObjectImpl extends _ServerObjectImplBase { boolean bool; int num; String string; public void setBoolean(boolean b) {bool = b;} public void setNumber(int i) {num = i;} public void setString(String s) {string = s;} }
All the support classes are generated using the idlj utility. For JDK 1.3, this generates interfaces ServerObject and ServerObjectOperations; skeleton classes _ServerObjectImplBase and _ServerObjectStub; and server object assistant classes ServerObjectHelper and ServerObjectHolder. In addition, I define a main( ) method that installs an instantiation of the server object in the name service and then remains alive to serve client requests. All classes are defined in the tuning.distrib.corba package.
My client simply resolves the server object from the name service, obtaining a proxy for the server, and then calls the three methods and sets the three instance variables. For the test, I repeat the method calls a number of times to obtain average measurements.
The optimization to reduce the number of method calls is extremely simple. Just add one method, which sets all three instance variables in one call in the following IDL definition:
void setAll(in boolean flag, in long i, in string obj);
The corresponding method is added to the server class:
public void setAll(boolean b, int i, String s) { bool = b; num = i; string = s; }
The result is that the single method call requires one-third of the network transfers and takes one-third of the time, compared to the triple method calls (see the later section Section 12.3).
The RMI implementation is essentially the same. The server-object interface (with optimized method) is defined as:
package tuning.distrib.rmi; import java.rmi.Remote; import java.rmi.RemoteException; public interface ServerObject extends Remote { public abstract void setBoolean(boolean flag) throws RemoteException; public abstract void setNumber(int i) throws RemoteException; public abstract void setString(String obj) throws RemoteException; public abstract void setAll(boolean flag, int i, String obj) throws RemoteException; }
The RMI server-object implementation is the same as the CORBA version, except that it extends UnicastRemoteObject , implements ServerObject, and defines the methods as throwing RemoteException. All the support classes are generated using the rmic utility. For JDK 1.3, this generates skeleton classes ServerObjectImpl_Skel and ServerObjectImpl_Stub. In addition, I define a main( ) method that sets a security manager and installs an instantiation of the server object in the name service. All classes are defined in the tuning.distrib.rmi package.
Once again, the result is that the single method call requires one-third of the network transfers and takes one-third of the time, compared to the triple method calls (see Section 12.3).
A distributed system can be defined with sockets and serialization. I have implemented a simple generator that provides all the basic stub and skeleton behavior for a distributed application (tuning.distrib.custom.Generate class; see the sidebar "Proprietary Communications Infrastructures"). The server object is defined as before, with the interface:
package tuning.distrib.custom; public interface ServerObject { public abstract void setBoolean(boolean flag); public abstract void setNumber(int i); public abstract void setString(String obj); public abstract void setAll(boolean flag, int i, String obj); }
This server-object implementation is the same as the CORBA version, though without the need to extend any class: it implements only ServerObject.
Yet again, the result is that the single method call requires one-third of the network transfers and takes one-third of the time, compared to the triple method calls (see the next section).