//cs3.java --- by tsaiwn@csie.nctu.edu.tw // javac cs3.java // java cs3 3388 (Default port 5566 is used if port == 0 ) ////// 要進入聊天室的用 telnet host_name port_listening 測試即可 /// For example, telnet 127.0.0.1 3388 import java.io.*; import java.net.*; import java.util.*; interface CMD_Constant { // 把所有共用常數集中在一個 interface public final int CMD_DATA = -1; public final int CMD_QUIT = 0; public final int CMD_MSG = 1, CMD_LIST = 2; public final int CMD_QUERY = 3, CMD_NICK = 4; public final int CMD_HELP = 999; public final String SECRET_NAME = "IloveGiGi IwantGiGi"; } /// 之後各class 只要 implements CMD_Constant 就能看到以上常數 public class cs3 { private final static int DEFAULT_PORT = 5566; private ServerSocket serverSock; private int portNumber; private Socket clientSock; public cs3 (int port) { // constructor portNumber = port; try { serverSock = new ServerSocket( portNumber ); System.out.println("cs3 started on port " + portNumber + " at " + new Date().toString() ); // then, wait for a client to connect into while ( true ) { clientSock = serverSock.accept(); // after accept a connection, fork a thread to handle it ClientThread guest = new ClientThread(clientSock); guest.start( ); // it will call run( ) in ClientThread } } catch ( Exception e ) { System.err.println("Cannot startup chat server!"); } } // cs3 constructor public static void main(String[ ] argv) { if ( argv.length != 1 ) { System.out.println("Usage: java cs3 [port number]"); System.exit(0); } int port = 0; try { port = Integer.parseInt( argv[0] ); } catch ( Exception e ) { System.err.println("Invalid port number"); System.exit(1); } if(port == 0) port = DEFAULT_PORT; new cs3( port ); // will call constructor } // main } // class cs3 ////////////////////////////////////////////////////////////// class ClientThread extends Thread implements CMD_Constant { //static private Hashtable onlineUsers = new Hashtable( ); // MAP //以上是舊版寫法, JDK 1.5 (5.0) 之後 support template 寫法 static private Hashtable onlineUsers = new Hashtable( ); // MAP static private String lock = "a Lock"; // static ensure one copy public ClientThread(Socket skt) { mySock = skt; } // constructor private void setNickname(String name) { synchronized ( lock ) { onlineUsers.put(name, this); } } // setNickname private void doBcast(String msg) { for (Enumeration e=onlineUsers.elements();e.hasMoreElements();){ ClientThread c = (ClientThread) e.nextElement(); if ( c != this ) c.writeLine(msg); // 不必寫給自己 ! } } // doBcast private void doList( ) { for(Enumeration e=onlineUsers.elements();e.hasMoreElements();) { ClientThread c = (ClientThread) e.nextElement(); writeLine("CHAT *** [" + c.getNickname() + "@" + c.getRemoteAddr() + "] ***"); } // for } // doList private Socket mySock; private String myName; private String remoteAddr; // 連著我的對方 IP address private BufferedReader in; private PrintWriter out; private PrintStream tty = System.out; // tty 只是簡寫而已 private Date now; private String getRemoteAddr() { return remoteAddr; } private String getNickname() { return myName; } public void run() { now = new Date(); try { remoteAddr = mySock.getInetAddress().getHostAddress(); // 然後取得InputStream並包成 BufferedReader 方便 readLine() in = new BufferedReader( new InputStreamReader(mySock.getInputStream()) ); // 再取得 OutputStream 並包成 PrintWriter out = new PrintWriter( new OutputStreamWriter(mySock.getOutputStream()), true ); // 接著, 要求連線者輸入 nickname myName = askNickname(); // 若輸入怪異的nickname例如Control_C 則終止連線 if (stranger(myName) ){ close(); return; } // 廣播給所有聊天室的人 doBcast("CHAT *** " + myName + " is coming in ***"); // 並在 console 上顯示 (tty == System.out) tty.println(myName + "@"+remoteAddr+" enters the Chat Room " + now.toString() ); // writeLine(msg) 會把 msg 寫到目前連線者終端機 writeLine("CHAT *** Welcome 歡迎 "+myName+" 進入聊天室 ***"); writeLine("You can type '/help' for help"); String cmd, msg; int mode; FOO: while ( (cmd = in.readLine()) != null ) { StringTokenizer stkn = new StringTokenizer(cmd, " \t"); String command = " "; if(stkn.countTokens( ) >= 1) command = stkn.nextToken(); msg = " "; if(stkn.hasMoreTokens()) msg = stkn.nextToken("\n"); mode = parseCommand(command.toUpperCase()); switch ( mode ) { case CMD_MSG: doMsg(msg); break; case CMD_LIST: doList(); break; case CMD_HELP: doHelp(); break; case CMD_QUIT: now = new Date(); tty.println(myName + " said BYE at " + now.toString() ); doBcast("["+myName + " saied Bye Bye! ]"); break FOO; case CMD_DATA: doBcast("["+myName + "] " + cmd); tty.println(myName + ": " + cmd); break; } // switch } // while FOO: } catch ( Exception e ) { tty.println(e.toString()); } now = new Date( ); tty.println(now.toString() + " one thread stop"); close(); } private void writeLine(String msg) { try { out.println(msg+"\r"); // with carriage-return } catch ( Exception e ) { } // simply ignore the Exception } // writeLine(msg) 把 msg 由此連線的 out 寫到連線者的 InputStream private void close( ) { try { onlineUsers.remove(myName); if ( mySock != null ) mySock.close(); } catch ( Exception e ) { } } // close( ) 關閉此連線之 socket 及其 In/Out Stream static int numUsers = 0; // should be static (one copy for all) String genName( ) { return "pp" + ++numUsers; } private String askNickname() throws Exception { String name = genName( ); writeLine("[now is " + new Date()+"\nYour Nickname is " + name); setNickname(name); return name; } // askNickname() private boolean stranger(String myName) { return false; } private void doMsg(String cmd) { writeLine("not Implemented yet!");} void doHelp() { writeLine("commands: /MSG /LIST /HELP /QUIT"); } private int parseCommand(String x) { if ( x.startsWith("/QUIT") || x.startsWith("/BYE") ) return CMD_QUIT; if ( x.startsWith("/LIST") ) return CMD_LIST; if (x.startsWith("/MSG")||x.startsWith("/TELL"))return CMD_MSG; if (x.startsWith("/HELP")||x.startsWith("/?")) return CMD_HELP; if ( x.startsWith("/") ) return CMD_HELP; // error cmd return CMD_DATA; // 不是 "/"開始的都當作 message data } // parseCommand } // class