/* A ConnectionWindow supports two-way chatting between two users over the Internet. The constructor for a ConnectionWindow requires a connected Socket. Messages are sent and received through this socket. The window has a text-input box where the user can type messages. When the user presses return or clicks a "Send" button, the text in this box is transmitted over the connection. The window runs a thread which reads messages received from the other side of the connection. Messages that are sent or received are displayed in a "transcript" that fills most of the window. There is a Close button that the user can click to close the connection and the window. Clicking in the window's close box has the same effect. The class includes a main program so it can be run as a stand-alone application. In this case, the program can act as a server that waits for a connection, or it can act as a client that connects to a ConnectionWindow server. After a connection is established, ConnectionWindows are opened by the client and by the server, and the users can chat with each other. To run as a server listing on port 17171, use the command "java ConnectionWindow -s". To listen on a different port, specify the port number as the second command-line argument. To run as a client. use "java ConnectionWindow " where is the name or IP number of the computer on which the program is running as a server. The listening port of the server can be specified as a second command-line parameter. Of course, ConnectionWindows can also be used by other programs. In particular, they are designed to work with the ConnectionBroker server and the BrokeredChat applet. */ import java.awt.*; import java.awt.event.*; import java.net.*; import java.io.*; public class ConnectionWindow extends Frame implements ActionListener, WindowListener, Runnable { /* The "static" parts of this class are used when it is run as a standalone application. In that case, it makes a connection, either as a server or client, then creates an object of type ConnectionWindow to handle the connection. */ static final String HANDSHAKE = ""; // The HANDSHAKE string is sent by each end of the connection // to the other end, as soon as the connection is opened. // This is done to verify that the partner in the connection // is another instance of this program. (This is used in // the main() routine.) static final int DEFAULT_PORT = 17171; // Port on which the // server listens, if // none is specified // on the command line. public static final void main(String[] args) { int port; // Port on which the server listens. Socket connection; // For communication with the // program on the other end. TextReader in; // A stream for receiving the handshake. PrintWriter out; // A stream for sending the handshake. String message; // The message from the other side, // which should be the handshake message. if (args.length == 0) { // There must be at least one command line argument. System.out.println("Usage: java ConnectionWindow []"); System.out.println(" or java ConnectionWindow -s []"); return; } /* Get the port number from the second command-line paramter, if there is one. Otherwise, use the default port. */ if (args.length == 1) port = DEFAULT_PORT; else { try { port = Integer.parseInt(args[1]); } catch (NumberFormatException e) { System.out.println(args[1] + " is not a legal port number."); return; } } /* Open a connection. If the first command-line argument is "-s", then wait for a connection as a server. Otherwise, use the first argument as the computer name and connect to that computer as a client. Once the connection is made, exchange handshake messages. */ try { if (args[0].equalsIgnoreCase("-s")) { ServerSocket listener = new ServerSocket(port); System.out.println("Listening on port " + listener.getLocalPort()); connection = listener.accept(); listener.close(); } else { connection = new Socket(args[0],port); } out = new PrintWriter(connection.getOutputStream()); out.println(HANDSHAKE); out.flush(); in = new TextReader(connection.getInputStream()); message = in.getln(); if (! message.equals(HANDSHAKE) ) throw new IOException("Connected program is not ConnectionWindow"); System.out.println("Connected."); } catch (Exception e) { System.out.println("Error opening connection."); System.out.println(e.toString()); return; } /* Open a ConnectionWindow to handle the connection, and set its standalone flag to true (so that the program will terminate when the window is closed). */ ConnectionWindow w = new ConnectionWindow("ConnectionWindow", connection); w.standalone = true; } // end main(); /* The rest of the class specifies the behavior of ConnectionWindow objects. */ static final char MESSAGE = '0'; // The first character of any // line sent over the connection static final char CLOSE = ']'; // is a command, either MESSAGE // or CLOSE. When a CLOSE is // received, it indicates that // the connection has been closed // from the other side. TextArea transcript; // For displaying messages sent and // and received over the network // (and also messages from the program). TextField messageInput; // Where the user types lines to be transmitted. Socket connection; // The connection to the other end. This // provided by the constructor. It is // set to null when the conenction closes. TextReader incoming; // Stream for receiving data. PrintWriter outgoing; // Stream for transmitting data. Thread reader; // A thread that reads the data received over // the connection. Button clearButton; // A button that clears the transcript. Button sendButton; // A button that transmits the string in // the messageInput box. Pressing // return in the box will do the // same thing. Button closeButton; // A button that closes the connection. // Clicking in the window's close box // is equivalent. This closes the // connection, if it is not already dead, // and closes the window. boolean waitFor; // This can be set by the constructor. If // so, the window will wait to receive an // incoming message before allowing // any data to be sent out. boolean standalone = false; // This is set to true by main() // when this class is run as a // standalone program. It is // checked in windowClosed() // which will call System.exit() // if this variable is true. public ConnectionWindow(String title, Socket connection) { this(title,connection,false); } public ConnectionWindow(String title, Socket connection, boolean waitFor) { // Constructor. Create the window and open it // on the screen. The first parameter gives the // title for the title bar of the window. The // second should be a Socket that is ready to // send and receive data. If waitFor is true, the // widow will wait to receive some data over the // network before allowing any data to be sent out. super(title); this.connection = connection; addWindowListener(this); /* Create all the components for the window. */ transcript = new TextArea(); transcript.setBackground(Color.white); transcript.setEditable(false); // It's for display only. transcript.setFont( new Font("Monospaced", Font.PLAIN, 12) ); messageInput = new TextField(); messageInput.addActionListener(this); messageInput.setBackground(Color.white); closeButton = new Button("Close"); closeButton.addActionListener(this); closeButton.setBackground(Color.lightGray); clearButton = new Button("Clear Transcript"); clearButton.addActionListener(this); clearButton.setBackground(Color.lightGray); sendButton = new Button("Send"); sendButton.addActionListener(this); sendButton.setBackground(Color.lightGray); /* Lay out the components. */ Panel bottom = new Panel(); bottom.setLayout(new GridLayout(2,1)); Panel buttonBar = new Panel(); buttonBar.setLayout(new GridLayout(1,3)); buttonBar.add(closeButton); buttonBar.add(clearButton); buttonBar.add(sendButton); bottom.add(messageInput); bottom.add(buttonBar); add(bottom, BorderLayout.SOUTH); add(transcript, BorderLayout.CENTER); /* Get the streams for reading and writing data. There should be no error here. If there is, set this.connection to null and show a message in the transcript. */ try { incoming = new TextReader( connection.getInputStream() ); outgoing = new PrintWriter( connection.getOutputStream() ); } catch (IOException e) { transcript.setText("Error opening I/O streams!\n" + "Connection can't be used.\n" + "You can close the window now.\n"); closeButton.setLabel("Close Window"); sendButton.setEnabled(false); clearButton.setEnabled(false); this.connection = null; } /* Create the thread for reading data from the connection, unless an error just occured. */ if (this.connection != null) { reader = new Thread(this); reader.start(); if (waitFor) { sendButton.setEnabled(false); transcript.setText("Waiting for connection.\n"); this.waitFor = true; } } /* Size and show the window. */ setSize(500,350); show(); } // end constructor synchronized void addToTranscript(String message) { // Add the message to the transcript, followed by // a line-separator. transcript.append(message + "\n"); } synchronized void clearTranscript() { // Remove all the text from the transcript. transcript.setText(""); } public void actionPerformed(ActionEvent evt) { // Respond when the user clicks one of the buttons // or presses return in the message input box. Object source = evt.getSource(); if (source == clearButton) clearTranscript(); else if (source == sendButton || source == messageInput) doSend(); else if (source == closeButton) doClose(); } synchronized void doSend() { // Called by actionPerformed when the user wants to // transmit the string in the messageInput box. if (connection == null) { // There is no connection, so we can't transmit. return; } if (waitFor) { // Still waiting to receive incoming data. Data can't // be sent until something is received. return; } String message = messageInput.getText(); outgoing.println(MESSAGE + message); outgoing.flush(); if (outgoing.checkError()) { // If the output stream has gone bad, end the // connection by calling doForceClose(). addToTranscript("ERROR: An error occured while sending data."); doForceClose(); } else addToTranscript("SENT: " + message); messageInput.selectAll(); messageInput.requestFocus(); } synchronized void doClose() { // Called when the user clicks the close button or the // close box on the window. Close the connection, if // it is still open. Then close the window. if (connection != null) { // First, inform the other side that the connection // is being closed. outgoing.println(CLOSE); outgoing.flush(); try { connection.close(); // Close the connection. } catch (IOException e) { } connection = null; // So we know the connection is closed. } dispose(); // Close the window. } synchronized void doForceClose() { // This is called when an error occurs either in // sending or transmitting data or when a CLOSE // message has been received from the other side. // Put a message in the transcript, change the // user interface to show that the show is over, // and close the connection. if (connection == null) { // There is no connection to be closed. // (This really shouldn't happen, but let's be safe. return; } closeButton.setLabel("Close Window"); sendButton.setEnabled(false); clearButton.setEnabled(false); try { connection.close(); } catch (IOException e) { } addToTranscript(""); addToTranscript("CLOSED: Connection has been closed."); addToTranscript(" You can now close the window."); connection = null; } public void run() { // The run method for the thread the reads the data // that is received over the connection. It simply // reads lines of data and posts them to the transcript // until the connection is closed or an error occurs. try { while (true) { String message = incoming.getln(); synchronized(this) { if (connection == null) { // The connection has been closed somehow. break; } if (waitFor) { addToTranscript("Connected.\n"); sendButton.setEnabled(true); waitFor = false; } else if (message.length() > 0) { if (message.charAt(0) == CLOSE) { // The other side has closed the connection. // Print a message and end. addToTranscript(""); addToTranscript("Connection closed from other side."); doForceClose(); break; } message = message.substring(1); addToTranscript("RECEIVED: " + message); } } } } catch (Exception e) { // If an error occurs during input, show a message // and close the connection. synchronized (this) { if (connection != null) { addToTranscript("ERROR: An error occurred while receiving data."); doForceClose(); } } } } public void windowClosing(WindowEvent evt) { // Part of the WindowListener interface. It is // called when the user clicks the window's close box. // Call doClose() to close the connection and the window. doClose(); } public void windowClosed(WindowEvent evt) { // This is called after the window has been closed. // If this was a standalone program, shut down the // Java virtual machine (to end the program). if (standalone) System.exit(0); } // Other methods required by the WindowListener interface. public void windowOpened(WindowEvent evt) { } public void windowIconified(WindowEvent evt) { } public void windowDeiconified(WindowEvent evt) { } public void windowActivated(WindowEvent evt) { } public void windowDeactivated(WindowEvent evt) { } } // end class ConnectionWindow