// This file contains material supporting section 3.8 of the textbook: // "Object Oriented Software Engineering" and is issued under the open-source // license found at www.lloseng.com package ocsf.server; import java.net.*; import java.util.*; import java.io.*; /** * The AbstractServer class maintains a thread that waits * for connection attempts from clients. When a connection attempt occurs * it creates a new ConnectionToClient instance which * runs as a thread. When a client is thus connected to the * server, the two programs can then exchange Object * instances.

* * Method handleMessageFromClient must be defined by * a concrete subclass. Several other hook methods may also be * overriden.

* * Several public service methods are provided to applications that use * this framework, and several hook methods are also available

* * Project Name: OCSF (Object Client-Server Framework)

* * @author Dr Robert Laganière * @author Dr Timothy C. Lethbridge * @author François Bélanger * @author Paul Holden * @version February 2001 (2.12) * @see ocsf.server.ConnectionToClient */ public abstract class AbstractServer implements Runnable { // INSTANCE VARIABLES ********************************************* /** * The server socket: listens for clients who want to connect. */ private ServerSocket serverSocket = null; /** * The connection listener thread. */ private Thread connectionListener; /** * The port number */ private int port; /** * The server timeout while for accepting connections. * After timing out, the server will check to see if a command to * stop the server has been issued; it not it will resume accepting * connections. * Set to half a second by default. */ private int timeout = 500; /** * The maximum queue length; i.e. the maximum number of clients that * can be waiting to connect. * Set to 10 by default. */ private int backlog = 10; /** * The thread group associated with client threads. Each member of the * thread group is a ConnectionToClient . */ private ThreadGroup clientThreadGroup; /** * Indicates if the listening thread is ready to stop. Set to * false by default. */ private boolean readyToStop = false; // CONSTRUCTOR ****************************************************** /** * Constructs a new server. * * @param port the port number on which to listen. */ public AbstractServer(int port) { this.port = port; this.clientThreadGroup = new ThreadGroup("ConnectionToClient threads") { // All uncaught exceptions in connection threads will // be sent to the clientException callback method. public void uncaughtException( Thread thread, Throwable exception) { clientException((ConnectionToClient)thread, exception); } }; } // INSTANCE METHODS ************************************************* /** * Begins the thread that waits for new clients. * If the server is already in listening mode, this * call has no effect. * * @exception IOException if an I/O error occurs * when creating the server socket. */ final public void listen() throws IOException { if (!isListening()) { if (serverSocket == null) { serverSocket = new ServerSocket(getPort(), backlog); } serverSocket.setSoTimeout(timeout); readyToStop = false; connectionListener = new Thread(this); connectionListener.start(); } } /** * Causes the server to stop accepting new connections. */ final public void stopListening() { readyToStop = true; } /** * Closes the server socket and the connections with all clients. * Any exception thrown while closing a client is ignored. * If one wishes to catch these exceptions, then clients * should be individually closed before calling this method. * The method also stops listening if this thread is running. * If the server is already closed, this * call has no effect. * * @exception IOException if an I/O error occurs while * closing the server socket. */ final synchronized public void close() throws IOException { if (serverSocket == null) return; stopListening(); try { serverSocket.close(); } finally { // Close the client sockets of the already connected clients Thread[] clientThreadList = getClientConnections(); for (int i=0; iThread containing * ConnectionToClient instances. */ synchronized final public Thread[] getClientConnections() { Thread[] clientThreadList = new Thread[clientThreadGroup.activeCount()]; clientThreadGroup.enumerate(clientThreadList); return clientThreadList; } /** * Counts the number of clients currently connected. * * @return the number of clients currently connected. */ final public int getNumberOfClients() { return clientThreadGroup.activeCount(); } /** * Returns the port number. * * @return the port number. */ final public int getPort() { return port; } /** * Sets the port number for the next connection. * The server must be closed and restarted for the port * change to be in effect. * * @param port the port number. */ final public void setPort(int port) { this.port = port; } /** * Sets the timeout time when accepting connections. * The default is half a second. This means that stopping the * server may take up to timeout duration to actually stop. * The server must be stopped and restarted for the timeout * change to be effective. * * @param timeout the timeout time in ms. */ final public void setTimeout(int timeout) { this.timeout = timeout; } /** * Sets the maximum number of waiting connections accepted by the * operating system. The default is 20. * The server must be closed and restarted for the backlog * change to be in effect. * * @param backlog the maximum number of connections. */ final public void setBacklog(int backlog) { this.backlog = backlog; } // RUN METHOD ------------------------------------------------------- /** * Runs the listening thread that allows clients to connect. * Not to be called. */ final public void run() { // call the hook method to notify that the server is starting serverStarted(); try { // Repeatedly waits for a new client connection, accepts it, and // starts a new thread to handle data exchange. while(!readyToStop) { try { // Wait here for new connection attempts, or a timeout Socket clientSocket = serverSocket.accept(); // When a client is accepted, create a thread to handle // the data exchange, then add it to thread group synchronized(this) { ConnectionToClient c = new ConnectionToClient( this.clientThreadGroup, clientSocket, this); } } catch (InterruptedIOException exception) { // This will be thrown when a timeout occurs. // The server will continue to listen if not ready to stop. } } // call the hook method to notify that the server has stopped serverStopped(); } catch (IOException exception) { if (!readyToStop) { // Closing the socket must have thrown a SocketException listeningException(exception); } else { serverStopped(); } } finally { readyToStop = true; connectionListener = null; } } // METHODS DESIGNED TO BE OVERRIDDEN BY CONCRETE SUBCLASSES --------- /** * Hook method called each time a new client connection is * accepted. The default implementation does nothing. * @param client the connection connected to the client. */ protected void clientConnected(ConnectionToClient client) {} /** * Hook method called each time a client disconnects. * The default implementation does nothing. The method * may be overridden by subclasses but should remains synchronized. * * @param client the connection with the client. */ synchronized protected void clientDisconnected( ConnectionToClient client) {} /** * Hook method called each time an exception is thrown in a * ConnectionToClient thread. * The method may be overridden by subclasses but should remains * synchronized. * * @param client the client that raised the exception. * @param Throwable the exception thrown. */ synchronized protected void clientException( ConnectionToClient client, Throwable exception) {} /** * Hook method called when the server stops accepting * connections because an exception has been raised. * The default implementation does nothing. * This method may be overriden by subclasses. * * @param exception the exception raised. */ protected void listeningException(Throwable exception) {} /** * Hook method called when the server starts listening for * connections. The default implementation does nothing. * The method may be overridden by subclasses. */ protected void serverStarted() {} /** * Hook method called when the server stops accepting * connections. The default implementation * does nothing. This method may be overriden by subclasses. */ protected void serverStopped() {} /** * Hook method called when the server is clased. * The default implementation does nothing. This method may be * overriden by subclasses. When the server is closed while still * listening, serverStopped() will also be called. */ protected void serverClosed() {} /** * Handles a command sent from one client to the server. * This MUST be implemented by subclasses, who should respond to * messages. * This method is called by a synchronized method so it is also * implcitly synchronized. * * @param msg the message sent. * @param client the connection connected to the client that * sent the message. */ protected abstract void handleMessageFromClient( Object msg, ConnectionToClient client); // METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ---------------- /** * Receives a command sent from the client to the server. * Called by the run method of ConnectionToClient * instances that are watching for messages coming from the server * This method is synchronized to ensure that whatever effects it has * do not conflict with work being done by other threads. The method * simply calls the handleMessageFromClient slot method. * * @param msg the message sent. * @param client the connection connected to the client that * sent the message. */ final synchronized void receiveMessageFromClient( Object msg, ConnectionToClient client) { this.handleMessageFromClient(msg, client); } } // End of AbstractServer Class