package Chat; import java.io.IOException; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.net.ServerSocket; import java.net.Socket; import java.util.Iterator; import java.util.Map; import java.util.Scanner; import java.util.Set; 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; //必須可以接收多個客戶端連接請求---多線程 //每當有一個客戶端連接到服務器後,就把socket包裝作爲一個線程處理,不同的線程在不同的子線程中處理,互不影響--線程池 //難點1.如何保存多個客戶端的連接 // 用Map實現,裏面有鍵值對----ConcurrentHashMap<> ()--保證多線程註冊時,用戶名一定只有一個,保證其安全 // 多個用戶能同時註冊到服務器,線程如果不安全,用戶名有可能重複,就導致之前註冊的用戶信息無效 public class MulThreadServer { //用來保存用戶的客戶端的列表 private static Map<String, Socket> clientMap = new ConcurrentHashMap<> (); //把socket包裝作爲一個線程,專門用來處理每個客戶端的輸入,輸出請求 private static class ExecuteClientRequest implements Runnable { //需要接收一個客戶端 private Socket client; public ExecuteClientRequest(Socket client) { this.client = client; } //具體處理每個客戶端的輸入輸出請求 //服務器的作用,數據的轉發 @Override public void run() { //獲取用戶輸入流,讀取用戶發來的信息 try { Scanner in = new Scanner (client.getInputStream ()); //從用戶發來的信息 String strFromClient = ""; //得不停的輸入輸出 while (true) { if (in.hasNext ()) { //獲取到她的輸入 strFromClient = in.nextLine (); //window下消除用戶輸入自帶的\r //正則表達式,Pattern--表示識別的時那種格式 //將\r替換爲空字符串 Pattern pattern = Pattern.compile ("\r"); Matcher matcher = pattern.matcher (strFromClient);//要替換的字符串放進去 strFromClient = matcher.replaceAll (""); } //根據輸入的內容,分析是羣聊信息還是私聊還是退出 //userName:wl------註冊---到我的服務器 if (strFromClient.startsWith ("userName")) { //1.拆出用戶名 String userName = strFromClient.split ("\\:")[1]; userRegister (userName, client); continue; } //G:----羣聊內容 if (strFromClient.startsWith ("G:")) { String fromUserName = null; for (String keyName : clientMap.keySet ()) { if (clientMap.get (keyName).equals (client)) { fromUserName = keyName; } } String groupMsg = strFromClient.split ("\\:")[1]; groupChat (fromUserName, groupMsg); continue; } //P:私聊--告訴給誰發--用戶名--私聊內容 if (strFromClient.startsWith ("P:")) { String fromUserName = null; for (String keyName : clientMap.keySet ()) { if (clientMap.get (keyName).equals (client)) { fromUserName = keyName; } } String[] thing = strFromClient.split ("\\:"); String toUserName = thing[1]; String privateMsg = thing[2]; privateChat (fromUserName, toUserName, privateMsg); continue; } //用戶退出:wl:exit if (strFromClient.contains ("exit")) { String userName = strFromClient.split ("\\:")[0]; useOffLine (userName); break; } } } catch (IOException e) { e.printStackTrace (); } } //1.註冊 private void userRegister(String userName, Socket client) throws IOException { //實際就是把信息放在Map中 clientMap.put (userName, client); //可以判斷一下用戶名是否存在,socket是否關閉 Set<Map.Entry<String, Socket>> clientEnter = clientMap.entrySet (); //2.取得迭代器Set Iterator<Map.Entry<String, Socket>> iterator = clientEnter.iterator (); System.out.println ("用戶" + userName + "註冊成功"); //3.遍歷 while (iterator.hasNext ()) { //取出每一個客戶端實體,就是取得了輸出流 Map.Entry<String, Socket> client1 = iterator.next (); //拿到客戶端輸出流輸出信息 try { PrintStream out = new PrintStream (client1.getValue () .getOutputStream (), true, "UTF-8"); out.println ("用戶" + userName + "上線了"); out.println ("當前聊天室人數爲:" + clientMap.size ()); } catch (IOException e) { e.printStackTrace (); } } } //2.獲取到羣聊信息 //羣聊方法:遍歷Map,向每個客戶端輸出一遍 private void groupChat(String fromUserName, String groupMsg) { //1.將Map變爲 Set<Map.Entry<String, Socket>> clientEnter = clientMap.entrySet (); //2.取得迭代器Set Iterator<Map.Entry<String, Socket>> iterator = clientEnter.iterator (); //3.遍歷 while (iterator.hasNext ()) { //取出每一個客戶端實體,就是取得了輸出流 Map.Entry<String, Socket> client = iterator.next (); //拿到客戶端輸出流輸出羣聊信息 try { PrintStream out = new PrintStream (client.getValue ().getOutputStream (), true, "UTF-8"); out.println ("用戶" + fromUserName + "發給你的羣聊信息爲" + groupMsg); } catch (UnsupportedEncodingException e) { e.printStackTrace (); } catch (IOException e) { e.printStackTrace (); } } } //3.私聊方法 private void privateChat(String fromUserName, String toUserName, String privateMsg) throws IOException { //.取出userName對應的Socket //Socket client = clientMap.get (toUserName); Socket privateSocket = clientMap.get (toUserName); PrintStream out = null; if (privateSocket == null) { try { out = new PrintStream (client.getOutputStream (), true, "UTF-8"); out.println ("沒有這個聯繫人"); return; } catch (IOException e) { e.printStackTrace (); } } //2.獲取輸出流 try { out = new PrintStream (privateSocket.getOutputStream (), true, "UTF-8"); out.println ("用戶" + fromUserName + "發給你的私聊信息爲" + privateMsg); } catch (IOException e) { e.printStackTrace (); } } //4.下線 private void useOffLine(String userName) { //1.將Map變爲 Set<Map.Entry<String, Socket>> clientEnter = clientMap .entrySet (); //2.取得迭代器Set Iterator<Map.Entry<String, Socket>> iterator = clientEnter.iterator (); //3.遍歷 while (iterator.hasNext ()) { //取出每一個客戶端實體,就是取得了輸出流 Map.Entry<String, Socket> client = iterator.next (); //拿到客戶端輸出流輸出羣聊信息 try { PrintStream out = new PrintStream (client.getValue () .getOutputStream (), true, "UTF-8"); //刪除Map中的用戶實體 clientMap .remove (userName); out.println ("用戶" + userName + "已下線"); out.println ("當前聊天室人數爲:" + clientMap .size ()); } catch (UnsupportedEncodingException e) { e.printStackTrace (); } catch (IOException e) { e.printStackTrace (); } } } } public static void main(String[] args) throws Exception { //1.建立基站 ServerSocket serverSocket = new ServerSocket (5210); //2.使用線程池來同時處理多個客戶端連接 //創建一個大小爲20的線程池來處理請求 ExecutorService executorService = Executors .newFixedThreadPool (20); System.out.println ("等待客戶端連接"); for (int i = 0; i < 20; i++) { Socket client = serverSocket.accept (); System.out.println ("有新的客戶端連接,端口號爲" + client.getPort ()); PrintStream out = new PrintStream (client .getOutputStream (), true, "UTF-8"); out.println ("服務器發來的介紹信息爲:" + "\n" + " “使用指南” " + "\n" + "1.第一步:註冊," + "使用userName:+<你想要給自己起的名字>" + "\n" + "2.第二步:發消息的使用方式" + "\n" + " 羣發消息:輸入:G:+<你想要羣發的內容>" + "\n" + " 私法消息:輸入:P:+<想要發的人的userName:+<你想要私發的內容>" + "\n" + " 退出聊天:輸入:你的用戶名:+exit"); //將收到客戶端信息傳入上面的方法中,提交一個任務 executorService.submit (new ExecuteClientRequest (client)); } //當多於20個線程時,關閉線程池與服務端 executorService.shutdown (); serverSocket.close (); } } ——————————————————————————————————————————————————————————————————————————————————————————————————————————————
package Chat; //基於多線程的客戶端 import java.io.IOException; import java.io.PrintStream; import java.net.Socket; import java.util.Scanner; //實現的功能:註冊。羣聊,私聊 //讀取服務器發來信息的線程 class ReadFromServer implements Runnable { private Socket client; //通過構造方法把共享的Socket客戶端傳進來,讀和寫是同一個Socket public ReadFromServer(Socket client) { this.client = client; } @Override public void run() { //獲取輸入流來取得服務器發來的信息 try { Scanner in = new Scanner (client.getInputStream ());//這裏只讀取了一次 while (true) {//進行多次讀取 if (client.isClosed ()) { System.out.println ("客戶端已關閉"); in.close (); break; } if (in.hasNext ()) { System.out.println ("服務器發來的信息爲:" + in.nextLine ()); } } } catch (IOException e) { e.printStackTrace (); } } } //向服務器發送信息線程 class SendMsgToServer implements Runnable{ private Socket client; //通過構造方法把共享的Socket客戶端傳進來,讀和寫是同一個Socket public SendMsgToServer(Socket client) { this.client = client; } @Override public void run() { //獲取輸出流,向服務器發送信息 try { PrintStream printStream=new PrintStream (client.getOutputStream (),true,"UTF-8"); //獲取用戶輸入,從鍵盤獲取 Scanner scanner=new Scanner (System.in); while(true){ //將用戶的輸入保存以下 String strFromUser=""; if(scanner.hasNext ()){ //獲取用戶 strFromUser=scanner.nextLine (); } System.out.println ("請輸入要向服務器發送的信息"); //不管輸出什麼,都應該將內容輸出到服務端 printStream.println (strFromUser); //判斷退出的條件,字符串包含exit if(strFromUser.contains ("exit")){ System.out.println ("您已退出聊天室"); scanner.close (); printStream.close (); client.close (); break; } } } catch (IOException e) { e.printStackTrace (); } } } public class MulThreadClient { public static void main(String[] args) throws IOException { //根據指定的IP和端口號建立連接 Socket client=new Socket ("127.0.0.1",5210); //啓動讀線 程與輸出線程 Thread readThread=new Thread (new ReadFromServer (client)); Thread sendThread= new Thread (new SendMsgToServer(client)); readThread.start (); sendThread.start (); } } ——————————————————————————————————————————————————————————————————————————————————————————————————————————————實驗結果:
客戶端:
等待客戶端連接
有新的客戶端連接,端口號爲43871
有新的客戶端連接,端口號爲43878
有新的客戶端連接,端口號爲43885
用戶1註冊成功
用戶2註冊成功
用戶3註冊成功
————————————————————————————————————————————————————————
用戶端1:
服務器發來的信息爲:服務器發來的介紹信息爲:
服務器發來的信息爲: “使用指南”
服務器發來的信息爲:1.第一步:註冊,使用userName:+<你想要給自己起的名字>
服務器發來的信息爲:2.第二步:發消息的使用方式
服務器發來的信息爲: 羣發消息:輸入:G:+<你想要羣發的內容>
服務器發來的信息爲: 私法消息:輸入:P:+<想要發的人的userName:+<你想要私發的內容>
服務器發來的信息爲: 退出聊天:輸入:你的用戶名:+exit
userName:1
請輸入要向服務器發送的信息
服務器發來的信息爲:用戶1上線了
服務器發來的信息爲:當前聊天室人數爲:1
服務器發來的信息爲:用戶2上線了
服務器發來的信息爲:當前聊天室人數爲:2
服務器發來的信息爲:用戶3上線了
服務器發來的信息爲:當前聊天室人數爲:3
G:你們好!
請輸入要向服務器發送的信息
服務器發來的信息爲:用戶1發給你的羣聊信息爲你們好!
————————————————————————————————————————————————————
用戶端2:
服務器發來的信息爲:服務器發來的介紹信息爲:
服務器發來的信息爲: “使用指南”
服務器發來的信息爲:1.第一步:註冊,使用userName:+<你想要給自己起的名字>
服務器發來的信息爲:2.第二步:發消息的使用方式
服務器發來的信息爲: 羣發消息:輸入:G:+<你想要羣發的內容>
服務器發來的信息爲: 私法消息:輸入:P:+<想要發的人的userName:+<你想要私發的內容>
服務器發來的信息爲: 退出聊天:輸入:你的用戶名:+exit
userName:2
請輸入要向服務器發送的信息
服務器發來的信息爲:用戶2上線了
服務器發來的信息爲:當前聊天室人數爲:2
服務器發來的信息爲:用戶3上線了
服務器發來的信息爲:當前聊天室人數爲:3
服務器發來的信息爲:用戶1發給你的羣聊信息爲你們好!
P:3:我的用戶名爲2,很開心認識你!
————————————————————————————————————————————————————————用戶端3:
服務器發來的信息爲:服務器發來的介紹信息爲:
服務器發來的信息爲: “使用指南”
服務器發來的信息爲:1.第一步:註冊,使用userName:+<你想要給自己起的名字>
服務器發來的信息爲:2.第二步:發消息的使用方式
服務器發來的信息爲: 羣發消息:輸入:G:+<你想要羣發的內容>
服務器發來的信息爲: 私法消息:輸入:P:+<想要發的人的userName:+<你想要私發的內容>
服務器發來的信息爲: 退出聊天:輸入:你的用戶名:+exit
userName:3
請輸入要向服務器發送的信息
服務器發來的信息爲:用戶3上線了
服務器發來的信息爲:當前聊天室人數爲:3
服務器發來的信息爲:用戶1發給你的羣聊信息爲你們好!
服務器發來的信息爲:用戶2發給你的私聊信息爲我的用戶名爲2,很開心認識你!