Java實現(xiàn)多線程聊天室
本文實例為大家分享了Java實現(xiàn)多線程聊天室的具體代碼,供大家參考,具體內(nèi)容如下
之前呢已經(jīng)用單線程的方式來實現(xiàn)了聊天室,但其實它的功能并不齊全,下面用多線程來實現(xiàn),功能會比單線程聊天室更加齊全,也更人性化一點
多線程版本的聊天室
1. 功能分析:
- 實現(xiàn)用戶注冊,上線,下線
- 實現(xiàn)群聊和私聊
- 統(tǒng)計當(dāng)前在線人數(shù)
2. 服務(wù)端實現(xiàn)
1.維護(hù)所有的在線用戶
2.注冊功能:客戶端名稱,添加到服務(wù)器的客戶端集合里
3.群聊功能:客戶端發(fā)送消息,所有的客戶端都能接收到
4.私聊功能:客戶端與指定客戶端進(jìn)發(fā)送和接收消息
5.退出功能: 從服務(wù)器客戶端集合中移除客戶端
3. 客戶端實現(xiàn)
1.注冊功能:創(chuàng)建Socket對象,給服務(wù)器發(fā)送注冊執(zhí)行(消息)
2.群聊功能:客戶端發(fā)送和接收數(shù)據(jù)
3.私聊功能:客戶端指定客戶端(用戶),發(fā)送和接收數(shù)據(jù)
4.退出功能:給服務(wù)器發(fā)送退出指令(消息)
5.命令行的交互式輸入輸出
4.實現(xiàn)思路:
首先,要實現(xiàn)服務(wù)端與客戶端之間的連接
這里是使用套接字建立TCP連接:
(1)服務(wù)器端先實例化一個描述服務(wù)器端口號的ServerSocket對象
(2)客戶端要創(chuàng)建Socket對象來連接指定的服務(wù)器端
(3)服務(wù)器端調(diào)用ServerSocket類的accept()方法來監(jiān)聽連接到服務(wù)器端的客戶端信息
(4)若服務(wù)器端與客戶端連接成功,雙方將返回一個Socket對象,此時雙方可以進(jìn)行通信
(5)服務(wù)器端與客戶端使用I/O流進(jìn)行連接,服務(wù)端的輸出流連接客戶端的輸入流,客戶端的輸出流連接服務(wù)端的輸入流
(6)使用close()方法關(guān)閉套接字(一定要記得關(guān)閉)
2.因為是擁有一個服務(wù)端來實現(xiàn)多個客戶端的連接,此處還要解決的是多線程的問題。
每個客戶端需要兩個線程,來分別處理向服務(wù)端發(fā)送消息和向服務(wù)端接收消息
而服務(wù)端,當(dāng)每增加一個客戶端與服務(wù)端連接,服務(wù)端都要多創(chuàng)建一個線程來處理與客戶端的連接
5. 圖解析
6. 服務(wù)端代碼實現(xiàn)
Server類
package test.Server; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * package:test.Server * Description:服務(wù)器端 * @date:2019/8/14 * @Author:weiwei **/ public class server { public static void main(String[] args) { try { int port = 6666; ServerSocket serverSocket = new ServerSocket(port); System.out.println("服務(wù)器啟動..." + serverSocket.getLocalSocketAddress()); //服務(wù)器啟動,打印本地地址 //線程池 ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); while (true) { //死循環(huán) Socket client = serverSocket.accept(); System.out.println("有客戶端連接到服務(wù)器:" + client.getRemoteSocketAddress()); executorService.execute(new HandlerClient(client)); } } catch (IOException e) { e.printStackTrace(); } } }
HandlerClient類
package test.Server; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import java.util.Map; import java.util.Scanner; import java.util.concurrent.ConcurrentHashMap; /** * Author:weiwei * description:HandlerClient * Creat:2019/3/12 **/ public class HandlerClient implements Runnable { /** * 維護(hù)所有的連接到服務(wù)端的客戶端對象 */ private static final Map<String,Socket> ONLINE_CLIENT_MAP = new ConcurrentHashMap<String, Socket>(); //靜態(tài)是為了不讓對象變化,final不讓對象被修改,ConcurrentHashMap是線程安全的類 //static final修飾后變量名應(yīng)該用常量--大寫字母加下劃線分隔 private final Socket client; public HandlerClient(Socket client) { //HandlerClient在多線程環(huán)境下調(diào)用,所以會產(chǎn)生資源競爭,用一個并發(fā)的HashMap this.client = client; //為了防止變量被修改,用final修飾 } //@Override public void run() { try { InputStream clientInput=client.getInputStream(); //獲取客戶端的數(shù)據(jù)流 Scanner scanner = new Scanner(clientInput); //字節(jié)流轉(zhuǎn)字符流 /** *消息是按行讀取 * 1.register:<username> 例如: register:張三 * 2.群聊: groupChat:<message> 例如:groupChat:大家好 * 3.私聊: privateChat:張三:你好,還錢 * 4.退出:bye */ while(true){ String data = scanner.nextLine(); //讀數(shù)據(jù),按行讀 if(data.startsWith("register:")){ //注冊 String userName = data.split(":")[1];//冒號分隔,取第一個 register(userName); continue; } if(data.startsWith("groupChat:")){ String message = data.split(":")[1]; groupChat(message); continue; } if(data.startsWith("privateChat:")){ String [] segments = data.split(":"); String targetUserName = segments[1].split("\\-")[0]; //取目標(biāo)用戶名 String message = segments[1].split("\\-")[1]; //因為要取兩次,所以用數(shù)組 //取發(fā)送的消息內(nèi)容 privateChat(targetUserName,message); continue; } if(data.equals("bye")){ //表示退出 bye(); continue; } } } catch (IOException e) { e.printStackTrace(); } } /** * 當(dāng)前客戶端退出 */ private void bye() { for(Map.Entry<String,Socket> entry : ONLINE_CLIENT_MAP.entrySet()){ Socket target = entry.getValue(); if(target.equals(this.client)){ //在在線用戶中找到自己并且移除 ONLINE_CLIENT_MAP.remove(entry.getKey()); break; } System.out.println(getCurrentUserName()+"退出聊天室"); } printOnlineClient();//打印當(dāng)前用戶 } private String getCurrentUserName(){ for (Map.Entry<String, Socket> entry : ONLINE_CLIENT_MAP.entrySet()) { Socket target = entry.getValue(); //getvalue得到Socket對象 if(target.equals(this.client)){ //排除群聊的時候自己給自己發(fā)消息的情況 return entry.getKey(); } } return ""; } /** * 私聊,給targetUserName發(fā)送message消息 * @param targetUserName * @param message */ private void privateChat(String targetUserName, String message) { Socket target = ONLINE_CLIENT_MAP.get(targetUserName);//獲取目標(biāo)用戶名 if(target == null){ this.sendMessage(this.client,"沒有這個人"+targetUserName,false); }else{ this.sendMessage(target,message,true); } } /** * 群聊,發(fā)送message * @param message */ private void groupChat(String message) { for (Map.Entry<String, Socket> entery : ONLINE_CLIENT_MAP.entrySet()) { Socket target = entery.getValue(); //getvalue得到Socket對象 if(target.equals(this.client)){ continue; //排除群聊的時候自己給自己發(fā)消息的情況 } this.sendMessage(target,message,true); } } /** * 以userName為key注冊當(dāng)前用戶(Socket client) * @param userName */ private void register(String userName) { if(ONLINE_CLIENT_MAP.containsKey(userName)){ this.sendMessage(this.client,"您已經(jīng)注冊過了,無需重復(fù)注冊",false); }else{ ONLINE_CLIENT_MAP.put(userName,this.client); printOnlineClient(); this.sendMessage(this.client,"恭喜"+userName+"注冊成功\n",false); } } private void sendMessage(Socket target,String message,boolean prefix){ OutputStream clientOutput = null; //value是每一個客戶端 try { clientOutput = target.getOutputStream(); OutputStreamWriter writer = new OutputStreamWriter(clientOutput); if(prefix) { String currentUserName = this.getCurrentUserName(); writer.write("<" + currentUserName + "說:>" + message + "\n"); }else{ writer.write( message + "\n"); } writer.flush(); } catch (IOException e) { e.printStackTrace(); } } /** * 打印在線客戶端 */ private void printOnlineClient(){ System.out.println("當(dāng)前在線人數(shù):"+ONLINE_CLIENT_MAP.size()+","+"用戶名如下列表:"); for(String userName : ONLINE_CLIENT_MAP.keySet()){ //Map的key為用戶名 System.out.println(userName); } } }
7. 客戶端代碼實現(xiàn)
Client類
package Cilent; import java.io.IOException; import java.net.Socket; /** * package:Cilent * Description:客戶端 * @date:2019/8/14 * @Author:weiwei **/ public class cilent { public static void main(String[] args) { try { //讀取地址 String host = "127.0.0.1"; //讀取端口號 int port = 6666; Socket client = new Socket(host,port); //先寫數(shù)據(jù)再讀數(shù)據(jù),讀寫線程分離 new ReadDataFromServerThread(client).start();//啟動讀線程 new WriteDataToServerThread(client).start();//啟動寫線程 } catch (IOException e) { e.printStackTrace(); } } }
WriteDateToServer類
package Cilent; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import java.util.Scanner; /** * Author:weiwei * description:客戶端給服務(wù)端發(fā)送數(shù)據(jù)的線程 * 發(fā)送的數(shù)據(jù)來自命令行的交互式輸入 * Creat:2019/3/12 **/ public class WriteDataToServerThread extends Thread{ private final Socket client; public WriteDataToServerThread(Socket client){ this.client = client; } @Override public void run(){ try { OutputStream clientOutput = this.client.getOutputStream(); OutputStreamWriter writer = new OutputStreamWriter(clientOutput); Scanner scanner = new Scanner(System.in); //有客戶端輸入數(shù)據(jù) while(true){ System.out.print("請輸入>>"); String data = scanner.nextLine(); //讀數(shù)據(jù) writer.write(data+"\n"); writer.flush(); if(data.equals("bye")){ System.out.println("您已下線..."); break; } } this.client.close(); } catch (IOException e) { // e.printStackTrace(); } } }
ReadDateFromServer類
package Cilent; import java.io.IOException; import java.io.InputStream; import java.net.Socket; import java.util.Scanner; /** * Author:weiwei * description:客戶端從服務(wù)端讀取數(shù)據(jù)的線程 * Creat:2019/3/12 **/ public class ReadDataFromServerThread extends Thread { private final Socket client; public ReadDataFromServerThread(Socket client){ this.client=client; } @Override public void run(){ try { InputStream clientInput = this.client.getInputStream(); Scanner scanner = new Scanner(clientInput); while(true){ String data = scanner.nextLine();//按行讀數(shù)據(jù) System.out.println("來自服務(wù)端消息:"+data); } } catch (IOException e) { e.printStackTrace(); } } }
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
淺談關(guān)于Mybatis的mapper-locations配置問題
MyBatis 是一款優(yōu)秀的半自動的ORM持久層框架,它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設(shè)置參數(shù)和獲取結(jié)果集的工作,需要的朋友可以參考下2023-05-05淺析Spring boot 中 logback 配置<springPropert
這篇文章主要介紹了淺析Spring boot 中 logback 配置<springProperty> 讀取application.properties 中的屬性,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-02-02java實現(xiàn)python session功能代碼實例
這篇文章主要介紹了java實現(xiàn)python session功能代碼實例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-11-11使用eclipse 實現(xiàn)將springboot項目打成jar包
這篇文章主要介紹了使用eclipse 實現(xiàn)將springboot項目打成jar包的流程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07MyBatis實現(xiàn)簡單的數(shù)據(jù)表分月存儲
本文主要介紹了MyBatis實現(xiàn)簡單的數(shù)據(jù)表分月存儲,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03