Introduction
Every now and then, we need to build a custom client/server model, especially if we are students and are required to submit some network project using Java’s powerful network programming capabilities. In this article I attempt to provide a pattern/framework for developing custom client/server programming using the Java language. This article is divided into two parts: server development and client development.
Server Development
Java’s powerful network and multi-tasking (threads) capabilities make it an ideal language for developing servers that can handle multiple clients simultaneously while providing platform independence. You will find many examples of commercial and open source servers built using Java, including famous Web servers such as Tomcat, Jetty, Resin Caucho, and so forth; from chat servers to financial/management servers such as Oracle Financial, and so on.
However, because we are developers, we are often required to build our own client/server model for specific needs. Using Java’s built-in capabilities of multi-tasking and Java’s network capabilities, we can quickly develop such a server.
Let’s start building our server. Following is a UML class diagram for our server:
I am using the Observer pattern to allow low coupling between ClientThread and the Server class. The Server class’s startServer method instantiates an inner class called StartServerThread. This class extends the Thread class and its run method listens to all incoming connections. From here, a ClientThread’s object is created that implements a runnable interface. Each ClientThread will handle a separate client; its run() method creates a loop for listening to messages from the client; all logic of the server can be implemented from this point onward. Here is the source code for Server class and ClientThread class.
//Server.java //© Usman Saleem, 2002 and beyond. //usman_saleem@yahoo.com package com.usal.serverPattern; import java.net.ServerSocket; import java.net.Socket; import java.util.Observer; import java.util.Vector; import java.util.Observable; import java.io.IOException; import java.io.*; public class Server implements Observer { private Socket socket; /** This vector holds all connected clients. * May be used for broadcasting, etc. */ private Vector clients; private ServerSocket ssocket; //Server Socket private StartServerThread sst; //inner class /** * Represents each currently connected client. * @label initiates * @clientCardinality 1 * @supplierCardinality 0..* */ private ClientThread clientThread; /** Port number of Server. */ private int port; private boolean listening; //status for listening public Server() { this.clients = new Vector(); this.port = 5555; //default port this.listening = false; } public void startServer() { if (!listening) { this.sst = new StartServerThread(); this.sst.start(); this.listening = true; } } public void stopServer() { if (this.listening) { this.sst.stopServerThread(); //close all connected clients// java.util.Enumeration e = this.clients.elements(); while(e.hasMoreElements()) { ClientThread ct = (ClientThread)e.nextElement(); ct.stopClient(); } this.listening = false; } } //observer interface// public void update(Observable observable, Object object) { //notified by observables, do cleanup here// this.clients.removeElement(observable); } public int getPort() { return port; } public void setPort(int port) { this.port = port; } /** This inner class will keep listening to incoming connections, * and initiating a ClientThread object for each connection. */ private class StartServerThread extends Thread { private boolean listen; public StartServerThread() { this.listen = false; } public void run() { this.listen = true; try { /**The following constructor provides a default number of * connections -- 50, according to Java's documentation. * An overloaded constructor is available for providing a * specific number, more or less, about connections. */ Server.this.ssocket = new ServerSocket(Server.this.port); while (this.listen) { //wait for client to connect// Server.this.socket = Server.this.ssocket.accept(); System.out.println("Client connected"); try { Server.this.clientThread = new ClientThread(Server.this.socket); Thread t = new Thread(Server.this.clientThread); Server.this.clientThread.addObserver(Server.this); Server.this.clients.addElement( Server.this.clientThread ); t.start(); } catch (IOException ioe) { //some error occured in ClientThread // } } } catch (IOException ioe) { //I/O error in ServerSocket// this.stopServerThread(); } } public void stopServerThread() { try { Server.this.ssocket.close(); } catch (IOException ioe) { //unable to close ServerSocket } this.listen = false; } } }
The working of the above Server class is in fact very simple. It simply listens for new clients, gets their socket objects, and passes the information to separate threads; in our case, it is called ClientThread. Following is the source code for the ClientThread class.
//ClientThread.java //© Usman Saleem, 2002 and Beyond //usman_saleem@yahoo.com package com.usal.serverPattern; import java.net.Socket; import java.io.PrintWriter; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.IOException; import java.util.Observable; public class ClientThread extends Observable implements Runnable { /** For reading input from socket */ private BufferedReader br; /** For writing output to socket. */ private PrintWriter pw; /** Socket object representing client connection */ private Socket socket; private boolean running; public ClientThread(Socket socket) throws IOException { this.socket = socket; running = false; //get I/O from socket try { br = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); pw = new PrintWriter(socket.getOutputStream(), true); running = true; //set status } catch (IOException ioe) { throw ioe; } } /** *Stops clients connection */ public void stopClient() { try { this.socket.close(); }catch(IOException ioe){ }; } public void run() { String msg = ""; //will hold message sent from client //sent out initial welcome message etc. if required... try { pw.println("Welcome to Java based Server"); } catch(IOException ioe) { } //start listening message from client// try { while ((msg = br.readLine()) != null && running) { //provide your server's logic here// //right now it is acting as an ECHO server// pw.println(msg); //echo msg back to client// } running = false; } catch (IOException ioe) { running = false; } //it's time to close the socket try { this.socket.close(); System.out.println("Closing connection"); } catch (IOException ioe) { } //notify the observers for cleanup etc. this.setChanged(); //inherit from Observable this.notifyObservers(this); //inherit from Observable } }
The core of the ClientThread class is its run method, which invokes a loop for getting input from the client. Your logic should start from this point onward. This logic is basically your application protocol. The above mentioned framework/pattern just provides a very generic server implementation. Several enhancements are possible, such as pausing the server, restarting the server, and so on; however, implementing these enhancements is not difficult at all. Two interested methods — setChanged() and notifyObservers() — are inherited from the java.util.Observable class; they notify the interested Observer classes that a change has been made. This provides a cleaner approach to decoupling classes, especially in multi-threaded programs.
As you can see, writing a server that handles multiple clients is not difficult at all in Java. I hope the pattern above will provide you with a good start for building servers for your specific tasks. Clients in Java that access these and other servers are even easier to write; I provide a pattern for them, as follows.
Client Development
Java’s powerful networking capabilities enable us to build highly sophisticated and complex Internet-capable software. The following pattern for building the client side of a client/server model also uses the Observer pattern; however, here the intent is to send out the messages to Observers, received from the server. The source code is as follows:
//Client.java //© Usman Saleem, 2002 and Beyond. //usman_saleem@yahoo.com package com.usal.clientPattern; import java.net.Socket; import java.io.PrintWriter; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Observable; public class Client extends Observable implements Runnable { /** * Uses to connect to the server */ private Socket socket; /** * For reading input from server. */ private BufferedReader br; /** * For writing output to server. */ private PrintWriter pw; /** * Status of client. */ private boolean connected; /** * Port number of server */ private int port=5555; //default port /** * Host Name or IP address in String form */ private String hostName="localhost";//default host name public Client() { connected = false; } public void connect(String hostName, int port) throws IOException { if(!connected) { this.hostName = hostName; this.port = port; socket = new Socket(hostName,port); //get I/O from socket br = new BufferedReader(new InputStreamReader(socket.getInputStream())); pw = new PrintWriter(socket.getOutputStream(),true); connected = true; //initiate reading from server... Thread t = new Thread(this); t.start(); //will call run method of this class } } public void sendMessage(String msg) throws IOException { if(connected) { pw.println(msg); } else throw new IOException("Not connected to server"); } public void disconnect() { if(socket != null && connected) { try { socket.close(); }catch(IOException ioe) { //unable to close, nothing to do... } finally { this.connected = false; } } } public void run() { String msg = ""; //holds the msg recieved from server try { while(connected && (msg = br.readLine())!= null) { System.out.println("Server:"+msg); //notify observers// this.setChanged(); //notify+send out recieved msg to Observers this.notifyObservers(msg); } } catch(IOException ioe) { } finally { connected = false; } } public boolean isConnected() { return connected; } public int getPort(){ return port; } public void setPort(int port){ this.port = port; } public String getHostName(){ return hostName; } public void setHostName(String hostName){ this.hostName = hostName; } //testing Client// public static void main(String[] argv)throws IOException { Client c = new Client(); c.connect("localhost",5555); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String msg = ""; while(!msg.equalsIgnoreCase("quit")) { msg = br.readLine(); c.sendMessage(msg); } c.disconnect(); } }
The core method of the client pattern is connect(). It creates a connection to the server, gets I/O streams, and initiates the Client as a Thread. The run() method, in turn, starts handling incoming messages from the server and notifying the interested Observer classes about the message received. This enables us to use sendMessage() for sending messages to the server separately.
I do not provide the class that implements the Observer interface here; however, all messages received from the server will be received via the update() method’s second argument in the implemented class that has registered the Client class as observable.
The above-mentioned Client is a very generic implementation; you will need to customize it according to your needs.
I hope that this article will be of help to you in starting to develop your own custom client/server-based programs. Do let me know of any improvements in this pattern or of any problems/errors you found.
Good luck and happy programming in Java.
Usman Saleem
Muhammad Ali Jinnah University, Islamabad Campus
Islamabad, Pakistan