項目名稱
- 多線程聊天室
項目簡介
-
多個用戶可以在同一局域網下進行聊天.
-
類圖:
-
項目執行流程
客戶端通過對應端口號,和IP地址t與服務器端建立連接後,進行註冊,然後通過輸入輸出流,進行通信。
服務器端收到信息後,進行解析,判斷客戶端的需求(私聊,羣聊,下線等),從而進行相應的處理。
因爲採取多線程的方式,服務器端,線程比較多,採取線程池的方式進行處理。每個客戶端僅需要一個發送和一個接受線程,而且也不牽扯什麼資源共享的問題,直接繼承Thread類來實現多線程。
用一個ConcurrentHashMap存儲當前在線用戶信息。key–Socket,Value–String(用戶名)
項目基本功能
- 註冊
- 私聊
- 羣聊
- 下線
- 顯示當前在線的用戶
項目擴展功能
- 參數靈活化。將IP地址,端口號,線程池的線程數通過外界傳入。
- 添加登錄功能。用戶註冊過之後,下線之後,下次直接登陸即可,無需重新註冊。
- 收到歷史私聊信息。用戶下線之後,有人私聊對其發消息,當用戶再次上線後,可以查收到。
- 客戶端異常關閉,服務器端進行相應的處理。
擴展方案
實現參數靈活化:
- 通過鍵盤將參數運行時讀入。
- 通過main方法的args參數傳入。
- 通過文件讀入。
- 通過數據庫傳入。
最終選擇main方法的參數進行傳入,因爲需要的參數(端口號,IP地址,線程數目)比較少,且很方便,便於施行。
實現登錄功能
- 在HandelClient類中,添加一個靜態內部類User,存放用戶的用戶名和密碼及與對象相關聯的一個消息文件(文件後期實現上線後收到私聊消息有用)。
- 在HandelClient類中,添加一個靜態常量USER_SET,本身是一個CopyOnWriteArraySet(線程安全的Set集合)。
- 用戶進行註冊時,就用用戶的信息生成User對象,存進Set中。之所以選擇Set是因爲Set裏面的元素不可重複,且查詢時間複雜度O(1).
- 用戶在選擇登陸功能時,只需判斷Set中是否包含該對象即可,存在就將該用戶放進ConcurrentHashMap中就OK了。
實現收到歷史私聊信息。
業務需求說明,用戶下線之後,有人對其私聊,將該信息存儲起來,待用戶再次上線後,將消息發過去。
- 在進行私聊時,判斷用戶想要聊天的目標用戶,如果該用戶在ConcurrentHashMap中,證明該用戶還在線,否則判斷是不是在CopyOnWriteArraySet中,在就說明該目標用戶是一個合法的註冊過的.
- 因此將相應的消息存儲在目標用戶的文件中,當該目標用戶上線之後,將所有消息發送過去,並且將文件內容清空(很重要)。
客戶端異常關閉,服務器端進行相應的處理。
待續…
遇到的問題及解決方案
- 問題一:客戶端收不到消息(1.服務器沒發過來? 2.客戶端讀不來?)
1.沒有使用println。
2.使用了仍然沒有發出去,沒有flush。 - 問題二:一次註冊,終生使用
程序結束時,將用戶名和密碼按行寫進文件
程序運行時,從文件裏面按行都出來,拆分生成新對象,放進Set集合中。 - 問題三:流採用自動關閉的方式
流關閉的太快了,導致Socket中斷。最終只能採取手動關閉。 - 問題四:IP地址的驗證
相對來說比較簡單,字符串拆分加判斷就OK了。 - 文件內容的問題
用戶上線之後,將私聊消息發過去之後,需要將文件內容清空,否則下次依然發原始的信息。
項目源碼
https://github.com/excellent01/Java-Code
項目難點
- 實現登陸功能,
- 下線後再次上線可以收到私聊消息。
知識體系
- Java集合框架
- JUC包下的線程安全的集合
- 多線程
- Java網絡編程
- JavaIO
- 其它知識
效果展示
服務器端啓動
客戶端啓動並註冊
羣聊功能
登錄功能及上線後收到私聊信息
不足之處
因爲暫時沒有學習數據庫,不能將用戶最終的信息,存入數據庫,只能使用文件代替.
小段代碼:IP地址的校驗
private static boolean hostIsLegal(String host) {
if(host == null)
return true;
String[] arr = host.split("\\.");
if(arr.length != 4){
return false;
}
for(String str : arr){
try{
int num = Integer.parseInt(str);
if(num < 0 || num > 255){
return false;
}
}catch (NumberFormatException e){
return false;
}
}
if(host.endsWith("\\.")){
return false;
}
return true;
}