The Java language is an excellent choice for developing multitier client/server applications. It supports several approaches to managing client-to-server communication, such as queued messaging, RPC, RMI, CORBA, and EJB technology (see "Alternative client/server implementation techniques" for more information). There are times, however, when these techniques are overkill, and a simpler message-passing approach is more appropriate. The Java language, with its easy access to TCP/IP-based sockets and its ability to stream objects over them, makes it easy to exchange command messages between clients and their associated servers
In a remote-objects approach, you must invoke a method of a server object. In a message-passing approach, message (or command) objects are exchanged between the client and the server. The server then interprets the messages and, if required, builds reply objects to send back to the client. (See Figure 1.) In this context the server does not appear to the client as an object, but instead as a command interpreter of indeterminate form. The parameters of the command, if any, are the instance variables of the message object.
Figure 1. Client/server command relationship

In this article, I will show you how to implement the message-passing approach. I'll explain how to construct command objects and describe how they are exchanged between the client and the server and subsequently processed by the server. Unlike the message-queueing approach, this approach is conversational or bi-directional (versus one-way). Conversation is synchronous (the reply comes immediately), while the alternative is asynchronous (the reply comes in the future, if at all).
What is the nature of the command objects exchanged between the clients and servers? In general they consist of a message identifier (or command code) and some parameters.
The most important characteristic is the command identifier. Basically, you can implement command identification in two ways: 1) use a command code; or 2) use the type (or class) of the command object. In traditional message-passing systems, you use a code, often an integer value or a short string. With the Java language's ability to test the type of an object (the instanceof operator), the type can be used as well. I will show you how to use either approach. To facilitate this, you can expect each command object to contain at least the following instance variables:
abstract public class ServerCommand
implements java.io.Serializable, java.lang.Cloneable {
// messageId is redundant with type but faster to test than instanceof
protected transient int uniqueId; // unique number
public int messageId; // identifies message
public int version = 0; // for extension
public int sequenceIn = 0; // unique in a session
public int sequenceOut = -1; // used in replies
}
|
Use the uniqueId value, assigned by the constructor, to distinguish any command from another in a single Java virtual machine (similar to the object ID of the command but human readable). You can use the values to make the objects unique in any traces, but it's not needed to support the processing of messages.
The messageId is the command code. Each code has a corresponding message type.
For the examples in this article the following codes, and thus message types, are defined:
public interface Messages {
int UNASSIGNED = 0;
int MESSAGE_1 = (1 << 16);
int MESSAGE_2 = (2 << 16);
int MESSAGE_N = (3 << 16);
int MESSAGE_N_PART1 = (3 << 16) + 1;
int MESSAGE_N_PART2 = (3 << 16) + 2;
int STOP = Integer.MAX_VALUE;
}
|
Use the version to facilitate future expansion. You can add more fields in future command versions. To coordinate commands with replies, use the sequenceIn and sequenceOut values. Any reply to a command has its sequenceOut set to the sequenceIn value of the command. A reply may be (and often is) of the same type as the command. Commands have a sequenceOut value of -1.
Beyond this common content, each message may have content required to carry out its function.
You define this content by subclassing ServerCommand and adding additional instance variables. For example, type Message1 adds an int value:
final public class Message1 extends ServerCommand {
public int index; // sequence value
}
|
Teaching your server processing tricks
After the server receives a command, how does it know how to process it? Command processing is most directly done by creating a method in the server that processes each command type. Traditionally, the server would use a switch statement indexed on the message identifier to select the desired method. This technique requires you to change the server constantly as you define new message types. A better approach would to separate the top-level command reception and buffering support from the particular command processing.
You can achieve this separation by using a registered command listener with a callback approach. This technique is similar to the Java 1.1 AWT event model. Each command processor is a method of a class that implements a listener interface defined for that command type. For example, for the listener interface:
public interface MessageNListener extends CommandListener {
public void messageN(MessageN c);
public void messageNPart1(MessageNPart1 c);
public void messageNPart2(MessageNPart2 c);
}
|
a possible command processor is:
class MessageHandler2 implements MessageNListener {
public void messageN(MessageN c)
{ ... }
public void messageNPart1(MessageNPart1 c)
{ ... }
public void messageNPart2(MessageNPart2 c)
{ ... }
}
|
When the server receives a command, say a MessageN, it determines that its matching listener is MessageNListener and it finds all instances of registered listeners for it (for example, MessageHandler2). Then it invokes the appropriate callback (for example, messageN(...)) in that class to process the command. If the server finds multiple listener classes, the command is multicast to each of them.
This registration is all done dynamically. In fact, it is possible to install new types of command listeners and handlers while the server is running (by using Class.forName()).
This would be quite difficult if the server employed a switch-based approach to command dispatching.
The server itself consists of two Java threads: one to accept commands from a client and another to send responses back to the client. The server code of these examples serves only a single client but you can easily extend it to support an indefinite number of clients. The client is represented below by an input stream of predefined commands and an output stream to accept replies.
The input thread repetitively calls processInboundMessage. The output thread repetitively calls processOutboundMessage. These loops end when you shut down the server.
public void processInboundMessage() throws Exception {
ServerCommand c = (ServerCommand)_inputStream.readObject();
Class clc = c.getListenerClass(); // who listens for it
Enumeration e = _commandQueues.elements(); // queues for this server
while(e.hasMoreElements()) { // for each queue in turn
ServerCommandQueue cq = (ServerCommandQueue)e.nextElement();
if(cq.listensTo(clc)) // this queue wants the command
cq.put(_copyMode ? (ServerCommand)c.clone() : c);
}
}
public void processOutboundMessage() throws Exception {
ServerCommandQueue cq =
(ServerCommandQueue)_commandQueues.elementAt(_outboundIndex);
ServerCommand c = cq.get(); // get next command
_outputStream.writeObject(c); _outputStream.flush(); // send the image
}
|
The server processes commands by placing them into ServerCommandQueues. The code in these examples supports multiple input queues and a single output queue. You can implement additional output queues, and you can clone commands by placing them into the inbound queues, a useful approach when you're multicasting and when you're changing the command while processing it. For input commands the server searches all known command queues looking for the ones that accept commands for that listener type:
public synchronized boolean listensTo(Class c) {
boolean result = false; // assume not found
Enumeration e = _listeners.elements(); // listeners for this queue
while(!result && e.hasMoreElements()) { // try c against all listeners
CommandListener cl = (CommandListener)e.nextElement();
if(cl != null && c.isInstance(cl)) // listener is correct for command
result = true;
}
return result;
}
|
To determine which command queue to place the command into, each ServerCommand class is responsible for returning the listener class that responds to it. This class is used to associate the message with the queue (as shown above). For example, the MessageN class provides this information:
public Class getListenerClass() throws ClassNotFoundException {
return Class.forName("commanddispatcher.MessageNListener");
}
|
It is the job of the server command queue to deliver the command to the appropriate server callback. Each queue has a thread that removes entries placed on it. The input queues dispatch the commands:
protected void dispatchListeners(ServerCommand c) throws Exception {
if(c != null && hasListeners()) { // some command to process
Class clc = c.getListenerClass(); // whom listens
if(clc != null) { // some defined listener
Enumeration e = _listeners.elements(); // registered listeners
while(e.hasMoreElements()) { // try each listener (ie multicast)
CommandListener cl = (CommandListener)e.nextElement();
if(cl != null && clc.isInstance(cl)) { // listener is correct
ServerCommand tc = _copyMode ? (ServerCommand)c.clone() : c;
tc.invoke(cl); // go do call back
}
}
}
}
}
|
You invoke the callback by the invoke method of the received command class.
The invoke method then delegates to the appropriate implementation method. For example, class MessageN has the method:
public void invoke(CommandListener cl) {
((MessageNListener)cl).messageN(this);
}
|
Because the command itself provides the code to invoke the callback, the base server and command queue code is ignorant of the command types. This is how you can add more commands to the system dynamically.
Getting your priorities straight
Multiple command queues allow you to process different command types at different priorities, allowing you to implement priority categories such as idle, normal, and critical. The different threads of the server and the command queues can run at different priorities, which gives you a very fine control on the relative rates of receiving, processing, and responding to commands.
Consider the example of command handlers (inner classes of ServerDemo) shown in Listing 1.
The delay method just spins, eating valuable CPU time.
This represents the time used by a true command processor in a real server.
The shutdown, message1, and messageNPart2 callbacks send responses.
The involved interactions described above are condensed in Figure 2. The sequence is:
- A command comes in from the client and is handled by the server's input thread.
- The command is placed in a inbound command queue.
- The command queue's processing thread invokes the command's registered callback.
- The callback generates a response, places it in the outbound command queue, and returns.
- The server's output thread gets the response from the outbound queue.
- The server responds to the client.
Figure 2. Command processing flow

Each command queue has its own direction, capacity, and priority. The server has flow-tracing code to indicate where a message (or action) comes from (such as the "main" lines in the trace shown in Listing 3). This trace is driven by the example command stream shown in this code snippet, which, when executed with the server configuration shown in Listing 2, outputs the trace shown in Listing 3.
In the tracing output:
- The "inbound xxxx" lines indicate the client has generated a command.
- The "read inbound command" lines indicate the server is waiting for the next command to arrive.
- The "process command: xxxx" lines indicate that the server input thread has received the command and will deliver it to a command queue.
- The "xxxx event" lines indicate the beginning of the processing of a command (that is, the callback is running).
- The corresponding "xxxx done" lines indicate the server has processed the command.
- The "write command: xxxx" lines indicate the server is sending a response.
- The "get output command" lines indicate the server is waiting for the next reply to send.
- The "outbound xxxx" lines indicate the client has received a response.
I have defined an example client (in the main method of class ServerDemo)
with the following instance variables and code sequence:
ServerCommand[] commands = { // test commands
new Message1(1), new Message2(),
new MessageN(), new MessageNPart1(),
new MessageNPart2(), new MessageN(),
new Message2(), new Message1(8),
new Message2(), new Message1(10)
};
for(int j = 0; j < 3; j++) { // do several times
try { Thread.sleep(j * 10); } catch(InterruptedException ie) {} // delay
for(int i = 0; i < commands.length; i++) {
send(commands[i]); // fake a client generated command
try { Thread.sleep((commands.length - i) * (j + 1)); } // delay
catch(InterruptedException ie) {}
}
send(new Stop()); // end it all
}
|
Note that all the client-generated commands are not processed. When the system receives the Stop command, it shuts down and thus ignores subsequent commands in progress or those not yet received. Also note that the servers don't always process commands in the order they are received and that multiple commands are executed concurrently. This is the result of having multiple command queues with different priorities. Servers that receive commands in a single command queue process them in arrival order. The responses have the sequenceOut value set to the sequenceIn value of the corresponding command.
In this article, I have described an approach to developing a command-based server that allows for the separation of the command-processing logic from the base server support. This separation allows for the dynamic addition of new command types and removes the concerns of command buffering and priority handling from the command processors themselves.
The server can consist of a single input and output command queue. This form processes all received commands sequentially. Alternatively, the server can have multiple command queues, each possibly running at different priorities, which allows for priority-based parallel command processing.
You can adjust the buffering capacity of the system. With single-entry queues, the transmission and processing of commands is lock step. With larger queues, the transmission and processing can overlap, helping you generally achieve greater command throughput. Larger queues also help when messages occasionally arrive faster than the server can process them.
You can have multiple client capability by adding multiple Server objects, each with their own input and output thread pairs, one per client. You can start these additional servers with an "accepting" thread (versus the static code in the example) that waits for connections to be established.
Up to this point, I have discussed the benefits of this approach. However, there are a few disadvantages:
- The use of multiple threads and the command queue mechanism adds processing time overhead,
especially if a lock-step (single queue) configuration is used. The overhead can be eliminated if you extract the
processInboundMessageanddispatchListenerscode from these methods and call them directly from the server code. - Using message codes requires that you change the
Messagesinterface when you add new message types, which can reduce flexibility. Thus, I recommend the use of type-based message selection (see the message codes discussion above). - Extending messages (
ServerCommandsubclasses) with more instance variables can cause difficulties. If any extension occurs, you'll need to upgrade the message's .class file for both the client and server and then restart them. To avoid this, I recommend that you create a single message class, such as shown below:import java.util.*; abstract public class GeneralMessage extends ServerCommand { protected Vector indexedItems; // holds value indexed by an int protected Hashtable namedItems; // holds named field value }This class would be extended by a class of the following form:
public class Message1 extends GeneralMessage { static final long serialVersionUID = ???L; // from serialver command protected static final String INDEX_KEY = "index"; public Message1(int index) { namedItems = new HashTable(); setIndex(index); } public int getIndex() { return ((Integer)namedItems.get(INDEX_KEY)).intValue(); } public void setIndex(int index) { namedItems.put(INDEX_KEY, new Integer(index)); } }The
serialVersionUIDfield is set to ensure that any future method additions do not invalidate the serialized form of the message. Use the serialver command to create this statement. Add a similarserialVersionUIDline to each newly created subclass ofGeneralMessage.Indexed types use the
indexedItemsfield while non-indexed items use thenamedItemsfield. You don't need to add new fields as you're adding more values, and thus this solution does not break version compatibility. If necessary, you can extend the message class with more values such as:public class Message1x extends Message1 { protected static final String NAME_KEY = "name"; public Message1x(int index, String name) { super(index); setName(name); } public String getNamex() { return (String)namedItems.get(NAME_KEY); } public void setName(String name) { namedItems.put(NAME_KEY, name); } }
- For general information on Java technology, including network (socket) usage, RMI, EJB technology, and message queueing, go to java.sun.com/products and select the API you want to review.
- For information on CORBA, visit the OMG Web site.
- For a basic introduction to sockets (using C), see Jim Frost's BSD Sockets: A Quick and Dirty Primer.
- If you're interested in creating a way for remote users to access your application, you might want to read this two-part series on XML-RPC by Joe Johnston from our Web services zone:
Dr. Feigenbaum has over 20 years of experience developing operating systems and complex applications. He has served as a consultant specializing in e-business enabling technologies such as Java, HTML, servlets, JSPs and EJBs. He is an expert in object-oriented analysis, design and development, and is well-versed on the latest trends in client/server and n-tier architectures. He has also published several books and articles and represented IBM at technical conferences (such as Sun's JavaOne) and trade shows (such as IBM's WebSphere).




