00001 00002 import java.awt.BorderLayout; 00003 import java.awt.Color; 00004 import java.awt.Dimension; 00005 import java.awt.Font; 00006 import java.awt.Toolkit; 00007 import java.text.SimpleDateFormat; 00008 import java.util.Calendar; 00009 00010 import javax.swing.JFrame; 00011 import javax.swing.JScrollPane; 00012 import javax.swing.JTextArea; 00013 import javax.swing.WindowConstants; 00014 00015 /** 00016 * Simple multi-threaded chat server with GUI 00017 * 00018 * @author Mikica B Kocic 00019 */ 00020 public class ChatServerFrame extends JFrame implements ChatServer.Context 00021 { 00022 /** 00023 * Implements java.io.Serializable interface 00024 */ 00025 private static final long serialVersionUID = 7408497530711923022L; 00026 00027 /** 00028 * TCP port that chat server listens for new connections 00029 */ 00030 private int port = 2000; 00031 00032 /* GUI components 00033 */ 00034 private JTextArea logArea; 00035 00036 /** 00037 * Creates a new instance of the <code>ChatClientFrame</code>. 00038 * 00039 * @param args the command line arguments passed to main 00040 */ 00041 public ChatServerFrame( String args[] ) 00042 { 00043 super( "IP1-4.1.2: Yet Another Simple Chat Server" ); 00044 00045 setDefaultCloseOperation( WindowConstants.EXIT_ON_CLOSE ); 00046 00047 ////////////////////////////////////////////////////////////////////////////////// 00048 00049 /* Top level components 00050 */ 00051 Font textFont = new Font( Font.MONOSPACED, Font.PLAIN, 14 ); 00052 00053 logArea = new JTextArea (); 00054 logArea.setFont( textFont ); 00055 logArea.setBackground( new Color( 0, 0, 128 ) ); 00056 logArea.setForeground( new Color( 220, 220, 192 ) ); 00057 logArea.setLineWrap( true ); 00058 logArea.setWrapStyleWord( true ); 00059 logArea.setEditable( false ); 00060 00061 JScrollPane logPane = new JScrollPane (); 00062 logPane.setViewportView( logArea ); 00063 00064 add( logPane, BorderLayout.CENTER ); 00065 00066 /* Adjust window dimensions not to exceed screen dimensions ... 00067 */ 00068 Dimension win = new Dimension( 1024, 600 ); 00069 Dimension scsz = Toolkit.getDefaultToolkit().getScreenSize(); 00070 win.width = Math.min( win.width, scsz.width ); 00071 win.height = Math.min( win.height, scsz.height - 40 ); 00072 setSize( win ); 00073 00074 /* ... then center window on the screen. 00075 */ 00076 setLocation( ( scsz.width - win.width )/2, ( scsz.height - 40 - win.height )/2 ); 00077 00078 /* Parse arguments: [ <port> ] 00079 */ 00080 if ( args.length >= 1 ) { 00081 try { 00082 port = Integer.parseInt( args[ 0 ] ); 00083 } catch ( NumberFormatException e ) { 00084 // TODO might report that we are using default port? 00085 } 00086 } 00087 00088 /* Start receiving connections 00089 */ 00090 ChatServer server = new ChatServer( port, this ); 00091 server.start(); 00092 } 00093 00094 /** 00095 * Gets current time stamp 00096 * 00097 * @return time in ISO format 00098 */ 00099 private static String now () 00100 { 00101 Calendar cal = Calendar.getInstance (); 00102 SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ); 00103 return sdf.format( cal.getTime() ); 00104 } 00105 00106 /** 00107 * Logs message in log area. 00108 * 00109 * @param str actual message 00110 */ 00111 @Override 00112 public void logMessage( String str ) 00113 { 00114 synchronized( logArea ) 00115 { 00116 logArea.append( now () + " " + str + "\n" ); 00117 logArea.setRows( logArea.getRows () + 1 ); 00118 logArea.setCaretPosition( logArea.getText().length () ); 00119 } 00120 } 00121 00122 /** 00123 * Updates status message by setting window title. 00124 * 00125 * @param str new status message 00126 */ 00127 @Override 00128 public void logStatus( String str ) 00129 { 00130 setTitle( "IP1-4.1.2 Chat Server " + str ); 00131 00132 /* Change background color to dark red if the socket is dead. 00133 */ 00134 if ( str.contains( ", dead") ) { 00135 logArea.setBackground( new Color( 92, 0, 0 ) ); 00136 } 00137 } 00138 00139 /** 00140 * Main entry point 00141 * 00142 * @param args the command line arguments 00143 */ 00144 public static void main( String args[] ) 00145 { 00146 final String[] copyOfArgs = args; 00147 00148 java.awt.EventQueue.invokeLater( 00149 new Runnable() { 00150 public void run() { 00151 new ChatServerFrame( copyOfArgs ).setVisible( true ); 00152 } 00153 } 00154 ); 00155 } 00156 } 00157 00158 /*! 00159 * \mainpage Yet Another Chat Server 00160 * 00161 * \section s_intro Introduction 00162 * 00163 * The package implements solution to \ref p_task as a part of 00164 * the <a href="http://dsv.su.se/utbildning/distans/ip1" target="_blank"><b>SU/IP1 00165 * course</b></a>. 00166 * 00167 * \image html chatServer.png 00168 * 00169 * \section s_desc Description 00170 * 00171 * Chat server's principal class is ChatServer that listens on socket for new 00172 * connections. It instantiates a new threaded client back-end for each new connection 00173 * (as instance of the ChatServerClient class) and keeps all such client connections 00174 * in the ChatServer.clients list. Client back-end thread purges itself 00175 * from the list after remote end is disconnected. 00176 * 00177 * ChatServerClient will receive messages in ChatServerClient.run() from the remote 00178 * client and call-back servers's ChatServer.broadcast() method that will distribute 00179 * message to all connected clients. 00180 * 00181 * Server may also opriginate its own messages, which will be prefixed 00182 * with the "[System] :: ". 00183 * 00184 * After receiving "wwhhoo" message, server will broadcast a list of all connected 00185 * clients in the form: <code>WWHHOO: <client-hostname>:<port></code>. 00186 * 00187 * One can start application with the following command: 00188 * 00189 * - <code>java -jar <a href="../chatServer.jar">chatServer.jar</a> 00190 * [ <port> ]</code> 00191 * 00192 * where the default port is <code>2000</code> 00193 * 00194 * \section s_eliza Joseph Weizenbaum's Eliza 00195 * 00196 * If the server detects that the user is alone in the chat-room, it will offer to user 00197 * to talk to an instance of 00198 * <a href="http://en.wikipedia.org/wiki/ELIZA" target="_blank"> Joseph Weizenbaum's 00199 * Eliza</a>, a simulation of a Rogerian 00200 * psychoterapist implemented by 00201 * <a href="http://chayden.net/eliza/Eliza.html" target="_blank">Charles Chayeden</a>. 00202 * 00203 * The server will start Eliza in respond to user message <code>talk eliza</code> 00204 * (ignoring spaces and character cases). 00205 * 00206 * \section s_jar Executable 00207 * 00208 * The jar file of the package can be found <a href="../chatServer.jar"><b>here</b></a>. 00209 * 00210 * \section s_src Sources 00211 * 00212 * Source files: 00213 * - \ref ChatServer.java 00214 * - \ref ChatServerClient.java 00215 * - \ref ChatServerFrame.java 00216 * 00217 */ 00218 /*! \page p_task IP1-4.1.2 Uppgift 00219 * 00220 * Gör ett fristående program med ett grafiskt användargränssnitt som implementerar en 00221 * chat-server som kan sända och ta emot text-meddelanden. Chat-servern ska använda 00222 * stream-sockets. 00223 * 00224 * Chat-servern ska kunna ta emot begäran om uppkoppling från nya chat-klienter och 00225 * placera alla chat-klienter i en egen tråd för att kunna ta emot ytterligare 00226 * chat-klienter. Varje chat-klient-tråd ligger och väntar på att en chat-klient 00227 * ska skicka ett text-meddelande. När så sker så ska chat-servern "broadcasta" 00228 * detta meddelande till samtliga chat-klienter. Chat-servern måste således hålla 00229 * reda på alla anslutna chat-klienter i någon datasamling. 00230 * 00231 * Alla text-meddelanden som sänds från chat-klienterna ska visas en en lämplig 00232 * swing-komponent, exempelvis en text-area. För samtliga text-meddelanden så ska 00233 * följande skrivas ut i denna swing-komponent: 00234 * 00235 * - Chat-klientens IP-adress 00236 * - Text-meddelandet från chat-klienten 00237 * 00238 * Chat-servern ska gå att starta på två sätt: 00239 * 00240 * -# <code>java Server</code> (då ska port default bli 2000) 00241 * -# <code>java Server <port></code> 00242 * 00243 * Efter detta ska chat-servern göra sig redo för att ta emot förfrågningar om 00244 * anslutning från chat-klienter. Om detta lyckas så ska det i chat-serverns 00245 * titelrad stå den host och port som chat-servern kör på samt antalet chat-klienter 00246 * som är uppkopplade. 00247 * 00248 * Observera! Chat-servern ska upptäcka när en klient inte längre är ansluten och 00249 * då ta bort den ur datasamlingen och döda tråden (eventuellt kan tråden döda sig 00250 * själv). 00251 */