聊天寶(升級版)

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,很開心認識你!

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章