import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import java.io.*; import java.net.*; import java.util.ArrayList; import java.util.Scanner; /** * BuddyChat client program. When started, it puts up a window to get info * from the user. It then connects to a BuddyChatServer. It puts up a new * window that displays the list of clients available on the server; the user * can select one of these client and open a chat connection to that user. * Furthremore, the BuddyChat program listens for incoming connection requests * from other clients of the server. A new window, of type BuddyChatWindow, * is opened for each incoming or outgoing connection. If the user closes this * window (or if it closes because the connection to the server dies), the user * can no longer open new connections, but connections that are in place will * continue and the program will not end until all BuddyChatWindows are closed. *
This program maintains a connection to the server as long as it is
* running (unless the connection closes becuase of error or because the
* server shuts down). It gets a notice from the server whenever a client
* is added to or removed from the list of clients registered with the
* server, and the list that is displayed to the user is modified accordingly.
*/
public class BuddyChat {
private static final String DEFAULT_SERVER_HOST = "localhost";
private static final int DEFAULT_SERVER_PORT = 12001;
private static Socket connectionToServer;
private static ServerSocket listeningSocket;
private static String secret; // This client's secret, provided by the server.
private static String handle; // This client's handle.
private static boolean running; // Is the connection to the server still in place?
/**
* The main routine just creates a window of type IntroWindow, which
* is a static nested class. That window gets info from the user
* and does all remaining setup.
*/
public static void main(String[] args) {
new IntroWindow();
}
/**
* Returns true if the client list window is still open.
* This is called by BuddyChatWindow when the last window
* of that type closes, to see whether the program should end.
* If the client list window is still open, the program does not end.
*/
public static boolean isRunning() {
return running;
}
/**
* A window of this type is opened by the main program. It gets
* the host name (or ip) of the server, the port on which the server
* is listening, and the "handle" that will identify this user
* in the list of clients on the server. The user can click
* "Cancel" or "Connect". If the user clicks "Connect", tries
* to establishe a connection to the server. If that succeeds,
* the IntroWindow is closed and a window of type BuddyListWindow
* is opened.
*/
private static class IntroWindow extends JFrame implements ActionListener {
JButton connectButton, cancelButton;
JTextField serverInput, portInput, handleInput; // For getting info from user.
/**
* Constructor shows a window with input boxes for server name/ip,
* server port number, and user's handle. All boxes have default
* values filled in.
*/
IntroWindow() {
super("Connect to BuddyChat server...");
cancelButton = new JButton("Cancel");
cancelButton.addActionListener(this);
connectButton = new JButton("Connect");
connectButton.addActionListener(this);
serverInput = new JTextField(DEFAULT_SERVER_HOST, 18);
portInput = new JTextField("" + DEFAULT_SERVER_PORT, 5);
handleInput = new JTextField("user" + (int)(10000*Math.random()), 10);
JPanel content = new JPanel();
setContentPane(content);
content.setBorder(BorderFactory.createLineBorder(Color.GRAY,3));
content.setLayout(new GridLayout(4,1,3,3));
content.setBackground(Color.GRAY);
JPanel row;
row = new JPanel();
row.setLayout(new FlowLayout(FlowLayout.CENTER,5,5));
row.add(new JLabel("Server name or IP:"));
row.add(serverInput);
content.add(row);
row = new JPanel();
row.setLayout(new FlowLayout(FlowLayout.CENTER,5,5));
row.add(new JLabel("Port number on server:"));
row.add(portInput);
content.add(row);
row = new JPanel();
row.setLayout(new FlowLayout(FlowLayout.CENTER,5,5));
row.add(new JLabel("Your \"handle\":"));
row.add(handleInput);
content.add(row);
row = new JPanel();
row.setLayout(new FlowLayout(FlowLayout.CENTER,5,5));
row.add(cancelButton);
row.add(connectButton);
content.add(row);
pack();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
setLocation( (screenSize.width - getWidth())/2, (screenSize.height - getHeight())/2);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
handleInput.selectAll();
handleInput.requestFocus();
}
/**
* Respond to "Cancel" button by ending program, and to
* "Connect" button by calling the doConnect() method.
*/
public void actionPerformed(ActionEvent evt) {
if (evt.getSource() == cancelButton)
System.exit(0);
else if (evt.getSource() == connectButton || evt.getSource() == handleInput)
doConnect();
}
/**
* Try to establish a connection to the server using info from
* the window. If this fails, a message box is popped up to
* inform the user, and the IntroWindow stays on the screen. If
* it succeeds, the IntroWindow is closed and a BuddyListWindow
* is opened.
*/
void doConnect() {
String server = serverInput.getText().trim();
if (server.length() == 0) {
JOptionPane.showMessageDialog(this,"Server name can't be empty.");
return;
}
int port;
try {
port = Integer.parseInt(portInput.getText());
if (port <= 0 || port > 65525)
throw new NumberFormatException();
}
catch (NumberFormatException e) {
JOptionPane.showMessageDialog(this,"Illegal port number.");
return;
}
handle = handleInput.getText().trim();
if (handle.length() == 0) {
JOptionPane.showMessageDialog(this,"Handle can't be empty.");
return;
}
try {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
connectionToServer = new Socket(server,port);
PrintWriter out = new PrintWriter(connectionToServer.getOutputStream());
BufferedReader in = new BufferedReader(new InputStreamReader(
connectionToServer.getInputStream()));
out.println("BuddyChatClient");
out.flush();
if (out.checkError())
throw new Exception("Error while sending identification info to server.");
String input = in.readLine();
if (! "BuddyChatServer".equals(input))
throw new Exception("Server did not properly identify itself.");
out.println(handle);
listeningSocket = new ServerSocket(0); // For accepting chat connection requests
out.println(listeningSocket.getLocalPort());
out.flush();
if (out.checkError())
throw new Exception("Error while sending identification info to server.");
secret = in.readLine();
if (secret == null)
throw new Exception("Connection closed unexpectedly by server.");
new BuddyListWindow(in,out);
dispose();
}
catch (Exception e) {
if (listeningSocket != null) {
try {
listeningSocket.close();
}
catch (Exception e2) {
}
}
setCursor(Cursor.getDefaultCursor());
JOptionPane.showMessageDialog(this,"Can't open connection to server:\n" + e);
}
}
} // end nested class IntroWindow
/**
* A window that displays the client list from the BuddyChatServer.
* The user can select one of these to connect to. The user can click
* a button in the window to close all windows (including BuddyChatWindows)
* and end the program. If the user just closes the BuddyListWindow
* by clicking its closed box, existing BuddyChatWindows remain open.
* The window maintains three threads: One for listening for connection
* requests from other clients, one for sending messages to the BuddyChatServer,
* and one for reading messages from the BuddyChatServer.
* Note that the sockets used for listening and for the connection to
* the server are static member variables in the main BuddyChat class.
* This client's handle and secret are also static member variables in BuddyChat.
*/
private static class BuddyListWindow extends JFrame
implements ActionListener, ListSelectionListener {
JButton connectButton;
JButton closeButton;
JList buddyList; // Holds the list of clients.
volatile ArrayList