package talk; import java.applet.Applet; import java.util.Vector; import java.util.Enumeration; import java.awt.*; import java.net.*; import java.io.*; public class Client extends Applet implements Runnable { // Actually, we'll use this button as both 'connect' // and 'disconnect' depending on whether we're // connected right now. Button loginButton; // Name of the user TextField nameField; String name; // Are we connected yet? boolean connected = false; // Keeps the user informed Label status; // The socket on which we connect to the world. Socket socket; // The input stream on which we will read messages. // DataInputStream is a very useful class because it // has standard methods to read integers, strings, // and so forth. DataInputStream inputStream; // The output stream on which we will write messages. DataOutputStream outputStream; // The thread that will read messages from the server. Thread inputThread; // The thread that will write messages to the server. Thread outputThread; // A queue of strings to be sent to the server. Vector outputStrings; // The frame where the actual dialogue will take place. ChatFrame chatFrame; public void init() { outputStrings = new Vector(); GridBagLayout layout = new GridBagLayout(); setLayout(layout); GridBagConstraints c = new GridBagConstraints(); c.gridx = 0; c.gridy = 0; Label l = new Label("Name:"); layout.setConstraints(l, c); add(l); c.gridx++; c.weightx = 1.0; c.fill = GridBagConstraints.HORIZONTAL; nameField = new TextField(32); layout.setConstraints(nameField, c); add(nameField); c.gridx = 0; c.gridy++; c.gridwidth = 2; loginButton = new Button("Connect"); layout.setConstraints(loginButton, c); add(loginButton); c.gridx = 0; c.gridy++; status = new Label("Not connected."); layout.setConstraints(status, c); add(status); } public boolean action(Event evt, Object arg) { if (evt.target == loginButton) { if (!connected) { connect(); } else { disconnect("Not Connected."); } return true; } else if (evt.target == nameField) { // If the user hits return in the name field, // make sure they aren't already connected; // if not, connect now. if (!connected) { connect(); } return true; } return false; } void connect() { try { // Reset the output queue outputStrings = new Vector(); // And add the user's name to it name = nameField.getText(); outputStrings.addElement(name); status.setText("Connecting..."); // Look for tags telling us where to go. String host = getParameter("host"); if (host == null) { status.setText("Host parameter missing!"); return; } String portString = getParameter("port"); if (portString == null) { status.setText("Port parameter missing!"); return; } int port; try { port = Integer.parseInt(portString); } catch (java.lang.NumberFormatException e) { status.setText("Bad port parameter!"); return; } // Try to make a connection to that host and port. socket = new Socket(host, port); // Create the ChatFrame. chatFrame = new ChatFrame(this); // Now set up input and output streams. inputStream = new DataInputStream( socket.getInputStream()); outputStream = new DataOutputStream( socket.getOutputStream()); // Finally, start input and output threads. inputThread = new Thread(this); outputThread = new Thread(this); inputThread.start(); outputThread.start(); // Set the status, change the button label, // and set the connected flag. connected = true; loginButton.setLabel("Disconnect"); status.setText("Connected."); } catch (java.io.IOException e) { // Something went wrong. Break the bad news. disconnect("Connection Failed."); } } void disconnect(String statusText) { connected = false; if (chatFrame != null) { // Close the chat frame. chatFrame.dispose(); chatFrame = null; } try { if (socket != null) { socket.close(); } } catch (java.io.IOException e) { // Precious little we can do about this. } socket = null; if (inputThread != null) { // Stop the reading thread. inputThread.stop(); inputThread = null; } if (outputThread != null) { // Stop the writing thread. outputThread.stop(); outputThread = null; } status.setText(statusText); loginButton.setLabel("Connect"); } public void run() { if (Thread.currentThread() == inputThread) { // Read input from the server. inputRun(); } else if (Thread.currentThread() == outputThread) { // Write output to the server. outputRun(); } } void inputRun() { try { while (true) { // Input a line from the server. String s = inputStream.readUTF(); // Output it to the frame. chatFrame.outputLine(s); } } catch (java.io.IOException e) { // Something went wrong with the connection. // Set inputThread to null so disconnect() // won't kill it (and itself). inputThread = null; disconnect("Connection Lost"); } } void outputRun() { try { while (true) { outputStep(); } } catch (java.io.IOException e) { // Something went wrong with the connection. // Set outputThread to null so disconnect() // won't kill it (and itself). outputThread = null; disconnect("Connection Lost"); } } void outputStep() throws java.io.IOException { // Do any work pending // in the output queue. flushOutputStrings(); // Now, if there is no output waiting already, // then wait to be notified of a change // in the output queue. To do this, we have // to first synchronize on it, then // signal our willingness to wait if data // is not ready. synchronized (outputStrings) { if (outputStrings.size() == 0) { // Give up the output queue and // wait to be notified of a change // by inputRun. try { outputStrings.wait(); } catch (InterruptedException ie) { // This shouldn't happen. } } } } // outputRun calls this when it becomes aware of // new data in the output queue. void flushOutputStrings() throws java.io.IOException { // We check size on every pass, because new strings // might be added as we work. (But never removed, // because only this thread ever removes items // from the queue.) while (outputStrings.size() > 0) { // Output a string to the server String s = (String) outputStrings.elementAt(0); outputStream.writeUTF(s); // And remove what we have sent // from our queue of strings outputStrings.removeElementAt(0); } } // Accept a line of input from the user, // to be output to the server. void inputLine(String s) { // We don't want to write it to the socket // now; that could cause the applet to pause. // Instead, we add it to the queue of strings to // be sent by the output thread, and send // a notification that the output queue has // been changed. synchronized (outputStrings) { outputStrings.addElement(s); outputStrings.notify(); } // Also, output the line to the chat frame. // There's no reason to make the user wait // to see what they have typed. chatFrame.outputLine(s); } // Called by the browser when the applet // is about to be discarded. Most cleanup // is automatic, but we should kill // our own threads. For convenience, // we just call disconnect(), which covers // the bases thoroughly. public void destroy() { disconnect("Applet Terminated"); } }