基于多線程與 Socket 實(shí)現(xiàn)的聊天系統(tǒng) v1.1(多線程、命令行、可記錄用戶信息)

image.png

image.png

image.png

image.png

服務(wù)端

public class Server
{
    private static final int SERVER_PORT = 30000;
    // 使用CrazyitMap對象來保存每個客戶名字和對應(yīng)輸出流之間的對應(yīng)關(guān)系。
    public static CrazyitMap<String , PrintStream> clients
        = new CrazyitMap<>();
    public void init()
    {
        try(
            // 建立監(jiān)聽的ServerSocket
            ServerSocket ss = new ServerSocket(SERVER_PORT))
        {
            // 采用死循環(huán)來不斷接受來自客戶端的請求
            while(true)
            {
                Socket socket = ss.accept();
                new ServerThread(socket).start();
            }
        }
        // 如果拋出異常
        catch (IOException ex)
        {
            System.out.println("服務(wù)器啟動失敗,是否端口"
                + SERVER_PORT + "已被占用?");
        }
    }
    public static void main(String[] args)
    {
        Server server = new Server();
        server.init();
    }
}

服務(wù)端實(shí)現(xiàn)

public class ServerThread extends Thread
{
    private Socket socket;
    BufferedReader br = null;
    PrintStream ps = null;
    // 定義一個構(gòu)造器,用于接收一個Socket來創(chuàng)建ServerThread線程
    public ServerThread(Socket socket)
    {
        this.socket = socket;
    }
    @Override
    public void run()
    {
        try
        {
            // 獲取該Socket對應(yīng)的輸入流
            br = new BufferedReader(new InputStreamReader(socket
                .getInputStream()));
            // 獲取該Socket對應(yīng)的輸出流
            ps = new PrintStream(socket.getOutputStream());
            String line = null;
            while((line = br.readLine())!= null)
            {
                // 如果讀到的行以CrazyitProtocol.USER_ROUND開始,并以其結(jié)束,
                // 可以確定讀到的是用戶登錄的用戶名
                if (line.startsWith(CrazyitProtocol.USER_ROUND)
                    && line.endsWith(CrazyitProtocol.USER_ROUND))
                {
                    // 得到真實(shí)消息
                    String userName = getRealMsg(line);
                    // 如果用戶名重復(fù)
                    if (Server.clients.map.containsKey(userName))
                    {
                        System.out.println("重復(fù)");
                        ps.println(CrazyitProtocol.NAME_REP);
                    }
                    else
                    {
                        System.out.println("成功");
                        ps.println(CrazyitProtocol.LOGIN_SUCCESS);
                        Server.clients.put(userName , ps);
                    }
                }
                // 如果讀到的行以CrazyitProtocol.PRIVATE_ROUND開始,并以其結(jié)束,
                // 可以確定是私聊信息,私聊信息只向特定的輸出流發(fā)送
                else if (line.startsWith(CrazyitProtocol.PRIVATE_ROUND)
                    && line.endsWith(CrazyitProtocol.PRIVATE_ROUND))
                {
                    // 得到真實(shí)消息
                    String userAndMsg = getRealMsg(line);
                    // 以SPLIT_SIGN分割字符串,前半是私聊用戶,后半是聊天信息
                    String user = userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[0];
                    String msg = userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[1];
                    // 獲取私聊用戶對應(yīng)的輸出流,并發(fā)送私聊信息
                    Server.clients.map.get(user).println(Server.clients
                        .getKeyByValue(ps) + "悄悄地對你說:" + msg);
                }
                // 公聊要向每個Socket發(fā)送
                else
                {
                    // 得到真實(shí)消息
                    String msg = getRealMsg(line);
                    // 遍歷clients中的每個輸出流
                    for (PrintStream clientPs : Server.clients.valueSet())
                    {
                        clientPs.println(Server.clients.getKeyByValue(ps)
                            + "說:" + msg);
                    }
                }
            }
        }
        // 捕捉到異常后,表明該Socket對應(yīng)的客戶端已經(jīng)出現(xiàn)了問題
        // 所以程序?qū)⑵鋵?yīng)的輸出流從Map中刪除
        catch (IOException e)
        {
            Server.clients.removeByValue(ps);
            System.out.println("剩余在線人數(shù)" + Server.clients.map.size());
            // 關(guān)閉網(wǎng)絡(luò)、IO資源
            try
            {
                if (br != null)
                {
                    br.close();
                }
                if (ps != null)
                {
                    ps.close();
                }
                if (socket != null)
                {
                    socket.close();
                }
            }
            catch (IOException ex)
            {
                ex.printStackTrace();
            }
        }
    }
    // 將讀到的內(nèi)容去掉前后的協(xié)議字符,恢復(fù)成真實(shí)數(shù)據(jù)
    private String getRealMsg(String line)
    {
        return line.substring(CrazyitProtocol.PROTOCOL_LEN
            , line.length() - CrazyitProtocol.PROTOCOL_LEN);
    }
}

客戶端

public class Client
{
    private static final int SERVER_PORT = 30000;
    private Socket socket;
    private PrintStream ps;
    private BufferedReader brServer;
    private BufferedReader keyIn;
    public void init()
    {
        try
        {
            // 初始化代表鍵盤的輸入流
            keyIn = new BufferedReader(
                new InputStreamReader(System.in));
            // 連接到服務(wù)器
            socket = new Socket("127.0.0.1", SERVER_PORT);
            // 獲取該Socket對應(yīng)的輸入流和輸出流
            ps = new PrintStream(socket.getOutputStream());
            brServer = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));
            String tip = "";
            // 采用循環(huán)不斷地彈出對話框要求輸入用戶名
            while(true)
            {
                String userName = JOptionPane.showInputDialog(tip
                    + "輸入用戶名");    //①
                // 將用戶輸入的用戶名的前后增加協(xié)議字符串后發(fā)送
                ps.println(CrazyitProtocol.USER_ROUND + userName
                    + CrazyitProtocol.USER_ROUND);
                // 讀取服務(wù)器的響應(yīng)
                String result = brServer.readLine();
                // 如果用戶重復(fù),開始下次循環(huán)
                if (result.equals(CrazyitProtocol.NAME_REP))
                {
                    tip = "用戶名重復(fù)!請重新";
                    continue;
                }
                // 如果服務(wù)器返回登錄成功,結(jié)束循環(huán)
                if (result.equals(CrazyitProtocol.LOGIN_SUCCESS))
                {
                    break;
                }
            }
        }
        // 捕捉到異常,關(guān)閉網(wǎng)絡(luò)資源,并退出該程序
        catch (UnknownHostException ex)
        {
            System.out.println("找不到遠(yuǎn)程服務(wù)器,請確定服務(wù)器已經(jīng)啟動!");
            closeRs();
            System.exit(1);
        }
        catch (IOException ex)
        {
            System.out.println("網(wǎng)絡(luò)異常!請重新登錄!");
            closeRs();
            System.exit(1);
        }
        // 以該Socket對應(yīng)的輸入流啟動ClientThread線程
        new ClientThread(brServer).start();
    }
    // 定義一個讀取鍵盤輸出,并向網(wǎng)絡(luò)發(fā)送的方法
    private void readAndSend()
    {
        try
        {
            // 不斷讀取鍵盤輸入
            String line = null;
            while((line = keyIn.readLine()) != null)
            {
                // 如果發(fā)送的信息中有冒號,且以//開頭,則認(rèn)為想發(fā)送私聊信息
                if (line.indexOf(":") > 0 && line.startsWith("http://"))
                {
                    line = line.substring(2);
                    ps.println(CrazyitProtocol.PRIVATE_ROUND +
                    line.split(":")[0] + CrazyitProtocol.SPLIT_SIGN
                        + line.split(":")[1] + CrazyitProtocol.PRIVATE_ROUND);
                }
                else
                {
                    ps.println(CrazyitProtocol.MSG_ROUND + line
                        + CrazyitProtocol.MSG_ROUND);
                }
            }
        }
        // 捕捉到異常,關(guān)閉網(wǎng)絡(luò)資源,并退出該程序
        catch (IOException ex)
        {
            System.out.println("網(wǎng)絡(luò)通信異常!請重新登錄!");
            closeRs();
            System.exit(1);
        }
    }
    // 關(guān)閉Socket、輸入流、輸出流的方法
    private void closeRs()
    {
        try
        {
            if (keyIn != null)
            {
                ps.close();
            }
            if (brServer != null)
            {
                ps.close();
            }
            if (ps != null)
            {
                ps.close();
            }
            if (socket != null)
            {
                keyIn.close();
            }
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
    }
    public static void main(String[] args)
    {
        Client client = new Client();
        client.init();
        client.readAndSend();
    }
}

客戶端實(shí)現(xiàn)

public class Client
{
    private static final int SERVER_PORT = 30000;
    private Socket socket;
    private PrintStream ps;
    private BufferedReader brServer;
    private BufferedReader keyIn;
    public void init()
    {
        try
        {
            // 初始化代表鍵盤的輸入流
            keyIn = new BufferedReader(
                new InputStreamReader(System.in));
            // 連接到服務(wù)器
            socket = new Socket("127.0.0.1", SERVER_PORT);
            // 獲取該Socket對應(yīng)的輸入流和輸出流
            ps = new PrintStream(socket.getOutputStream());
            brServer = new BufferedReader(
                new InputStreamReader(socket.getInputStream()));
            String tip = "";
            // 采用循環(huán)不斷地彈出對話框要求輸入用戶名
            while(true)
            {
                String userName = JOptionPane.showInputDialog(tip
                    + "輸入用戶名");    //①
                // 將用戶輸入的用戶名的前后增加協(xié)議字符串后發(fā)送
                ps.println(CrazyitProtocol.USER_ROUND + userName
                    + CrazyitProtocol.USER_ROUND);
                // 讀取服務(wù)器的響應(yīng)
                String result = brServer.readLine();
                // 如果用戶重復(fù),開始下次循環(huán)
                if (result.equals(CrazyitProtocol.NAME_REP))
                {
                    tip = "用戶名重復(fù)!請重新";
                    continue;
                }
                // 如果服務(wù)器返回登錄成功,結(jié)束循環(huán)
                if (result.equals(CrazyitProtocol.LOGIN_SUCCESS))
                {
                    break;
                }
            }
        }
        // 捕捉到異常,關(guān)閉網(wǎng)絡(luò)資源,并退出該程序
        catch (UnknownHostException ex)
        {
            System.out.println("找不到遠(yuǎn)程服務(wù)器,請確定服務(wù)器已經(jīng)啟動!");
            closeRs();
            System.exit(1);
        }
        catch (IOException ex)
        {
            System.out.println("網(wǎng)絡(luò)異常!請重新登錄!");
            closeRs();
            System.exit(1);
        }
        // 以該Socket對應(yīng)的輸入流啟動ClientThread線程
        new ClientThread(brServer).start();
    }
    // 定義一個讀取鍵盤輸出,并向網(wǎng)絡(luò)發(fā)送的方法
    private void readAndSend()
    {
        try
        {
            // 不斷讀取鍵盤輸入
            String line = null;
            while((line = keyIn.readLine()) != null)
            {
                // 如果發(fā)送的信息中有冒號,且以//開頭,則認(rèn)為想發(fā)送私聊信息
                if (line.indexOf(":") > 0 && line.startsWith("http://"))
                {
                    line = line.substring(2);
                    ps.println(CrazyitProtocol.PRIVATE_ROUND +
                    line.split(":")[0] + CrazyitProtocol.SPLIT_SIGN
                        + line.split(":")[1] + CrazyitProtocol.PRIVATE_ROUND);
                }
                else
                {
                    ps.println(CrazyitProtocol.MSG_ROUND + line
                        + CrazyitProtocol.MSG_ROUND);
                }
            }
        }
        // 捕捉到異常,關(guān)閉網(wǎng)絡(luò)資源,并退出該程序
        catch (IOException ex)
        {
            System.out.println("網(wǎng)絡(luò)通信異常!請重新登錄!");
            closeRs();
            System.exit(1);
        }
    }
    // 關(guān)閉Socket、輸入流、輸出流的方法
    private void closeRs()
    {
        try
        {
            if (keyIn != null)
            {
                ps.close();
            }
            if (brServer != null)
            {
                ps.close();
            }
            if (ps != null)
            {
                ps.close();
            }
            if (socket != null)
            {
                keyIn.close();
            }
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
    }
    public static void main(String[] args)
    {
        Client client = new Client();
        client.init();
        client.readAndSend();
    }
}

服務(wù)端封裝的 Map,為聊天室提供存儲用戶的功能

// 通過組合HashMap對象來實(shí)現(xiàn)CrazyitMap,CrazyitMap要求value也不可重復(fù)
public class CrazyitMap<K,V>
{
    // 創(chuàng)建一個線程安全的HashMap
    public Map<K ,V> map = Collections.synchronizedMap(new HashMap<K,V>());
    // 根據(jù)value來刪除指定項(xiàng)
    public synchronized void removeByValue(Object value)
    {
        for (Object key : map.keySet())
        {
            if (map.get(key) == value)
            {
                map.remove(key);
                break;
            }
        }
    }
    // 獲取所有value組成的Set集合
    public synchronized Set<V> valueSet()
    {
        Set<V> result = new HashSet<V>();
        // 將map中所有value添加到result集合中
        map.forEach((key , value) -> result.add(value));
        return result;
    }
    // 根據(jù)value查找key。
    public synchronized K getKeyByValue(V val)
    {
        // 遍歷所有key組成的集合
        for (K key : map.keySet())
        {
            // 如果指定key對應(yīng)的value與被搜索的value相同,則返回對應(yīng)的key
            if (map.get(key) == val || map.get(key).equals(val))
            {
                return key;
            }
        }
        return null;
    }
    // 實(shí)現(xiàn)put()方法,該方法不允許value重復(fù)
    public synchronized V put(K key,V value)
    {
        // 遍歷所有value組成的集合
        for (V val : valueSet() )
        {
            // 如果某個value與試圖放入集合的value相同
            // 則拋出一個RuntimeException異常
            if (val.equals(value)
                && val.hashCode()== value.hashCode())
            {
                throw new RuntimeException("MyMap實(shí)例中不允許有重復(fù)value!");
            }
        }
        return map.put(key , value);
    }
}

協(xié)議字符

public interface CrazyitProtocol
{
    // 定義協(xié)議字符串的長度
    int PROTOCOL_LEN = 2;
    // 下面是一些協(xié)議字符串,服務(wù)器和客戶端交換的信息
    // 都應(yīng)該在前、后添加這種特殊字符串。
    String MSG_ROUND = "§γ";
    String USER_ROUND = "∏∑";
    String LOGIN_SUCCESS = "1";
    String NAME_REP = "-1";
    String PRIVATE_ROUND = "★【";
    String SPLIT_SIGN = "※";
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內(nèi)容

  • 點(diǎn)擊查看原文 Web SDK 開發(fā)手冊 SDK 概述 網(wǎng)易云信 SDK 為 Web 應(yīng)用提供一個完善的 IM 系統(tǒng)...
    layjoy閱讀 13,853評論 0 15
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,130評論 1 32
  • 一、簡歷準(zhǔn)備 1、個人技能 (1)自定義控件、UI設(shè)計(jì)、常用動畫特效 自定義控件 ①為什么要自定義控件? Andr...
    lucas777閱讀 5,235評論 2 54
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,176評論 4 61
  • 今年的三八節(jié)恰逢二月二,龍鳳節(jié),大家似乎都很興奮!我早早起來修飾一下,還破天慌的帶了一副耳環(huán),因?yàn)槲覀円灿谢顒樱c...
    81c9dcfe55b5閱讀 354評論 0 0