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(); } }