/* ConnectionBroker is a network server program that "brokers" connections between pairs of users on the network. It keeps a list of clients who have registered themselves as available for connection. Anyone who connects to the server must give one of the following commands (indicated by the first charcter that is transmitted to the server): REGISTER: The connecting user is added to the list of available clients. Any remaining characters on the line after the REGISTER command are stored as info for the client. The client is assigned a unique ID number. The client waits for a partner to be connected. Note that a client can only have one partner. The client should not send any data, except for a close command, until it has a partner. When it gets a partner, it will be notified by a message that begins with the CONNECTED character. (Any data that is sent before connection is ignored.) CONNECT: The remaining data on the line after the CONNECT command must be an ID number. A connection is made between the client who sent the CONNECT command and the waiting client with the specified ID number, if any. The server will then relay data between the two clients until one side or the other closes the connection. Note that a message begining with the CLOSE character is interpreted as closing the connection. If the connection cannot be made, then the server replies with a line begining with the NOT_AVAILABLE character. If the connection is made, then a line beginning with the CONNECTED character is sent to both partners in the connection. SEND_CLIENT_LIST: The server responds by sending info about all the waiting clients. Each line begins with the CLIENT_INFO character, followed by the client's ID, a space, and the client's info. After all the info has been sent, a line beginning with the END_CLIENT_INFO character is sent to mark the end of the transmission. The port on which the server listens can be specified as a command-line parameter. If none is specified, then the DEFAULT_PORT is used. Note that since the ConnectionBroker relays data between the two partners in a connection, the partners can be applets on Web pages downloaded from the computer where the server is running. */ import java.io.*; import java.net.*; import java.util.Vector; public class ConnectionBroker { /* Characters that can occur as the first char on a transmitted or received line of data. See the comment at the top of this file for more info on how these are used. */ static final char REGISTER = '['; // Characters sent as commands static final char CONNECT = '='; // by a client to server. static final char SEND_CLIENT_LIST = ':'; static final char CLOSE = ']'; // Sent by a client to close a connection. static final char NOT_AVAILABLE = '!'; // Characters sent by the static final char CONNECTED = '.'; // server to the a client, static final char CLIENT_INFO = '>'; // with info for the static final char END_CLIENT_INFO = '<'; // client. /* The server listens on the DEFAULT_PORT if none is specified on the command line. */ static final int DEFAULT_PORT = 3030; private static Vector clientList = new Vector(); // Available clients. private static int nextClientID = 1; // Keeps track of the next // available number to use // to use as a unique ID for // a client. public static void main(String[] args) { // The main() routine creates a listening socket and // listens for requests. When a request is received, // a thread is created to service it. int port; // Port on which server listens. ServerSocket listener; Socket client; if (args.length == 0) port = DEFAULT_PORT; else { try { port = Integer.parseInt(args[0]); } catch (NumberFormatException e) { System.out.println(args[0] + " is not a legal port number."); return; } } try { listener = new ServerSocket(port); } catch (IOException e) { System.out.println("Can't start server."); System.out.println(e.toString()); return; } System.out.println("Listening on port " + listener.getLocalPort()); try { while (true) { client = listener.accept(); new ClientThread(client); } } catch (Exception e) { System.out.println("Server shut down unexpectedly."); System.out.println(e.toString()); System.exit(1); } } /* The four following routines manipulate the list of availalble clients who are waiting for a connection. All manipulation of the clientList is synchronized on the list object to avoid race conditions. */ static void addClient(Client client) { // Adds a new client to the clientList vector. synchronized(clientList) { client.ID = nextClientID++; if (client.info.length() == 0) client.info = "Anonymous" + client.ID; clientList.addElement(client); } System.out.println("Added client " + client.ID + " " + client.info); } static void removeClient(Client client) { // Removes the client from the clientList, if present. synchronized(clientList) { clientList.removeElement(client); } System.out.println("Removed client " + client.ID); } static Client getClient(int ID) { // Removes client from the clientList vector, if it // contains a client of the given ID. If so, the // removed client is returned. Otherwise, null is returned. synchronized(clientList) { for (int i = 0; i < clientList.size(); i++) { Client c = (Client)clientList.elementAt(i); if (c.ID == ID) { clientList.removeElementAt(i); System.out.println("Removed client " + c.ID); c.ID = 0; // Since this client is no longer waiting! return c; } } return null; } } static Client[] getClients() { // Returns an array of all the clients in the // clientList. If there are none, null is returned. synchronized(clientList) { if (clientList.size() == 0) return null; Client[] clients = new Client[ clientList.size() ]; for (int i = 0; i < clientList.size(); i++) clients[i] = (Client)clientList.elementAt(i); return clients; } } private static class Client { // Contains the data for a client. For clients who are // waiting for a connection, the ID will be > 0. For // clients who request a connection, the ID will be 0. int ID; // Unique identifier for a waiting client. String info; // The info for a waiting client. Socket connection; // Connection between server and this client. Reader incoming; // For reading data from the connection. PrintWriter outgoing; // For sending data over the connection. volatile Client partner; // If non-null, data is being relayed // between this client and its partner. private StringBuffer line = new StringBuffer(); // For reading data. private boolean checkLineFeed; // A kludge to handle the differerent // possible end-of-line markers. // If a \r character is seen, this // is set to true as a signal to // ignore the following \n, if any. String getln() throws IOException { // Read and return one line of data from the connection. if (incoming == null) // (This is not expected.) throw new IOException("Not connected."); int ch; ch = incoming.read(); if (ch == -1) throw new IOException("Attempt to read past end-of-stream."); if (ch == '\n' && checkLineFeed) ch = incoming.read(); line.setLength(0); while (ch != -1 && ch != '\n' && ch != '\r') { line.append((char)ch); ch = incoming.read(); } checkLineFeed = (ch == '\r'); return line.toString(); } void send(String message) throws IOException { // Send the message over the connection. if (outgoing == null) // (This is not expected.) throw new IOException("Not connected."); outgoing.println(message); outgoing.flush(); if (outgoing.checkError()) throw new IOException("Error while sending data."); } } // end nested class Client private static class ClientThread extends Thread { // A thread that handles one connection to the server. // The constructor starts the thread. Client client; // Represents the connected client. ClientThread(Socket connection) { // Constructor. Create the Client object and // start the thread to handle the connection. client = new Client(); client.connection = connection; start(); } public void run() { // The method that is run by this thread. Process // the connection, catching any errors. in the end, // make sure that the connection is closed and that // the client has been removed from the client list. try { client.connection.setSoTimeout(30000); // 30 seconds processClient(); } catch (Exception e) { } finally { try { client.connection.close(); } catch (Exception e) { } if (client.ID > 0) removeClient(client); } } void processClient() throws IOException { // Do all processing for this client. Create streams // for communication. Read the first line from the // incoming stream, which must be one of three possible // commands. Carry out the command, or throw an // exception if it is not a legal command. client.incoming = new InputStreamReader(client.connection.getInputStream()); client.outgoing = new PrintWriter(client.connection.getOutputStream()); String message = client.getln(); if (message.length() == 0) { throw new IOException( "Unexpected input to 'ConnectionBroker' program."); } client.connection.setSoTimeout(3600000); // 60 minutes char cmd = message.charAt(0); // This is the command. message = message.substring(1); // This might be data for command, if (cmd == REGISTER) { // Client wants to register and wait for a connection. // Add client to the clientList. Then start reading // data until a close command is received. If this // client has a partner, the incoming data is relayed // to the partner. A well-behaved client will not // send data until it has a partner (except if it // wants to close before getting a partner). client.info = message; addClient(client); int ID = client.ID; while (true) { message = client.getln(); if (client.partner != null) client.partner.send(message); if (message.length() > 0 && message.charAt(0) == CLOSE) { System.out.println("Closing down relay for client " + ID); break; } } } else if (cmd == CONNECT) { // Client wants to connect to another waiting client. // The data for the command is the ID of the waiting // client. Try to form the partnership. Then read // incoming data and relay it to the partner until // a CLOSE command is seen. int partnerID; try { partnerID = Integer.parseInt(message); } catch (NumberFormatException e) { client.send(NOT_AVAILABLE + "Client ID is not an integer."); throw new IOException("Illegal connect request."); } Client partner = getClient(partnerID); if (partner == null) { client.send(NOT_AVAILABLE + "Unknown client ID."); throw new IOException("Requested connection not found."); } client.partner = partner; // Set up partnership. partner.partner = client; System.out.println("Setting up relay to client " + partnerID); partner.send("" + CONNECTED); // Notify both partners. client.send("" + CONNECTED); while (true) { message = client.getln(); partner.send(message); if (message.length() > 0 && message.charAt(0) == CLOSE) { System.out.println("Closing down relay for client " + partnerID); break; } } } else if (cmd == SEND_CLIENT_LIST) { // Client wants a list of available waiting clients. // Send it and exit. Client[] clients = getClients(); if (clients != null) { for (int i =0; i < clients.length; i++) client.send("" + CLIENT_INFO + clients[i].ID + " " + clients[i].info); } client.send("" + END_CLIENT_INFO); } else { // The command is not one of the three legal commands. throw new IOException("Unexpected input to 'ConnectionBroker' program."); } } // end processClient() } // end class ClientThread } // end class ConnectionRelay