轉載請注明原文地址:http://www.cnblogs.com/ygj0930/p/5827212.html
另:具體代碼實現已移植github: https://github.com/ygj0930/Chat-Room-in-Java ,大家fork之余記得給我個star呀~
聊天室程序的結構圖:

架構解釋:
Server服務器相當於一個中轉站,Client客戶端程序傳送信息到服務器,服務器再把信息分發到其他客戶端上,實現即時通信。
所需技術:
1:數據傳輸。
服務器與客戶端之間的信息傳遞,都通過數據通道實現,有一個客戶端連接到服務器,就有一條數據通道架設於該客戶端和服務器之間。
這條數據通道通過Socket來實現:每個客戶端通過一個socket與服務器建立連接,這是通道的一個“端點”。而服務器端ServerSocket一旦檢測到有客戶端接入,accept()方法就會馬上返回一個socket對象與客戶端的socket對接起來,這是通道的另一個“端點”。“兩點建立一條直線”,當然,這里不一定是直的(廢話)。總之,一條像水管一樣的通道就這樣把一個客戶端與服務器聯系起來了。之后的事情,就是把我們要傳輸的數據,通過數據通道傳輸,如同水管運水一樣,數據就是那些水。
2:持續監聽。
即時聊天要求某一客戶端(聊天者)敲出來的話要立刻更新到連接到該服務器上的所有客戶端的界面上,這個簡單,學過while循環的人應該都能立刻想到:我用一個while()語句持續地從數據通道檢測數據不就成了?一旦數據通道有信息,馬上把它取出來,在客戶端顯示就ok了。
然而!這事兒還沒完。每個客戶端都要保持持續接收用戶輸入的狀態。你總不能一個客戶端只發送一次信息就關閉掉了吧?
那么問題來了,又要保持持續監聽服務器端傳過來的信息,又要保持持續監聽鍵盤輸入,那這倆while循環到底怎么安排呢?
這里最容易犯的錯誤就是,用一個while循環嵌套了另一個while循環。比如下面的代碼:
while ((msg1 = br.readLine()) != null) { System.out.println(msg1); while (true) { msg2 = re.readLine(); pw.println(msg2); }
外層循環持續監聽數據通道,一旦有服務器端傳過來的信息,就提取出來顯示到客戶端。內層循環持續監聽鍵盤輸入,有則通過pw(數據通道,等下會有源碼)傳輸給服務器。
這里錯誤在於,內層循環是死循環,不會停止,所以會出現客戶端不停輸入信息發送到服務器,然而信息卻沒有更新到其他的客戶端去。因為客戶端代碼停留在內層循環,沒有出去執行監聽並提取數據通道信息那一部分代碼。
還有一種案例,就是在客戶端輸入信息,卻沒有顯示出來,狂按鍵盤也沒見打出一個字。但是把服務器程序關閉后卻又在客戶端程序窗口看到一大堆剛才輸入的信息。
這種錯誤的原因與上面相反,是監聽數據通道的while循環阻塞了下一個while循環(接收鍵盤輸入部分)的執行。代碼如下:
BufferedReader br = new BufferedReader(new InputStreamReader(socket .getInputStream())); String msg1; while ((msg1 = br.readLine()) != null) { System.out.println(msg1); } BufferedReader re = new BufferedReader(new InputStreamReader(System.in)); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); String msg2; while (true) { msg2 = re.readLine(); pw.println(msg2); }
上面一個while循環持續監聽數據通道(服務器向客戶端傳送信息),保持在等待接收信息狀態,導致下面的代碼沒有執行,所以客戶端不能接收用戶輸入,狂敲鍵盤也沒用。
而關閉服務器程序后,數據通道斷了,就不再監聽,轉而運行到了下面的while循環,所以就能接收用戶輸入。
那么正確的姿勢是怎樣的呢?那就是:並行。
在一個while循環運行的同時,另一個while循環也運行。兩個while循環沒有先后之分。
要實現並行,就需要用到多線程技術。【多線程是啥我就不講了,基本上能做到這個項目的人都是學了多線程再來實踐的吧哈哈哈~】
正確完整代碼貼出來,僅供參考。如果你是用來應付老師布置的作業的,小心和別的同學撞車-_-:
Server.java: import java.net.*; import java.io.*; import java.util.*; public class Server { int port; List<Socket> clients; ServerSocket server; public static void main(String[] args) { new Server(); } public Server() { try { port = 8080; clients = new ArrayList<Socket>(); server = new ServerSocket(port); while (true) { Socket socket = server.accept(); clients.add(socket); Mythread mythread = new Mythread(socket); mythread.start(); } } catch (Exception ex) { } } class Mythread extends Thread { Socket ssocket; private BufferedReader br; private PrintWriter pw; public String msg; public Mythread(Socket s) { ssocket = s; } public void run() { try { br = new BufferedReader(new InputStreamReader(ssocket.getInputStream())); msg = "歡迎【" + ssocket.getInetAddress() + "】進入聊天室!當前聊天室有【" + clients.size() + "】人"; sendMsg(); while ((msg = br.readLine()) != null) { msg = "【" + ssocket.getInetAddress() + "】說:" + msg; sendMsg(); } } catch (Exception ex) { } } public void sendMsg() { try { System.out.println(msg); for (int i = clients.size() - 1; i >= 0; i--) { pw = new PrintWriter(clients.get(i).getOutputStream(), true); pw.println(msg); pw.flush(); } } catch (Exception ex) { } } } } Client.java: import java.io.*; import java.net.*; import java.util.*; public class Client { public int port = 8080; Socket socket = null; public static void main(String[] args) { new Client(); } public Client() { try { socket = new Socket("127.0.0.1", port); new Cthread().start(); BufferedReader br = new BufferedReader(new InputStreamReader(socket .getInputStream())); String msg1; while ((msg1 = br.readLine()) != null) { System.out.println(msg1); } } catch (Exception e) { } } class Cthread extends Thread { public void run() { try { BufferedReader re = new BufferedReader(new InputStreamReader(System.in)); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); String msg2; while (true) { msg2 = re.readLine(); pw.println(msg2); } } catch (Exception e) { e.printStackTrace(); } } } }
老習慣,來個總結:用Socket構建數據通道(InputStream/OutputStream),用多線程同時運行兩個while實現監聽與更新,就是聊天室的技術所在。
(本人新手,懇請老碼農們指正。感激不盡!若需要轉載與交流,請於評論處留言告知我一聲,謝謝)
