diff --git a/code/simplechat1/ClientConsole.java b/code/simplechat1/ClientConsole.java index c9bb4e9..e3848e2 100644 --- a/code/simplechat1/ClientConsole.java +++ b/code/simplechat1/ClientConsole.java @@ -24,6 +24,7 @@ public class ClientConsole implements ChatIF * The default port to connect on. */ final public static int DEFAULT_PORT = 5555; + private static String loginID; //Instance variables ********************************************** @@ -41,13 +42,13 @@ public class ClientConsole implements ChatIF * @param host The host to connect to. * @param port The port to connect on. */ - public ClientConsole(String host, int port) + public ClientConsole(String host, int port, String loginID) { try { - client= new ChatClient(host, port, this); + client= new ChatClient(host, port, loginID, this); } - catch(IOException exception) + catch(IOException exception) { System.out.println("Error: Can't setup connection!" + " Terminating client."); @@ -91,7 +92,7 @@ public void accept() */ public void display(String message) { - System.out.println("> " + message); + System.out.println(message); } @@ -102,21 +103,46 @@ public void display(String message) * * @param args[0] The host to connect to. */ - public static void main(String[] args) + public static void main(String[] args) { String host = ""; int port = 0; //The port number - - try + loginID = ""; + + try + { + loginID = args[0]; + } + catch(ArrayIndexOutOfBoundsException e) {} + + try { - host = args[0]; + host = args[2]; } - catch(ArrayIndexOutOfBoundsException e) + catch(Exception e) { host = "localhost"; } - ClientConsole chat= new ClientConsole(host, DEFAULT_PORT); - chat.accept(); //Wait for console data + + try + { + port = Integer.parseInt(args[1]); + } + catch(Exception e) + { + port = DEFAULT_PORT; + } + + + if(args.length > 0) { + try { + ClientConsole chat= new ClientConsole(host, port, loginID); + chat.accept(); //Wait for console data + } catch(Exception e) {} + } else { + System.out.println("Invalid Command!\nMust Enter Login ID!"); + System.exit(0); + } } } //End of ConsoleChat class diff --git a/code/simplechat1/EchoServer.java b/code/simplechat1/EchoServer.java index d4f3a1a..76d4544 100644 --- a/code/simplechat1/EchoServer.java +++ b/code/simplechat1/EchoServer.java @@ -4,6 +4,7 @@ import java.io.*; import ocsf.server.*; +import common.*; /** * This class overrides some of the methods in the abstract @@ -23,6 +24,8 @@ public class EchoServer extends AbstractServer * The default port to listen on. */ final public static int DEFAULT_PORT = 5555; + private static String loginID; + ChatIF serverUI; //Constructors **************************************************** @@ -31,9 +34,10 @@ public class EchoServer extends AbstractServer * * @param port The port number to connect on. */ - public EchoServer(int port) + public EchoServer(int port, ChatIF serverUI) { super(port); + this.serverUI = serverUI; } @@ -45,12 +49,20 @@ public EchoServer(int port) * @param msg The message received from the client. * @param client The connection from which the message originated. */ - public void handleMessageFromClient - (Object msg, ConnectionToClient client) - { - System.out.println("Message received: " + msg + " from " + client); - this.sendToAllClients(msg); - } + public void handleMessageFromClient + (Object msg, ConnectionToClient client) + { + String temp[] = String.valueOf(msg).split(" "); + + if(temp[0].equals("#login") && temp.length > 1) { + client.setInfo(loginID, temp[1]); + this.sendToAllClients(client.getInfo(loginID) + " has logged on"); + System.out.println("Message received: " + msg + " from " + client); + } else { + System.out.println("Message received: " + msg + " from " + client); + this.sendToAllClients(client.getInfo(loginID) + "> " + msg); + } + } /** * This method overrides the one in the superclass. Called @@ -81,29 +93,13 @@ protected void serverStopped() * @param args[0] The port number to listen on. Defaults to 5555 * if no argument is entered. */ - public static void main(String[] args) - { - int port = 0; //Port to listen on - - try - { - port = Integer.parseInt(args[0]); //Get port from command line - } - catch(Throwable t) - { - port = DEFAULT_PORT; //Set port to 5555 - } - - EchoServer sv = new EchoServer(port); - - try - { - sv.listen(); //Start listening for connections - } - catch (Exception ex) - { - System.out.println("ERROR - Could not listen for clients!"); - } + synchronized protected void clientConnected(ConnectionToClient client) { + System.out.println("A client has connected"); + } + + synchronized protected void clientDisconnected(ConnectionToClient client) { + System.out.println("A client has disconnected"); + this.sendToAllClients(client.getInfo(loginID) + " has logged off"); } } //End of EchoServer class diff --git a/code/simplechat1/ServerConsole.java b/code/simplechat1/ServerConsole.java new file mode 100644 index 0000000..d5d07f7 --- /dev/null +++ b/code/simplechat1/ServerConsole.java @@ -0,0 +1,186 @@ +// This file contains material supporting section 3.7 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +import java.io.*; +import client.*; +import ocsf.server.*; +import common.*; + +/** + * This class constructs the UI for a chat client. It implements the + * chat interface in order to activate the display() method. + * Warning: Some of the code here is cloned in ServerConsole + * + * @author François Bélanger + * @author Dr Timothy C. Lethbridge + * @author Dr Robert Laganière + * @version July 2000 + */ +public class ServerConsole implements ChatIF +{ + //Class variables ************************************************* + + /** + * The default port to connect on. + */ + final public static int DEFAULT_PORT = 5555; + private EchoServer sv; + private boolean serverClosed = false; + //Constructors **************************************************** + + /** + * Constructs an instance of the ClientConsole UI. + * + * @param host The host to connect to. + * @param port The port to connect on. + */ + public ServerConsole(int port) + { + sv = new EchoServer(port, this); + } + + public EchoServer get() { + return sv; + } + + public void handleMessageFromServer(String message) { + String temp[] = message.split(" "); + + if(message.equals("#quit")) { + try { + displayMessage("client has quit"); + System.out.println("Closing Connection"); + sv.close(); + System.exit(0); + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else if(message.equals("#stop")) { + try { + displayMessage("Server has stopped listening for new clients"); + System.out.println("Closing Connection"); + sv.stopListening(); + serverClosed = true; + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else if(message.equals("#close")) { + try { + sv.close(); + serverClosed = true; + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else if(temp[0].equals("#setport") && serverClosed) { + try { + System.out.println("Setting port to: " + Integer.parseInt(temp[1])); + sv.setPort(Integer.parseInt(temp[1])); + sv.listen(); + serverClosed = false; + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else if(message.equals("#start")) { + try { + displayMessage("client is logging in"); + System.out.println("Opening connection to server"); + sv.listen(); + serverClosed = false; + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else if(message.equals("#getport")) { + try { + System.out.println("Port is currently: " + sv.getPort()); + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else if(message.charAt(0) == '#') { + try { + System.out.println("ERROR! Command not valid"); + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else + displayMessage(message); + } + + + //Instance methods ************************************************ + + /** + * This method waits for input from the console. Once it is + * received, it sends it to the client's message handler. + */ + public void displayMessage(String msg) { + sv.sendToAllClients("SERVER MSG> " + msg); + } + + public void accept() + { + try + { + BufferedReader fromConsole = + new BufferedReader(new InputStreamReader(System.in)); + String message; + + while (true) + { + message = fromConsole.readLine(); + handleMessageFromServer(message); + } + } + catch (Exception ex) + { + System.out.println + ("Unexpected error while reading from console!"); + } + } + + /** + * This method overrides the method in the ChatIF interface. It + * displays a message onto the screen. + * + * @param message The string to be displayed. + */ + public void display(String message) + { + System.out.println("> " + message); + } + + + //Class methods *************************************************** + + /** + * This method is responsible for the creation of the Client UI. + * + * @param args[0] The host to connect to. + */ + public static void main(String[] args) + { + int port = 0; //Port to listen on + + try + { + port = Integer.parseInt(args[0]); //Get port from command line + } + catch(Throwable t) + { + port = DEFAULT_PORT; //Set port to 5555 + } + + ServerConsole sv = new ServerConsole(port); + + try + { + sv.get().listen(); //Start listening for connections + sv.accept(); + } + catch (Exception ex) + { + System.out.println("ERROR - Could not listen for clients!"); + } + } +} +//End of ConsoleChat class diff --git a/code/simplechat1/client/ChatClient.java b/code/simplechat1/client/ChatClient.java index fe1401e..bf54ddf 100644 --- a/code/simplechat1/client/ChatClient.java +++ b/code/simplechat1/client/ChatClient.java @@ -25,7 +25,10 @@ public class ChatClient extends AbstractClient * The interface type variable. It allows the implementation of * the display method in the client. */ - ChatIF clientUI; + + private boolean loggedOff; + private static String loginID; + ChatIF clientUI; //Constructors **************************************************** @@ -38,12 +41,21 @@ public class ChatClient extends AbstractClient * @param clientUI The interface type variable. */ - public ChatClient(String host, int port, ChatIF clientUI) + public ChatClient(String host, int port, String loginID, ChatIF clientUI) throws IOException { super(host, port); //Call the superclass constructor this.clientUI = clientUI; - openConnection(); + this.loginID = loginID; + + try { + openConnection(); + sendToServer("#login " + loginID); + loggedOff = false; + } catch(Exception e) { + System.out.println("Cannot open connection. Awaiting command."); + loggedOff = true; + } } @@ -68,7 +80,73 @@ public void handleMessageFromClientUI(String message) { try { - sendToServer(message); + String temp1[] = message.split(" "); + + if(temp1[0].equals("#quit")) { + try { + System.out.println("Closing Connection"); + quit(); + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else if(temp1[0].equals("#logoff")) { + try { + loggedOff = true; + sendToServer("client has logged off"); + System.out.println("Closing Connection"); + closeConnection(); + } catch(IOException e) { + System.out.println("ERROR!"); + } + } else if(temp1[0].equals("#sethost") && loggedOff) { + try { + System.out.println("Host set to: " + temp1[1]); + setHost(temp1[1]); + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else if(temp1[0].equals("#setport") && loggedOff) { + try { + System.out.println("Port set to: " + Integer.parseInt(temp1[1])); + setPort(Integer.parseInt(temp1[1])); + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else if(temp1[0].equals("#login") && loggedOff) { + if(temp1.length == 2) { + if(temp1[1].equals(loginID)) { + try { + System.out.println("Opening connection to server"); + openConnection(); + sendToServer("#login " + loginID); + loggedOff = false; + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else + System.out.println("Invalid Login ID!"); + } else + System.out.println("Command must be followed by loginID i.e.: #login "); + } else if(message.equals("#gethost")) { + try { + System.out.println(getHost()); + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else if(message.equals("#getport")) { + try { + System.out.println(getPort()); + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else if(temp1[0].charAt(0) == '#') { + try { + System.out.println("ERROR! Command not valid"); + } catch(Exception e) { + System.out.println("ERROR!"); + } + } else + sendToServer(message); } catch(IOException e) { @@ -90,5 +168,14 @@ public void quit() catch(IOException e) {} System.exit(0); } + + protected void connectionException(Exception e) { + clientUI.display("Connection to Server Has Forcefully Terminated!"); + System.exit(0); + } + + protected void connectionClosed() { + clientUI.display("Connection Closed!"); + } } //End of ChatClient class diff --git a/code/simplechat1/ocsf/client/AbstractClient.java b/code/simplechat1/ocsf/client/AbstractClient.java new file mode 100644 index 0000000..e905636 --- /dev/null +++ b/code/simplechat1/ocsf/client/AbstractClient.java @@ -0,0 +1,333 @@ +// This file contains material supporting section 3.7 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.client; + +import java.io.*; +import java.net.*; + +/** + * The AbstractClient contains all the methods necessary to set + * up the client side of a client-server architecture. When a client is thus + * connected to the server, the two programs can then exchange + * Object instances. + *

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

+ * Several public service methods are provided to application that use this + * framework. + *

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

+ * + * @author Dr. Robert Laganière + * @author Dr. Timothy C. Lethbridge + * @author François Bél;langer + * @author Paul Holden + * @version February 2001 (2.12) + */ +public abstract class AbstractClient implements Runnable { + + // INSTANCE VARIABLES *********************************************** + + /** + * Sockets are used in the operating system as channels of communication + * between two processes. + * + * @see java.net.Socket + */ + private Socket clientSocket; + + /** + * The stream to handle data going to the server. + */ + private ObjectOutputStream output; + + /** + * The stream to handle data from the server. + */ + private ObjectInputStream input; + + /** + * The thread created to read data from the server. + */ + private Thread clientReader; + + /** + * Indicates if the thread is ready to stop. Needed so that the loop in the + * run method knows when to stop waiting for incoming messages. + */ + private boolean readyToStop = false; + + /** + * The server's host name. + */ + private String host; + + /** + * The port number. + */ + private int port; + + // CONSTRUCTORS ***************************************************** + + /** + * Constructs the client. + * + * @param host + * the server's host name. + * @param port + * the port number. + */ + public AbstractClient(String host, int port) { + // Initialize variables + this.host = host; + this.port = port; + } + + // INSTANCE METHODS ************************************************* + + /** + * Opens the connection with the server. If the connection is already + * opened, this call has no effect. + * + * @exception IOException + * if an I/O error occurs when opening. + */ + final public void openConnection() throws IOException { + // Do not do anything if the connection is already open + if (isConnected()) + return; + + // Create the sockets and the data streams + try { + clientSocket = new Socket(host, port); + output = new ObjectOutputStream(clientSocket.getOutputStream()); + input = new ObjectInputStream(clientSocket.getInputStream()); + } catch (IOException ex) + // All three of the above must be closed when there is a failure + // to create any of them + { + try { + closeAll(); + } catch (Exception exc) { + } + + throw ex; // Rethrow the exception. + } + + clientReader = new Thread(this); // Create the data reader thread + readyToStop = false; + clientReader.start(); // Start the thread + } + + /** + * Sends an object to the server. This is the only way that methods should + * communicate with the server. + * + * @param msg + * The message to be sent. + * @exception IOException + * if an I/O error occurs when sending + */ + final public void sendToServer(Object msg) throws IOException { + if (clientSocket == null || output == null) + throw new SocketException("socket does not exist"); + + output.writeObject(msg); + } + + /** + * Reset the object output stream so we can use the same + * buffer repeatedly. This would not normally be used, but is necessary + * in some circumstances when Java refuses to send data that it thinks has been sent. + */ + final public void forceResetAfterSend() throws IOException { + output.reset(); + } + + /** + * Closes the connection to the server. + * + * @exception IOException + * if an I/O error occurs when closing. + */ + final public void closeConnection() throws IOException { + // Prevent the thread from looping any more + readyToStop = true; + + try { + closeAll(); + } finally { + // Call the hook method + connectionClosed(); + } + } + + // ACCESSING METHODS ------------------------------------------------ + + /** + * @return true if the client is connnected. + */ + final public boolean isConnected() { + return clientReader != null && clientReader.isAlive(); + } + + /** + * @return the port number. + */ + final public int getPort() { + return port; + } + + /** + * Sets the server port number for the next connection. The change in port + * only takes effect at the time of the next call to openConnection(). + * + * @param port + * the port number. + */ + final public void setPort(int port) { + this.port = port; + } + + /** + * @return the host name. + */ + final public String getHost() { + return host; + } + + /** + * Sets the server host for the next connection. The change in host only + * takes effect at the time of the next call to openConnection(). + * + * @param host + * the host name. + */ + final public void setHost(String host) { + this.host = host; + } + + /** + * returns the client's description. + * + * @return the client's Inet address. + */ + final public InetAddress getInetAddress() { + return clientSocket.getInetAddress(); + } + + // RUN METHOD ------------------------------------------------------- + + /** + * Waits for messages from the server. When each arrives, a call is made to + * handleMessageFromServer(). Not to be explicitly called. + */ + final public void run() { + connectionEstablished(); + + // The message from the server + Object msg; + + // Loop waiting for data + + try { + while (!readyToStop) { + // Get data from Server and send it to the handler + // The thread waits indefinitely at the following + // statement until something is received from the server + msg = input.readObject(); + + // Concrete subclasses do what they want with the + // msg by implementing the following method + handleMessageFromServer(msg); + } + } catch (Exception exception) { + if (!readyToStop) { + try { + closeAll(); + } catch (Exception ex) { + } + + connectionException(exception); + } + } finally { + clientReader = null; + } + } + + // METHODS DESIGNED TO BE OVERRIDDEN BY CONCRETE SUBCLASSES --------- + + /** + * Hook method called after the connection has been closed. The default + * implementation does nothing. The method may be overriden by subclasses to + * perform special processing such as cleaning up and terminating, or + * attempting to reconnect. + */ + protected void connectionClosed() { + } + + /** + * Hook method called each time an exception is thrown by the client's + * thread that is waiting for messages from the server. The method may be + * overridden by subclasses. + * + * @param exception + * the exception raised. + */ + protected void connectionException(Exception exception) { + } + + /** + * Hook method called after a connection has been established. The default + * implementation does nothing. It may be overridden by subclasses to do + * anything they wish. + */ + protected void connectionEstablished() { + } + + /** + * Handles a message sent from the server to this client. This MUST be + * implemented by subclasses, who should respond to messages. + * + * @param msg + * the message sent. + */ + protected abstract void handleMessageFromServer(Object msg); + + // METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ---------------- + + /** + * Closes all aspects of the connection to the server. + * + * @exception IOException + * if an I/O error occurs when closing. + */ + private void closeAll() throws IOException { + try { + // Close the socket + if (clientSocket != null) + clientSocket.close(); + + // Close the output stream + if (output != null) + output.close(); + + // Close the input stream + if (input != null) + input.close(); + } finally { + // Set the streams and the sockets to NULL no matter what + // Doing so allows, but does not require, any finalizers + // of these objects to reclaim system resources if and + // when they are garbage collected. + output = null; + input = null; + clientSocket = null; + } + } +} +// end of AbstractClient class \ No newline at end of file diff --git a/code/simplechat1/ocsf/client/AdaptableClient.java b/code/simplechat1/ocsf/client/AdaptableClient.java new file mode 100644 index 0000000..143592e --- /dev/null +++ b/code/simplechat1/ocsf/client/AdaptableClient.java @@ -0,0 +1,82 @@ +// This file contains material supporting section 6.13 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.client; + +import java.io.*; +import java.net.*; +import java.util.*; + +/** +* The AdaptableClient is a class +* that extends the AbstractClient in place of +* the ObservableClient .

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

+* +* @author Dr. Robert Laganière +* @version Febuary 2001 +*/ +class AdaptableClient extends AbstractClient +{ + //Instance variables ********************************************** + + /** + * The proxy used to simulate multiple class inheritance. + */ + private ObservableClient client; + +// CONSTRUCTORS ***************************************************** + + /** + * Constructs the client adapter. + * + * @param host the server's host name. + * @param port the port number. + */ + public AdaptableClient(String host, int port, ObservableClient client) + { + super(host, port); + this.client = client; + } + +// OVERRIDDEN METHODS ************************************************* + + /** + * Hook method called after the connection has been closed. + */ + final protected void connectionClosed() + { + client.connectionClosed(); + } + + /** + * Hook method called after an exception + * is raised by the client listening thread. + * + * @param exception the exception raised. + */ + final protected void connectionException(Exception exception) + { + client.connectionException(exception); + } + + /** + * Hook method called after a connection has been established. + */ + final protected void connectionEstablished() + { + client.connectionEstablished(); + } + + /** + * Handles a message sent from the server to this client. + * + * @param msg the message sent. + */ + final protected void handleMessageFromServer(Object msg) + { + client.handleMessageFromServer(msg); + } +} diff --git a/code/simplechat1/ocsf/client/ObservableClient.java b/code/simplechat1/ocsf/client/ObservableClient.java new file mode 100644 index 0000000..a816488 --- /dev/null +++ b/code/simplechat1/ocsf/client/ObservableClient.java @@ -0,0 +1,181 @@ +// This file contains material supporting section 6.13 of the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.client; + +import java.util.*; +import java.io.*; +import java.net.*; + +/** + * This class acts as a subclass of AbstractClient + * and is also an Observable class. + * Each time a new message is received, observers are notified. + * + * @author Dr Robert Laganière + * @author Dr Timothy C. Lethbridge + * @author François Bélange + * @version Febuary 2001 + */ +public class ObservableClient extends Observable +{ + // Class variables *************************************************** + + /** + * Indicates occurence of a connection exception. + */ + public static final String CONNECTION_EXCEPTION = "#OC:Connection error."; + + /** + * Indicates a close of the connection to server. + */ + public static final String CONNECTION_CLOSED = "#OC:Connection closed."; + + /** + * Indicates establishment of a connection to server. + */ + public static final String CONNECTION_ESTABLISHED = "#OC:Connection established."; + + //Instance variables ********************************************** + + /** + * The service instance used to simulate multiple class inheritance. + */ + private AdaptableClient service; + + //Constructor ***************************************************** + + public ObservableClient(String host, int port) + { + service = new AdaptableClient(host, port, this); + } + + //Instance methods ************************************************ + + /** + * Opens the connections with the server. + */ + final public void openConnection() throws IOException + { + service.openConnection(); + } + + /** + * Closes the connection to the server. + */ + final public void closeConnection() throws IOException + { + service.closeConnection(); + } + + /** + * Sends an object to the server. This is the only way that + * methods should communicate with the server. + * + * @param msg The message to be sent. + */ + final public void sendToServer(Object msg) throws IOException + { + service.sendToServer(msg); + } + +// ACCESSING METHODS ------------------------------------------------ + + /** + * @used to find out if the client is connnected. + */ + final public boolean isConnected() + { + return service.isConnected(); + } + + /** + * @return the port number. + */ + final public int getPort() + { + return service.getPort(); + } + + /** + * Sets the server port number for the next connection. + * Only has effect if the client is not currently connected. + * + * @param port the port number. + */ + final public void setPort(int port) + { + service.setPort(port); + } + + /** + * @return the host name. + */ + final public String getHost() + { + return service.getHost(); + } + + /** + * Sets the server host for the next connection. + * Only has effect if the client is not currently connected. + * + * @param host the host name. + */ + final public void setHost(String host) + { + service.setHost(host); + } + + /** + * @return the client's Inet address. + */ + final public InetAddress getInetAddress() + { + return service.getInetAddress(); + } + + + /** + * This method is used to handle messages from the server. This method + * can be overriden but should always call notifyObservers(). + * + * @param message The message received from the client. + */ + protected void handleMessageFromServer(Object message) + { + setChanged(); + notifyObservers(message); + } + + /** + * Hook method called after the connection has been closed. + */ + protected void connectionClosed() + { + setChanged(); + notifyObservers(CONNECTION_CLOSED); + } + + /** + * Hook method called each time an exception + * is raised by the client listening thread. + * + * @param exception the exception raised. + */ + protected void connectionException(Exception exception) + { + setChanged(); + notifyObservers(CONNECTION_EXCEPTION); + } + + /** + * Hook method called after a connection has been established. + */ + protected void connectionEstablished() + { + setChanged(); + notifyObservers(CONNECTION_ESTABLISHED); + } +} diff --git a/code/simplechat1/ocsf/index.html b/code/simplechat1/ocsf/index.html new file mode 100644 index 0000000..13025a0 --- /dev/null +++ b/code/simplechat1/ocsf/index.html @@ -0,0 +1,22 @@ + + +Installing and Running OCSF + + + +

To install OCSF, simply compile all the .java files in the client and +server directories.

+ +

OCSF is a Framework, so this directory contains no main program. To +learn how it works, consult the book +"Object-Oriented Software Engineering: Practical Software Development +using UML and Java" by Lethbridge and Laganière.

+ +

To use OCSF, import the "ocsf.client" or "ocsf.server" package in your +application code. Make sure that the ocsf directory is in your classpath +when you compile your application.

+ +

Back to the source code page.

+ + + diff --git a/code/simplechat1/ocsf/server/AbstractServer.java b/code/simplechat1/ocsf/server/AbstractServer.java new file mode 100644 index 0000000..0134b22 --- /dev/null +++ b/code/simplechat1/ocsf/server/AbstractServer.java @@ -0,0 +1,459 @@ +// 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 diff --git a/code/simplechat1/ocsf/server/AdaptableServer.java b/code/simplechat1/ocsf/server/AdaptableServer.java new file mode 100644 index 0000000..24462d4 --- /dev/null +++ b/code/simplechat1/ocsf/server/AdaptableServer.java @@ -0,0 +1,129 @@ +// This file contains material supporting section 6.13 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.io.*; +import java.net.*; +import java.util.*; + +/** +* The AdaptableServer is an adapter class +* that extends the AbstractServer class in place of +* the AbstractObservableServer .

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

+* +* @author Dr. Robert Laganière +* @version Febuary 2001 +*/ +class AdaptableServer extends AbstractServer +{ + //Instance variables ********************************************** + + /** + * The adapter used to simulate multiple class inheritance. + */ + private ObservableServer server; + +// CONSTRUCTORS ***************************************************** + + /** + * Constructs the server adapter. + * + * @param host the server's host name. + * @param port the port number. + */ + public AdaptableServer(int port, ObservableServer server) + { + super(port); + this.server = server; + } + +// OVERRIDDEN METHODS --------- + + /** + * Hook method called each time a new client connection is + * accepted. + * + * @param client the connection connected to the client. + */ + final protected void clientConnected(ConnectionToClient client) + { + server.clientConnected(client); + } + + /** + * Hook method called each time a client disconnects. + * + * @param client the connection with the client. + */ + final protected void clientDisconnected(ConnectionToClient client) + { + server.clientDisconnected(client); + } + + /** + * Hook method called each time an exception + * is raised in a client thread. + * + * @param client the client that raised the exception. + * @param exception the exception raised. + */ + final protected void clientException(ConnectionToClient client, + Throwable exception) + { + server.clientException(client, exception); + } + + /** + * Hook method called when the server stops accepting + * connections because an exception has been raised. + * + * @param exception the exception raised. + */ + final protected void listeningException(Throwable exception) + { + server.listeningException(exception); + } + + /** + * Hook method called when the server stops accepting + * connections. + */ + final protected void serverStopped() + { + server.serverStopped(); + } + + /** + * Hook method called when the server starts listening for + * connections. + */ + final protected void serverStarted() + { + server.serverStarted(); + } + + /** + * Hook method called when the server is closed. + */ + final protected void serverClosed() + { + server.serverClosed(); + } + + /** + * Handles a command sent from the client to the server. + * + * @param msg the message sent. + * @param client the connection connected to the client that + * sent the message. + */ + final protected void handleMessageFromClient(Object msg, + ConnectionToClient client) + { + server.handleMessageFromClient(msg, client); + } +} diff --git a/code/simplechat1/ocsf/server/ConnectionToClient.java b/code/simplechat1/ocsf/server/ConnectionToClient.java new file mode 100644 index 0000000..4394b37 --- /dev/null +++ b/code/simplechat1/ocsf/server/ConnectionToClient.java @@ -0,0 +1,270 @@ +// 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.io.*; +import java.net.*; +import java.util.HashMap; + +/** + * An instance of this class is created by the server when a client connects. It + * accepts messages coming from the client and is responsible for sending data + * to the client since the socket is private to this class. The AbstractServer + * contains a set of instances of this class and is responsible for adding and + * deleting them. + *

+ * 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) + */ +public class ConnectionToClient extends Thread { + // INSTANCE VARIABLES *********************************************** + + /** + * A reference to the Server that created this instance. + */ + private AbstractServer server; + + /** + * Sockets are used in the operating system as channels of communication + * between two processes. + * + * @see java.net.Socket + */ + private Socket clientSocket; + + /** + * Stream used to read from the client. + */ + private ObjectInputStream input; + + /** + * Stream used to write to the client. + */ + private ObjectOutputStream output; + + /** + * Indicates if the thread is ready to stop. Set to true when closing of the + * connection is initiated. + */ + private boolean readyToStop; + + /** + * Map to save information about the client such as its login ID. The + * initial size of the map is small since it is not expected that concrete + * servers will want to store many different types of information about each + * client. Used by the setInfo and getInfo methods. + */ + private HashMap savedInfo = new HashMap(10); + + // CONSTRUCTORS ***************************************************** + + /** + * Constructs a new connection to a client. + * + * @param group + * the thread groupSystem.out.println("Client at "+ client + + * "connected"); that contains the connections. + * @param clientSocket + * contains the client's socket. + * @param server + * a reference to the server that created this instance + * @exception IOException + * if an I/O error occur when creating the connection. + */ + ConnectionToClient(ThreadGroup group, Socket clientSocket, AbstractServer server) throws IOException { + super(group, (Runnable) null); + // Initialize variables + this.clientSocket = clientSocket; + this.server = server; + + clientSocket.setSoTimeout(0); // make sure timeout is infinite + + // Initialize the objects streams + try { + input = new ObjectInputStream(clientSocket.getInputStream()); + output = new ObjectOutputStream(clientSocket.getOutputStream()); + } catch (IOException ex) { + try { + closeAll(); + } catch (Exception exc) { + } + + throw ex; // Rethrow the exception. + } + + readyToStop = false; + start(); // Start the thread waits for data from the socket + } + + // INSTANCE METHODS ************************************************* + + /** + * Sends an object to the client. + * + * @param msg + * the message to be sent. + * @exception IOException + * if an I/O error occur when sending the message. + */ + final public void sendToClient(Object msg) throws IOException { + if (clientSocket == null || output == null) + throw new SocketException("socket does not exist"); + + output.writeObject(msg); + } + + /** + * Reset the output stream so we can use the same + * buffer repeatedly. This would not normally be used, but is necessary + * in some circumstances when Java refuses to send data that it thinks has been sent. + */ + final public void forceResetAfterSend() throws IOException { + output.reset(); + } + + /** + * Closes the client. If the connection is already closed, this call has no + * effect. + * + * @exception IOException + * if an error occurs when closing the socket. + */ + final public void close() throws IOException { + readyToStop = true; // Set the flag that tells the thread to stop + + try { + closeAll(); + } finally { + server.clientDisconnected(this); + } + } + + // ACCESSING METHODS ------------------------------------------------ + + /** + * Returns the address of the client. + * + * @return the client's Internet address. + */ + final public InetAddress getInetAddress() { + return clientSocket == null ? null : clientSocket.getInetAddress(); + } + + /** + * Returns a string representation of the client. + * + * @return the client's description. + */ + public String toString() { + return clientSocket == null ? null : clientSocket.getInetAddress().getHostName() + " (" + + clientSocket.getInetAddress().getHostAddress() + ")"; + } + + /** + * Saves arbitrary information about this client. Designed to be used by + * concrete subclasses of AbstractServer. Based on a hash map. + * + * @param infoType + * identifies the type of information + * @param info + * the information itself. + */ + public void setInfo(String infoType, Object info) { + savedInfo.put(infoType, info); + } + + /** + * Returns information about the client saved using setInfo. Based on a hash + * map. + * + * @param infoType + * identifies the type of information + */ + public Object getInfo(String infoType) { + return savedInfo.get(infoType); + } + + // RUN METHOD ------------------------------------------------------- + + /** + * Constantly reads the client's input stream. Sends all objects that are + * read to the server. Not to be called. + */ + final public void run() { + server.clientConnected(this); + + // This loop reads the input stream and responds to messages + // from clients + try { + // The message from the client + Object msg; + + while (!readyToStop) { + // This block waits until it reads a message from the client + // and then sends it for handling by the server + msg = input.readObject(); + server.receiveMessageFromClient(msg, this); + } + } catch (Exception exception) { + if (!readyToStop) { + try { + closeAll(); + } catch (Exception ex) { + } + + server.clientException(this, exception); + } + } + } + + // METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ---------------- + + /** + * Closes all connection to the server. + * + * @exception IOException + * if an I/O error occur when closing the connection. + */ + private void closeAll() throws IOException { + try { + // Close the socket + if (clientSocket != null) + clientSocket.close(); + + // Close the output stream + if (output != null) + output.close(); + + // Close the input stream + if (input != null) + input.close(); + } finally { + // Set the streams and the sockets to NULL no matter what + // Doing so allows, but does not require, any finalizers + // of these objects to reclaim system resources if and + // when they are garbage collected. + output = null; + input = null; + clientSocket = null; + } + } + + /** + * This method is called by garbage collection. + */ + protected void finalize() { + try { + closeAll(); + } catch (IOException e) { + } + } +} +// End of ConnectionToClient class \ No newline at end of file diff --git a/code/simplechat1/ocsf/server/ObservableOriginatorServer.java b/code/simplechat1/ocsf/server/ObservableOriginatorServer.java new file mode 100644 index 0000000..4333694 --- /dev/null +++ b/code/simplechat1/ocsf/server/ObservableOriginatorServer.java @@ -0,0 +1,165 @@ +// This file contains material supporting the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.server; + +/** +* The ObservableOriginatorServer is a subclass +* of ObservableServer that sends +* OriginatorMessage instances to its observers. +* This class should be used when the observers need to know +* the orginator of the messages received. The originator +* is null when the message sent concerns the server. +* +* 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.OriginatorMessage +*/ +public class ObservableOriginatorServer extends ObservableServer +{ + // Constructor ****************************************************** + + /** + * Constructs a new server. + * + * @param port the port on which to listen. + */ + public ObservableOriginatorServer(int port) + { + super(port); + } + + // Instance methods ************************************************ + + /** + * This method is used to handle messages coming from the client. + * Observers are notfied by receiveing an instance of OriginatorMessage + * that contains both the message received and a reference to the + * client who sent the message. + * + * @param message The message received from the client. + * @param client The connection to the client. + */ + protected synchronized void handleMessageFromClient + (Object message, ConnectionToClient client) + { + setChanged(); + notifyObservers(new OriginatorMessage(client, message)); + } + + /** + * Method called each time a new client connection is + * accepted. It notifies observers by sending an + * OriginatorMessage instance + * containing a reference to that client and + * the message defined by the static variable CLIENT_CONNECTED. + * + * @param client the connection connected to the client. + */ + protected synchronized void clientConnected(ConnectionToClient client) + { + setChanged(); + notifyObservers(new OriginatorMessage(client, CLIENT_CONNECTED)); + } + + /** + * Method called each time a client connection is + * disconnected. It notifies observers by sending an + * OriginatorMessage instance + * containing a reference to that client and + * the message defined by the static variable CLIENT_DISCONNECTED. + * + * @param client the connection connected to the client. + */ + synchronized protected void clientDisconnected(ConnectionToClient client) + { + setChanged(); + notifyObservers(new OriginatorMessage(client, CLIENT_DISCONNECTED)); + } + + + /** + * Method called each time an exception is raised + * by a client connection. + * It notifies observers by sending an + * OriginatorMessage instance + * containing a reference to that client and + * the message defined by the static variable CLIENT_EXCEPTION + * to which is appended the exception message. + * + * @param client the client that raised the exception. + * @param Throwable the exception thrown. + */ + synchronized protected void clientException( + ConnectionToClient client, Throwable exception) + { + setChanged(); + notifyObservers( + new OriginatorMessage(client, + CLIENT_EXCEPTION + exception.getMessage())); + } + + /** + * Method called each time an exception is raised + * while listening. + * It notifies observers by sending an + * OriginatorMessage instance + * containing the message defined by the static variable LISTENING_EXCEPTION + * to which is appended the exception message. + * The originator is set to null. + * + * @param exception the exception raised. + */ + protected synchronized void listeningException(Throwable exception) + { + setChanged(); + notifyObservers( + new OriginatorMessage(null, + LISTENING_EXCEPTION + exception.getMessage())); + } + + /** + * Method called each time the server is started. + * It notifies observers by sending an + * OriginatorMessage instance + * containing the message defined by the static variable SERVER_STARTED. + * The originator is set to null. + */ + protected synchronized void serverStarted() + { + setChanged(); + notifyObservers(new OriginatorMessage(null, SERVER_STARTED)); + } + + /** + * Method called each time the server is stopped. + * It notifies observers by sending an + * OriginatorMessage instance + * containing the message defined by the static variable SERVER_STOPPED. + * The originator is set to null. + */ + synchronized protected void serverStopped() + { + setChanged(); + notifyObservers(new OriginatorMessage(null, SERVER_STOPPED)); + } + + /** + * Method called each time the server is closed. + * It notifies observers by sending an + * OriginatorMessage instance + * containing the message defined by the static variable SERVER_CLOSED. + * The originator is set to null. + */ + synchronized protected void serverClosed() + { + setChanged(); + notifyObservers(new OriginatorMessage(null, SERVER_CLOSED)); + } +} diff --git a/code/simplechat1/ocsf/server/ObservableServer.java b/code/simplechat1/ocsf/server/ObservableServer.java new file mode 100644 index 0000000..5b16752 --- /dev/null +++ b/code/simplechat1/ocsf/server/ObservableServer.java @@ -0,0 +1,309 @@ +// This file contains material supporting section 6.13 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.util.*; +import java.io.*; +import java.net.*; + +/** + * This class acts as a subclass of AbstractServer + * and is also an Observable class. + * This means that when a message is received, all observers + * are notified. + * + * @author François Bélange + * @author Dr Timothy C. Lethbridge + * @author Dr Robert Laganière + * @version August 2000 + */ + +public class ObservableServer extends Observable +{ + // Class variables ************************************************ + + /** + * The string sent to the observers when a client has connected. + */ + public static final String CLIENT_CONNECTED= "#OS:Client connected."; + + /** + * The string sent to the observers when a client has disconnected. + */ + public static final String CLIENT_DISCONNECTED= "#OS:Client disconnected."; + + /** + * The string sent to the observers when an exception occurred with a client. + * The error message of that exception will be appended to this string. + */ + public static final String CLIENT_EXCEPTION= "#OS:Client exception."; + + /** + * The string sent to the observers when a listening exception occurred. + * The error message of that exception will be appended to this string. + */ + public static final String LISTENING_EXCEPTION= "#OS:Listening exception."; + + /** + * The string sent to the observers when the server has closed. + */ + public static final String SERVER_CLOSED= "#OS:Server closed."; + + /** + * The string sent to the observers when the server has started. + */ + public static final String SERVER_STARTED= "#OS:Server started."; + + /** + * The string sent to the observers when the server has stopped. + */ + public static final String SERVER_STOPPED= "#OS:Server stopped."; + + + //Instance variables ********************************************** + + /** + * The service used to simulate multiple class inheritance. + */ + private AdaptableServer service; + + + //Constructor ***************************************************** + + /** + * Constructs a new server. + * + * @param port the port on which to listen. + */ + public ObservableServer(int port) + { + service = new AdaptableServer(port, this); + } + + //Instance methods ************************************************ + + /** + * Begins the thread that waits for new clients + */ + final public void listen() throws IOException + { + service.listen(); + } + + /** + * Causes the server to stop accepting new connections. + */ + final public void stopListening() + { + service.stopListening(); + } + + /** + * Closes the server's connections with all clients. + */ + final public void close() throws IOException + { + service.close(); + } + + /** + * Sends a message to every client connected to the server. + * + * @param msg The message to be sent + */ + public void sendToAllClients(Object msg) + { + service.sendToAllClients(msg); + } + +// ACCESSING METHODS ------------------------------------------------ + + /** + * Used to find out if the server is accepting new clients. + */ + final public boolean isListening() + { + return service.isListening(); + } + + /** + * Returns an array of containing the existing + * client connections. This can be used by + * concrete subclasses to implement messages that do something with + * each connection (e.g. kill it, send a message to it etc.) + * + * @return an array of Thread containing + * ConnectionToClient instances. + */ + final public Thread[] getClientConnections() + { + return service.getClientConnections(); + } + + /** + * @return the number of clients currently connected. + */ + final public int getNumberOfClients() + { + return service.getNumberOfClients(); + } + + /** + * @return the port number. + */ + final public int getPort() + { + return service.getPort(); + } + + /** + * Sets the port number for the next connection. + * Only has effect if the server is not currently listening. + * + * @param port the port number. + */ + final public void setPort(int port) + { + service.setPort(port); + } + + /** + * Sets the timeout time when accepting connection. + * The default is half a second. + * The server must be stopped and restarted for the timeout + * change be in effect. + * + * @param timeout the timeout time in ms. + */ + final public void setTimeout(int timeout) + { + service.setTimeout(timeout); + } + + /** + * Sets the maximum number of + * waiting connections accepted by the operating system. + * The default is 20. + * The server must be closed and restart for the backlog + * change be in effect. + * + * @param backlog the maximum number of connections. + */ + final public void setBacklog(int backlog) + { + service.setBacklog(backlog); + } + + /** + * Hook method called each time a new client connection is + * accepted. The method may be overridden by subclasses. + * + * @param client the connection connected to the client. + */ + protected synchronized void clientConnected(ConnectionToClient client) + { + setChanged(); + notifyObservers(CLIENT_CONNECTED); + } + + /** + * Hook method called each time a client disconnects. + * The method may be overridden by subclasses. + * + * @param client the connection with the client. + */ + protected synchronized void clientDisconnected(ConnectionToClient client) + { + setChanged(); + notifyObservers(CLIENT_DISCONNECTED); + } + + /** + * Hook method called each time an exception + * is raised in a client thread. + * This implementation simply closes the + * client connection, ignoring any exception. + * The method may be overridden by subclasses. + * + * @param client the client that raised the exception. + * @param exception the exception raised. + */ + protected synchronized void clientException(ConnectionToClient client, + Throwable exception) + { + setChanged(); + notifyObservers(CLIENT_EXCEPTION); + try + { + client.close(); + } + catch (Exception e) {} + } + + /** + * This method is called when the server stops accepting + * connections because an exception has been raised. + * This implementation + * simply calls stopListening. + * This method may be overriden by subclasses. + * + * @param exception the exception raised. + */ + protected synchronized void listeningException(Throwable exception) + { + setChanged(); + notifyObservers(LISTENING_EXCEPTION); + stopListening(); + } + + /** + * This method is called when the server stops accepting + * connections for any reason. This method may be overriden by + * subclasses. + */ + synchronized protected void serverStopped() + { + setChanged(); + notifyObservers(SERVER_STOPPED); + } + + /** + * This method is called when the server is closed. + * This method may be overriden by subclasses. + */ + synchronized protected void serverClosed() + { + setChanged(); + notifyObservers(SERVER_CLOSED); + } + + /** + * This method is called when the server starts listening for + * connections. The method may be overridden by subclasses. + */ + protected synchronized void serverStarted() + { + setChanged(); + notifyObservers(SERVER_STARTED); + } + + /** + * This method is used to handle messages coming from the client. + * Observers are notfied by receiveing the transmitted message. + * Note that, in this implementation, the information concerning + * the client that sent the message is lost. + * It can be overriden, but is still expected to call notifyObservers(). + * + * @param message The message received from the client. + * @param client The connection to the client. + * @see ocsf.server.ObservableOriginatorServer + */ + protected synchronized void handleMessageFromClient + (Object message, ConnectionToClient client) + { + setChanged(); + notifyObservers(message); + } +} diff --git a/code/simplechat1/ocsf/server/OriginatorMessage.java b/code/simplechat1/ocsf/server/OriginatorMessage.java new file mode 100644 index 0000000..a25d2b4 --- /dev/null +++ b/code/simplechat1/ocsf/server/OriginatorMessage.java @@ -0,0 +1,61 @@ +// This file contains material supporting the textbook: +// "Object Oriented Software Engineering" and is issued under the open-source +// license found at www.lloseng.com + +package ocsf.server; + +/** + * A message class used by the Observable layer of the OCSF in order to conserve + * information about the originator of a message. + * + * @author Dr. Robert Laganière + * @version July 2001 + */ +public class OriginatorMessage +{ + /** + * The connection that originated the message + */ + private ConnectionToClient originator; + + /** + * The message. + */ + private Object message; + +// Constructor *************************************************************** + + /** + * Constructs an instance of an OriginatorMessage + * + * @param originator The client who created this message + * @param message The contents of the message + */ + public OriginatorMessage(ConnectionToClient originator, Object message) + { + this.originator = originator; + this.message = message; + } + +// Accessor methods ********************************************************* + + /** + * Returns the originating connection. + * + * @return The connection from which the message originated. + */ + public ConnectionToClient getOriginator() + { + return originator; + } + + /** + * Returns the message's contents. + * + * @return The content of the message. + */ + public Object getMessage() + { + return message; + } +} diff --git a/code/simplechat2/SimpleChat_Testcase_File_Abilaash_Uthayachandran_0300116640_autha045@uottawa.ca.docx b/code/simplechat2/SimpleChat_Testcase_File_Abilaash_Uthayachandran_0300116640_autha045@uottawa.ca.docx new file mode 100644 index 0000000..75d848a Binary files /dev/null and b/code/simplechat2/SimpleChat_Testcase_File_Abilaash_Uthayachandran_0300116640_autha045@uottawa.ca.docx differ