The three main architectures used in distributed processing are Pipeline, Client-Server, and Peer-to-Peer.
In these notes we will focus on Client-Server architectures. Pipeline architectures are discussed here. Peer-to-peer architectures are discussed in Wikipedia. They are also the basis of agent-based systems.
A server is like a call center and clients are like customers. A customer (client) needing service picks up his telephone (socket) and dials the call center's switchboard (server socket). The telephone number is the IP address plus the port number. The switch board connects the customer's telephone to the telephone (socket) of the next available agent (request handler). In other words, the receiver of the client's telephone is connected to the transmitter of the agent's telephone and vice-versa. While the customer and agent talk, the switchboard can resume receiving customer calls. Usually the conversation between the customer and the agent follows a strict pattern called a protocol.
The server side of our framework uses the Master-Slave pattern enhanced by the Factory pattern. The master is a request listener that listens for incoming client ("customer") requests using a server socket (the "switchboard").
When a client request is received, the request listener either creates a request handler thread ("agent") or pulls one from a pool of available request handlers. The request listener creates a socket ("telephone") connected to the client's socket. Gives it to the request handler, then resumes listening for incoming client requests.
There are two types of request handlers: stateful and stateless. A stateless request handler executes the client's command, returns the result through the socket, then "hangs up". If the client has follow up questions, he'll have to call back. Of course he may not get the same agent, and the new agent will have to start all over again.
By contrast, a stateful request handler can engage the client in longer conversations which only end when the client quits "hangs up" or times out. The stateful request handler can keep track of the history of the conversation in a session object.
The client side of the framework looks like the Model-View-Controller pattern. The ClientUI plays the View role and the CommandProcessor (which may run in a separate thread from the ClientUI) plays the Controller role. However the Model role is played by the server-side RequestHandler.
A. Implement the Client-Server framework described above.
B. To prove your framework works, customize it into a table lookup server (TLK Server). In a TLK server stateless request handlers have access to a static table:
class TLKRequestHandler extends StatelessRequestHandler {
private Map<String, String>
theTable = new Hashtable<String, String>();
public String execute(String cmmd) {
//...
}
}
The execute method handles three types of commands:
get KEY // return
theTable.get(KEY);
put KEY VALUE // theTable.put(KEY, VALUE);
rem KEY // theTable.remove(KEY);
In the last two cases either "error" or "done" is returned to the client.
The ClientUI can be a simple console user interface that perpetually:
1. print prompt
2. read command
3. ask command processor to execute command
4. print result
5. if (!command.equals("quit")) goto 1.
The CommandProcessor simply sends commands, including the "quit" command to the request handler.
Test your implementation by launching the RequestListener in one console window and launching two ClientUI's in two other console windows. See if you can put a pair of strings into the table using one client and get it using the other.
1. Choose a port number for your server socket that's between 1000 and 32000. Your IPHost will be localhost.
2. CommandProcessor and RequestHandler both have a socket and both implement the same send and receive methods. Factor this common code into a common superclass:
class Correspondent {
protected Socket socket;
public Correspondent(Socket s) { socket
= s; }
public void send(String) { ... }
public String receive() { ... }
}
class CommandProcessor extends Correspondent { ... }
class RequestHandler extends Correspondent { ... }
Two classical examples of table lookup servers (see project 1) are white page servers and yellow page servers. A white page server maintains a table of server names and their associated addresses (IP address plus port number). A yellow page server maintains a table that associates service descriptions with a list of addresses of servers that implement the description.
The TLK Server described in the project could function as an adequate white page server (WPServer). It does have some limitations, however. For example, what happens if two clients try to access the table at the same time? What if we want to block certain services? What if we want to keep track of the number of times a particular service has been requested? What if we want to cache frequently requested services? What if we want to restrict access to paying customers?
One option is to rewrite the server each time we want to add a new feature. A better option is to design the server using the Proxy pattern so that it can be easily extended without modification.
The proxies shown above can be divided into client-side proxies (WPFirewallProxy, WPCacheProxy, WPStub) and server-side proxies (all the rest of them).
The most interesting example is WPStub, which is an example of a remote proxy. Unlike the other proxies, the stub delegates client messages across a process or machine boundary using a socket.
Like the Gateway pattern, a stub hides the complexities of socket communication from the rest of the application. In fact, this is the basic idea behind remote method invocation (rmi).
Here is how we can combine WPStub with our client-server framework:
1. Refactor your TLK Server from project 1 to use the Proxy patter as shown above.
2. Prove your new design works by implementing TLKStub, TLKCacheProxy, and TLKSynchProxy.
The cache proxy maintains its own lookup table and searches it first each time the client issues a get command. (Of course the cache can be dangerously out of date if we still allow put and remove commands. But at least this will give you a way to test your proxy.)
The synchronization proxy simply provides synchronized versions of the get, put, and rem methods.