A Simple Client-Server Framework

The two important protocols in the transport layer are the User Datagram Protocol (UDP) and the Transmission Control Protocol (TCP). UDP is the connectionless protocol while TCP is the connection-oriented protocol.

In a connection-oriented protocol processes exchange bytes using stream-mode sockets. A stream-mode socket, s, consists of two streams, an input stream (s.in) and an output stream (s.out). Assume processes A and B want to have a conversation, then A and B will each need a stream-mode socket, A.s and B.s. Furthermore, A.s.out must be connected to B.s.in and B.s.out must be connected to A.s.in:

An Analogy

Think of a socket as a telephone. For A to call B, both need phones (sockets). A needs to know the number (address) of B's phone, the transmitter (output stream) of A's phone must be connected to the receiver (input stream) of B's phone, and the receiver of A's phone (input stream) must be connected to the transmitter of B's phone (output stream).

Relevant Java classes

class java.net.Socket; // stream-mode socket
class java.net.ServerSocket; // connection request socket

A Simple Client-Server Framework

Correspondent.java

Here are the import statements most of our files will need:

import java.util.*;
import java.io.*;
import java.net.*;
import jutil.*;

A correspondent has a socket. The input and output streams of the socket are filtered by readers and writers, respectively:

class Correspondent {
   protected Socket sock;
   protected BufferedReader sockIn;
   protected PrintWriter sockOut;

   public Correspondent() { } // init fields later
   public Correspondent(Socket s) {
      sock = s;
      initStreams();
   }

Let's convert the input and output streams of our sockets into something a bit more user friendly:

   protected void initStreams() {
      try {
         sockIn = new BufferedReader(
            new InputStreamReader(
               sock.getInputStream()));
         sockOut = new PrintWriter(
            sock.getOutputStream(), true);
      } catch(IOException e) {
         System.err.println(e.getMessage());
      }
   }

Subclasses can close streams without worrying about exceptions:

   protected void close() {
      try {
         sock.close();
      } catch(IOException e) {
         System.err.println(e.getMessage());
      }
   }

This method is used to request a connection to a new server:

   protected void requestConnection(String host, int port) {
      try {
         sock = new Socket(host, port);
         initStreams();
      } catch(UnknownHostException uhe) {
         System.err.println("unknown host " + uhe);
         System.exit(1);
      } catch(IOException ioe) {
         System.err.println("failed to create streams " + ioe);
         System.exit(1);
      }
   }

Basic send and receive:

   public void send(String msg) {
      sockOut.println(msg);
   }

   public String receive() {
      String msg = null;
      try {
         msg = sockIn.readLine();
      } catch(Exception e) {
         System.err.println(e.getMessage());
         System.exit(1);
      }
      return msg;
   }
} // Correspondent

SimpleClient.java

A client perpetually:

1. reads user's command
2. sends the command to the server
3. receives the server's response
4. displays response

Of course the client is a correspondent:

public class SimpleClient extends Correspondent {

   protected BufferedReader stdin;
   protected PrintWriter stdout;
   protected PrintWriter stderr;

The constructor sets up the standard input, output, and error streams. It also requests a connection to the server:

   public SimpleClient(String host, int port) {
      requestConnection(host, port);
      stdout = new PrintWriter(
         new BufferedWriter(
               new OutputStreamWriter(System.out)), true);
      stderr = new PrintWriter(
         new BufferedWriter(
            new OutputStreamWriter(System.out)), true);
      stdin = new BufferedReader(
         new InputStreamReader(System.in));
   }

Our basic read-send-receive-display control loop:

   public void controlLoop() {
      while(true) {
         try {
            stdout.print("-> ");
            stdout.flush();
            String msg = stdin.readLine();
            if (msg == null) continue;
            if (msg.equals("quit")) break;
            stdout.println("sending: " + msg);
            send(msg);
            msg = receive();

            stdout.println("received: " + msg);
         } catch(IOException e) {
            stderr.println(e.getMessage());
            break;
         }
      }
      send("quit");
      stdout.println("bye");
   }

Users can specify the location of the server on the command line:

   public static void main(String[] args) {
      int port = 5555;
      String host = "localhost";
      if (1 <= args.length) {
         port = Integer.parseInt(args[0]);
      }
      if (2 <= args.length) {
         host = args[1];
      }
      SimpleClient client = new SimpleClient(host, port);
      client.controlLoop();
   }
}

Server.java

The server perpetually listens for incoming client requests using a server socket:

public class Server {
   protected ServerSocket mySocket;
   protected int myPort;
   protected Class handlerType;
   public Server() { this(5555); }
   public Server(int port) { this(port, "RequestHandler"); }
   public Server(int port, String htp) {
      try {
         myPort = port;
         mySocket = new ServerSocket(myPort);
         handlerType = Class.forName(htp);
      } catch(Exception e) {
         System.err.println(e.getMessage());
         System.exit(1);
      } // catch
   }

The listener perpetually calls:

socket = mySocket.accept();

This causes the server to block until a request arrives from the client. This happens when the client calls:

Correspondent.requestConnection()

When the accept() method returns, it returns a server-side socket connected to the client-side socket. Next, the server creates a slave thread to correspond with the client. This is necessary to keep the response time of the server reasonable. The slave thread is an instance of the RequestHandler class:

 
   public void listen() {
      System.out.println("server address: " +
         mySocket.getInetAddress());
      try {
         while(true) {
            System.out.println("Server listening at port " + myPort);
            Socket socket = mySocket.accept(); // blocks
            RequestHandler handler = makeHandler(socket);
            if (handler == null) continue;
            Thread slave = new Thread(handler);
            slave.start();
         } // while
      } catch(IOException ioe) {
         System.err.println("Failed to accept socket, " + ioe);
         System.exit(1);
      } // catch
   }

The field:

protected Class handlerType;

indicates the subclass of RequestHandler that slaves actually instantiate. This field was initialized by the constructor using the name of the class. The request handler factory uses reflection to create a new instance of this class, then initializes its socket to be the server-side socket, which is already connected to the client-side socket:

   public RequestHandler makeHandler(Socket s) {
      RequestHandler handler = null;
      try {
         handler = (RequestHandler) handlerType.newInstance();
         handler.setSocket(s);
      } catch(Exception e) {
         System.err.println(e.getMessage());
      }
      return handler;
   }

Users can specify the port and request handler subclass at the command line:

   public static void main(String[] args) {
      int port = 5555;
      String service = "RequestHandler";
      if (1 <= args.length) {
         port = Integer.parseInt(args[0]);
      }
      if (2 <= args.length) {
         service = args[1];
      }
      Server server = new Server(port, service);
      server.listen();
   }
}

RequestHandler.java

A request handler is a correspondent and runs in its own thread of control.  It perpetually:

1. receives a command from the client
2. creates a response to the command
3. sends the response back to the client

Notice that the current implementation of response simply echoes the command back to the client. Naturally, this can be overridden in a subclass:

class RequestHandler extends Correspondent implements Runnable {
   public RequestHandler(Socket s) { super(s); }
   // override in a subclass:
   protected String response(String msg) {
      return "echo: " + msg;
   }
   public void run() {
      while(true) {
         String msg = receive();
         System.out.println("received: " + msg);
         //if (msg == null) continue;
         if (msg.equals("quit")) break;
         msg = response(msg);
         System.out.println("sending: " + msg);
         send(msg);
      }
      close();
      System.out.println("request handler shutting down");
   }
}

Example: CommandProcessor.java

As an example, we extend the RequestHandler and override the response method so it executes commands of the form:

add NUMBER ... NUMBER
mul NUMBER ... NUMBER
sub NUMBER NUMBER
div NUMBER NUMBER

The response method splits the users command into an array of tokens (i.e., strings that don't contain white space characters,) starting from position 1, parses tokens into numbers, then performs the arithmetic combination required by the first token:

class CommandProcessor extends RequestHandler {
   protected String response(String msg) {
      String[] tokens = msg.split("\\s");
      double[] args = new double[tokens.length - 1];
      for(int i = 1; i < tokens.length; i++) {
         try {
            args[i - 1] = Double.parseDouble(tokens[i]);
         } catch(NumberFormatException nfe) {
            return "arguments must be numbers";
         }
      }
      if (tokens[0].equals("add")) return "" + add(args);
      if (tokens[0].equals("mul")) return "" + mul(args);
      return "unrecognized command: " + tokens[0];
   }

Here's a sample helper method for adding numbers. The helper methods for the other arithmetic operators are similar:

   private double add(double[] args) {
      double result = 0;
      for (int i = 0; i < args.length; i++) {
         result += args[i];
      }
      return result;
   }
   // etc.
} // CommandProcessor

Running the application

Note all classes except for the command processor are in the jutil package.

Client Side

Server-Side