package simpleNetworking;
import java.io.IOException;
import java.net.Socket;
import java.security.SignatureException;
import java.util.ArrayList;
import javax.swing.JOptionPane;
/**
*
* A simple Server that you can use in your programs.
*
* It has methods for:
* -- reading from each of the Clients attached to this Server
* -- writing to each of the Clients attached to this Server
* -- closing this Server's resources
*
* It does a minimal-security validation protocol with each Client
* who seeks to connect (the Client must use the Client's side of that protocol).
*
* You can construct this Server directly, but it is probably easier for you to:
* 1. Construct a new MultiServer. It constructs a Server for you.
* 2. Use the MultiServer's getServer(int n) method to get the Server
* after that Server has been connected to n validated Clients.
* For example, if you want a Server that communicates with 2 Clients, use:
* MultiServer multiServer = new MultiServer();
* Server server = multiServer.getServer(2);
* ... code that uses
* ... server.readLine
* ... server.writeLine
* ... server.close
* ... as you see fit.
*
*
* @author David Mutchler, based on the Java Tutorials on networking. May, 2009.
*/
public class Server {
/**
* Password shared by the Server and all Clients in this
* minimal-security protocol. Change the password if you wish.
*/
static final String PASSWORD = "Lucy in the Sky with Diamonds";
private ArrayList clients;
/**
* Prepares to communicate with Clients, with no Clients accepted so far.
*/
public Server() {
this.clients = new ArrayList();
}
/**
* Prepares to communicate with Clients, with the given Client as the sole
* Client accepted so far (assuming that the given Client passes the validation protocol).
*
* @param socketToCommunicateWithClient
* Socket to the sole Client with whom this Server can communicate so far
* (assuming that the Client passes the validation protocol).
* @throws IOException if unable to construct a Reader/Writer to the Client.
* @throws SignatureException if unable to validate the given Client
*/
public Server(Socket socketToCommunicateWithClient) throws SignatureException, IOException {
this();
addClient(socketToCommunicateWithClient);
}
/**
* Adds the given Client to the list of clients with which this Server
* can communicate, if the Client passes the validation protocol.
*
* @param socketToCommunicateWithClient
* Socket to the Client to be added to the list of clients
* with whom this Server can communicate,
* if the Client passes the validation protocol.
* @throws IOException if unable to construct a Reader/Writer to the Client.
* @throws SignatureException if unable to validate the given Client
*/
public synchronized void addClient(Socket socketToCommunicateWithClient) throws SignatureException, IOException {
try {
ReaderWriter readerWriter = new ReaderWriter(socketToCommunicateWithClient);
if (this.validate(readerWriter)) {
this.clients.add(readerWriter);
} else {
throw new SignatureException(
"Validation of this Client failed!\n\n"
+ "Is this a port-sniffing attack?\n"
+ "Or is your Client not using the matching validation protocol?\n");
}
} catch (IOException exception) {
throw new IOException(
"Could not construct a Reader/Writer to Socket " + socketToCommunicateWithClient);
}
}
/**
* Returns the next line available from this Server's nth Client,
* where n is the given parameter, stripping the terminating newline.
* Blocks (waits) if no line is available yet.
*
* @param client Index in the array of Clients for this Server,
* indicating which Client from which to read.
*
* @return the next line available from the nth Client,
* but with the terminating newline stripped.
* @throws IOException if an IO error occurs while reading.
* @throws IndexOutOfBoundsException if the given index of the Client is out of bounds.
*/
public String readLine(int client) throws IOException, IndexOutOfBoundsException {
// Blocks until a line is sent, the buffer is filled, or an IO exception occurs.
try {
return this.clients.get(client).readLine();
} catch (IndexOutOfBoundsException exception) {
throw new IndexOutOfBoundsException(
"Attempt to access client " + client + ".\n"
+ "There are only " + this.clients.size() + " clients that have been validated.\n");
}
}
/**
* Convenience method intended for when there is a single Client.
*
* @return the next line available from the first (and presumably only) Client,
* but with the terminating newline stripped.
* @throws IOException if an IO error occurs while reading.
*/
public String readLine() throws IOException {
return this.readLine(0);
}
/**
* Returns the next line available from this Server's nth Client,
* where n is the given parameter, stripping the terminating newline.
* Returns immediately (returning null) if no line is available yet.
*
* @param client Index in the array of Clients for this Server,
* indicating which Client from which to read.
* @return the next line available from the nth Client,
* but with the terminating newline stripped,
* or null if no line is available yet.
* @throws IOException if an IO error occurs while reading.
* @throws IndexOutOfBoundsException if the given index of the Client is out of bounds.
*/
public String readLineIfReady(int client) throws IOException, IndexOutOfBoundsException {
try {
return this.clients.get(client).readLineIfReady();
} catch (IndexOutOfBoundsException exception) {
throw new IndexOutOfBoundsException(
"Attempt to access client " + client + ".\n"
+ "There are only " + this.clients.size() + " clients that have been validated.\n");
}
}
/**
* Convenience method intended for when there is a single Client.
*
* @return the next line available from the first (and presumably only) Client,
* but with the terminating newline stripped,
* or null if no line is available yet.
* @throws IOException if an IO error occurs while reading.
*/
public String readLineIfReady() throws IOException {
return this.readLineIfReady(0);
}
/**
* Sends the given String, appending a newline to it,
* to this Server's nth Client, where n is the given parameter.
*
* @param stringToWrite String to send (with a newline appended)
* to this Server's nth Client.
* @param client Index in the array of Clients for this Server,
* indicating which Client to which to write.
* @throws IOException if an error occurs while writing.
* @throws IndexOutOfBoundsException if the given index of the Client is out of bounds.
*/
public void writeLine(String stringToWrite, int client) throws IOException, IndexOutOfBoundsException {
try {
this.clients.get(client).writeLine(stringToWrite);
} catch (IndexOutOfBoundsException exception) {
throw new IndexOutOfBoundsException(
"Attempt to access client " + client + ".\n"
+ "There are only " + this.clients.size() + " clients that have been validated.\n");
}
}
/**
* Convenience method intended for when there is a single Client.
*
* @param stringToWrite String to send (with a newline appended)
* to this Server's first (and presumably only) Client.
* @throws IOException if an IO error occurs while reading.
*/
public void writeLine(String stringToWrite) throws IOException {
this.writeLine(stringToWrite, 0);
}
/**
* To validate a proposed Client:
* -- The Client should send the (announced) password to this Server.
* -- This Server receives the password and checks whether it is OK.
* -- If it is not OK, this Server returns that the proposed connection is not validated
* and this Server does no further communication with the proposed Client.
* -- This Server echoes back the sent password.
* This method is the Server side of the above.
*
* Subclasses can override this, simply returning 'true' for no validation
* or doing their own validation protocol with their Clients.
*
* @param connectionToClient ReaderWriter to Client to use for validation.
* @return true if the Client is valid according to this validation protocol.
*/
protected boolean validate(ReaderWriter connectionToClient) {
try {
String passwordSent = connectionToClient.readLine();
connectionToClient.writeLine(passwordSent);
return passwordSent.equals(Server.PASSWORD);
} catch (IOException exception) {
exception.printStackTrace();
return false;
}
}
/**
* Closes the resources associated with communicating to the given Client.
* @param client Index in the array of Clients for this Server,
* indicating which Client to close resources for communicating.
*/
public void close(int client) {
this.clients.get(client).close();
}
/**
* For every Client currently associated with this Server,
* closes the resources associated with communicating with that Client.
*/
public void close() {
JOptionPane.showMessageDialog(null, "Closing the Servers's resources.");
for (ReaderWriter readerWriter : this.clients) {
readerWriter.close();
}
}
/**
* Returns the number of validated Clients currently associated with this Server.
* Blocks (waits) if a validation is in progress.
*
* @return the number of validated Clients currently associated with this Server.
*/
public synchronized int numberOfValidatedClients() {
return this.clients.size();
}
}