// 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