Java Socket模擬實(shí)現(xiàn)聊天室
使用Java Socket模擬實(shí)現(xiàn)了一個聊天室,實(shí)現(xiàn)了基本的私聊以及群聊。分為服務(wù)器端和客戶端,下面我來介紹一下實(shí)現(xiàn)的步驟。
服務(wù)器端
服務(wù)器端是聊天室的核心所在,主要用來處理客戶端的請求,先來看一下服務(wù)器端的主方法:
public static void main(String[] args) { try { ExecutorService executorService = Executors.newFixedThreadPool(100);//最多容納100個客戶端聊天 ServerSocket serverSocket = new ServerSocket(6655);//監(jiān)聽6655號端口 for (int i = 0; i < 100; i++) { Socket client = serverSocket.accept(); System.out.println("有新的用戶連接 " + client.getInetAddress() + client.getPort()); executorService.execute(new ExecuteClientThread(client)); } executorService.shutdown(); serverSocket.close(); } catch (Exception e) { e.printStackTrace(); } }
首先我創(chuàng)建了一個固定大小為100的線程池,這個聊天室的實(shí)現(xiàn)是一個服務(wù)器線程對應(yīng)一個客戶端線程的,就是說線程池的大小就是最大的同時聊天的人數(shù)。服務(wù)器的執(zhí)行順序是這樣的:
1.監(jiān)聽端口,等待客戶端連接
2.如果有客戶端連接到監(jiān)聽的端口,那么通過accept()方法返回該客戶端的Socket,并且在線程池中啟動一個新的服務(wù)器線程用來與剛剛連接的客戶端"溝通"。
3.把接收到的客戶端的Socket構(gòu)造注入新啟動的服務(wù)器線程中,這樣服務(wù)器線程就可以獲取到客戶端對應(yīng)的流。
到這里,服務(wù)器已經(jīng)和客戶端連接成功了,我們現(xiàn)在來看一下服務(wù)器線程是如何處理客戶端的請求的,先上一段服務(wù)器代碼
private static Map<String, Socket> clientMap = new ConcurrentHashMap<>();//存儲所有的用戶信息 static class ExecuteClientThread implements Runnable { private Socket client;//每一個服務(wù)器線程對應(yīng)一個客戶端線程 ExecuteClientThread(Socket client) { this.client = client; } ......
代碼的第一行,創(chuàng)建了一個ConcurrentHashmap,這個map不是某個線程中的,而是服務(wù)器的static屬性,用來存儲所有客戶端的信息。因?yàn)榭蛻舳耸怯行彰?,有Socket的,所以采用K-value的模式來存儲,用戶名作為Key??紤]到線程安全的原因,采用了ConcurrentHashmap,保證了線程安全。
接下來就是剛剛構(gòu)造注入的、連接的客戶端的Socket了,我們可以通過這個Socket獲取到輸入和輸出流。
然后就是服務(wù)器的線程執(zhí)行的run方法了,具體的就直接看代碼把。都有注釋,就不一一解釋了,以下是所有服務(wù)器端的代碼
import java.io.IOException; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { private static Map<String, Socket> clientMap = new ConcurrentHashMap<>();//存儲所有的用戶信息 static class ExecuteClientThread implements Runnable { private Socket client;//每一個服務(wù)器線程對應(yīng)一個客戶端線程 ExecuteClientThread(Socket client) { this.client = client; } @Override public void run() { boolean Flag = true;//防止一個客戶端多次注冊所做的標(biāo)記位置 try { PrintStream PrintToCilent = new PrintStream(client.getOutputStream());//服務(wù)器向用戶輸出一些提示信息 Scanner scanner = new Scanner(client.getInputStream()); String str = null;//用戶外部的輸入信息 while (true) { if (scanner.hasNext()) { str = scanner.next();//外部的用戶輸出 Pattern pattern = Pattern.compile("\r");//排除特殊符號 Matcher matcher = pattern.matcher(str); str = matcher.replaceAll(""); if (str.startsWith("userName")) { String userName = str.split(":")[1]; userRegist(userName, client, Flag); Flag = false; } // 群聊流程 else if (str.startsWith("G:")) { PrintToCilent.println("已進(jìn)入群聊模式!"); groupChat(scanner,client); } // 私聊流程 else if (str.startsWith("P")) {//模式 String userName = str.split("-")[1]; PrintToCilent.println("已經(jīng)進(jìn)入與"+userName+"的私聊"); privateChat(scanner,userName); } // 用戶退出 else if (str.contains("byebye")) { String userName = null; for (String getKey:clientMap.keySet()) { if (clientMap.get(getKey).equals(client)) { userName = getKey; } } System.out.println("用戶"+userName+"下線了.."); clientMap.remove(userName);//將此實(shí)例從map中移除 } } } } catch (IOException e) { e.printStackTrace(); } } private void userRegist(String userName, Socket client, boolean Flag) throws IOException { PrintStream PrintToCilent = new PrintStream(client.getOutputStream());//服務(wù)器向用戶輸出一些提示信息 if(Flag) { System.out.println("用戶" + userName + "上線了!"); clientMap.put(userName, client);//把用戶加入儲存map System.out.println("當(dāng)前群聊人數(shù)為" + (clientMap.size()) + "人"); PrintToCilent.println("注冊成功!"); }else { PrintToCilent.println("警告:一個客戶端只能注冊一個用戶!"); } } private void groupChat(Scanner scanner,Socket client) throws IOException { // 取出clientMap中所有客戶端Socket,然后遍歷一遍 // 分別取得每個Socket的輸出流向每個客戶端輸出 PrintStream PrintToClient = new PrintStream(client.getOutputStream());//在群聊的時候服務(wù)器向客戶端發(fā)送數(shù)據(jù) boolean ExitFlag = false; Set<Map.Entry<String, Socket>> entrySet = clientMap.entrySet(); String userName = null; for (Map.Entry<String, Socket> socketEntry : entrySet) {//獲得:是哪個用戶說的話 if (socketEntry.getValue() == client) { userName = socketEntry.getKey();//發(fā)出信息的用戶 } } String msg = null; while (true) { if (scanner.hasNext()) { msg = scanner.next(); if("exit".equals(msg)){//如果用戶退出了 for(Map.Entry<String,Socket> stringSocketEntry : entrySet){ new PrintStream(stringSocketEntry.getValue().getOutputStream(),true).println("用戶"+userName+"剛剛退出了群聊??!");//給所有人發(fā)退出群聊的消息 } return; } for (Map.Entry<String, Socket> stringSocketEntry : entrySet) {//遍歷用戶的map,獲取所有用戶的Socket try { Socket socket = stringSocketEntry.getValue(); PrintStream ps = new PrintStream(socket.getOutputStream(), true); ps.println("群聊:用戶" + userName + "說: " + msg);//給每個用戶發(fā)消息 } catch (IOException e) { e.printStackTrace(); } } } } } private void privateChat(Scanner scanner, String privatepeopleName) throws IOException { Socket privateUser = clientMap.get(privatepeopleName); PrintStream ps = new PrintStream(privateUser.getOutputStream());//拿到私聊對象的輸出流 PrintStream PrintToClient = new PrintStream(client.getOutputStream());//拿到當(dāng)前客戶端的輸出流 String Message = null; String MyName = null; Set<Map.Entry<String,Socket>> set = clientMap.entrySet(); for(Map.Entry<String,Socket> value : set){ if(value.getValue() == client){ MyName = value.getKey(); break; } } while (true) { if(scanner.hasNext()) { Message = scanner.next(); if ("exit".equals(Message)){//如果用戶輸入了退出 PrintToClient.println("已退出和"+privatepeopleName+"的私聊"); ps.println("對方已經(jīng)退出了私聊"); break; } ps.println(MyName+"說"+Message);//如果用戶沒有退出,向私聊對象發(fā)送消息 } } } } public static void main(String[] args) { try { ExecutorService executorService = Executors.newFixedThreadPool(100);//最多容納100個客戶端聊天 ServerSocket serverSocket = new ServerSocket(6655); for (int i = 0; i < 100; i++) { Socket client = serverSocket.accept(); System.out.println("有新的用戶連接 " + client.getInetAddress() + client.getPort()); executorService.execute(new ExecuteClientThread(client)); } executorService.shutdown(); serverSocket.close(); } catch (Exception e) { e.printStackTrace(); } } }
然后是客戶端的代碼,客戶端的代碼比較簡單:分為兩個線程,一個線程用于接收服務(wù)器的數(shù)據(jù),一個線程用于向服務(wù)器發(fā)送數(shù)據(jù)。我就直接上代碼了,里面有注釋的。
import java.io.IOException; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; class ExcuteServerInPut implements Runnable{//接收服務(wù)器的數(shù)據(jù) private Socket ToServer; ExcuteServerInPut(Socket ToServer){ this.ToServer = ToServer; } @Override public void run() { try { Scanner scanner = new Scanner(ToServer.getInputStream()); while (scanner.hasNext()){ System.out.println(scanner.nextLine()); } scanner.close(); ToServer.close(); } catch (IOException e) { e.printStackTrace(); } } } class ExcuteServerOutPut implements Runnable{//向服務(wù)器發(fā)送數(shù)據(jù) private Socket Socket; ExcuteServerOutPut(Socket Socket){ this.Socket = Socket; } @Override public void run() { try { PrintStream printStream = new PrintStream(Socket.getOutputStream()); Scanner scanner = new Scanner(System.in); scanner.useDelimiter("\n"); System.out.println("*****************************************"); System.out.println("***用戶注冊:useerName:同戶名(僅限一次)***"); System.out.println("***進(jìn)入群聊:G: 退出群聊:exit***"); System.out.println("***私聊:P-用戶名 退出私聊:exit***"); System.out.println("***********退出聊天室:byebye*************"); while (true){ if(scanner.hasNext()) { String string = scanner.next(); printStream.println(string); if ("byebye".equals(string)) { System.out.println("退出!"); printStream.close(); scanner.close(); break; } } } Socket.close(); } catch (IOException e) { e.printStackTrace(); } } } public class Main { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 6655); ExcuteServerInPut excuteServerInPut = new ExcuteServerInPut(socket); ExcuteServerOutPut excuteServerOutPut = new ExcuteServerOutPut(socket); new Thread(excuteServerInPut).start(); new Thread(excuteServerOutPut).start(); } }
后續(xù)我會做一些改進(jìn),希望可以對大家有所幫助
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- java+socket實(shí)現(xiàn)簡易局域網(wǎng)聊天室
- Java Socket實(shí)現(xiàn)聊天室附1500行源代碼
- java實(shí)現(xiàn)多人聊天工具(socket+多線程)
- java課程設(shè)計(jì)做一個多人聊天室(socket+多線程)
- Java Socket+多線程實(shí)現(xiàn)多人聊天室功能
- Java Socket實(shí)現(xiàn)多人聊天系統(tǒng)
- Java通過Socket實(shí)現(xiàn)簡單多人聊天室
- Java Socket實(shí)現(xiàn)簡易聊天室
- Java socket通信模擬QQ實(shí)現(xiàn)多人聊天室
相關(guān)文章
基于Tomcat7、Java、WebSocket的服務(wù)器推送聊天室實(shí)例
HTML5 WebSocket實(shí)現(xiàn)了服務(wù)器與瀏覽器的雙向通訊,本篇文章主要介紹了基于Tomcat7、Java、WebSocket的服務(wù)器推送聊天室實(shí)例,具有一定的參考價值,有興趣的可以了解一下。2016-12-12java 內(nèi)部類(匿名類,匿名對象,靜態(tài)內(nèi)部類)詳解及實(shí)例
這篇文章主要介紹了java 內(nèi)部類詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-12-12Maven創(chuàng)建項(xiàng)目過慢的4種解決辦法
最近經(jīng)常會遇到一個困擾,那就是用idea創(chuàng)建maven項(xiàng)目時,速度很慢,本文就來介紹一下Maven創(chuàng)建項(xiàng)目過慢的4種解決辦法,感興趣的可以了解一下2021-12-12Mybatis 實(shí)現(xiàn)打印sql語句的代碼
這篇文章主要介紹了Mybatis 實(shí)現(xiàn)打印sql語句的代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07Java 獲取當(dāng)前系統(tǒng)時間的三種方法
這篇文章主要介紹了Java 獲取當(dāng)前系統(tǒng)時間的三種方法,幫助大家利用Java處理時間,感興趣的朋友可以了解下2020-10-10基于String不可變字符與StringBuilder可變字符的效率問題
這篇文章主要介紹了String不可變字符與StringBuilder可變字符的效率問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07JMagick實(shí)現(xiàn)基本圖像處理的類實(shí)例
這篇文章主要介紹了JMagick實(shí)現(xiàn)基本圖像處理的類,實(shí)例分析了java圖像處理的相關(guān)技巧,需要的朋友可以參考下2015-06-06