国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

這篇具有很好參考價值的文章主要介紹了java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

目錄

前言

一、拾枝雜談

? ? ? ? 1.項目開發(fā)大體流程 :?

? ? ? ? 2.多用戶即時通信系統(tǒng)分析 :?

? ? ? ? ? ? ? ? 1° 需求分析

? ? ? ? ? ? ? ? 2° 整體分析

二、用戶登錄

? ? ? ? 1.準備工作?:?

????????2.客戶端 :?

????????????????1° 菜單界面

? ? ? ? ? ? ? ? 2° 登錄驗證

? ? ? ? ? ? ? ? 3° 線程創(chuàng)建

? ? ? ? ? ? ? ? 4° 線程管理

? ? ? ? 3.服務端 :?

? ? ? ? ? ? ? ? 1° 用戶驗證

? ? ? ? ? ? ? ? 2° 線程創(chuàng)建

? ? ? ? ? ? ? ? 3° 線程管理

? ? ? ? 4.登錄測試?:?

三、在線列表

? ? ? ? 1.擴充MessageType中的類型?:?

? ? ? ? 2.擴充UserClientService類中的方法 :?

? ? ? ? 3.擴充客戶端線程類中的內(nèi)容 :?

????????4.擴充ControlServerConnectClientThread類中的方法 :?

? ? ? ? 5.擴充服務端線程類中的內(nèi)容 :?

? ? ? ? 6.拉取測試 :?

四、退出系統(tǒng)

? ? ? ? 1.需要解決的問題 :?

? ? ? ? 2.解決辦法 :?

? ? ? ? ? ? ? ? 1° 總思路

? ? ? ? ? ? ? ? 2° 客戶端

????????????????3° 服務端

五、私聊群聊

? ? ? ? 1.私發(fā)消息 :?

? ? ? ? ? ? ? ? 1° 思路分析

? ? ? ? ? ? ? ? 2° 代碼實現(xiàn)

? ? ? ? ? ? ? ? 3° 運行測試?

? ? ? ? 2.群發(fā)消息 :?

? ? ? ? ? ? ? ? 1° 客戶端?

? ? ? ? ? ? ? ? 2° 服務端?

? ? ? ? ? ? ? ? 3° 運行測試?

六、傳輸文件

? ? ? ? 1.思路分析 :?

? ? ? ? 2.客戶端 :?

????????3.服務端 :?

????????4.運行測試 :?

七、最終代碼

? ? ? ? 1.客戶端 :?

? ? ? ? ? ? ? ? 1° View

? ? ? ? ? ? ? ? 2°?UserClientService

? ? ? ? ? ? ? ? 3°?MessageClientService

? ? ? ? ? ? ? ? 4°?FileClientService

? ? ? ? ? ? ? ? 5°?ClientConnectServiceThread

? ? ? ? ? ? ? ? 6°?ControlClientConnectServiceThread

? ? ? ? 2.服務端 :?

? ? ? ? ? ? ? ? 1°?ChatServer

? ? ? ? ? ? ? ? 2°?ServerConnectClientThread

? ? ? ? ? ? ? ? 3°?ControlServerConnectClientThread

? ? ? ? ? ? ? ? 4°?ChatFrame

? ? ? ? 3.公共部分 :?

? ? ? ? ? ? ? ? 1° Message

? ? ? ? ? ? ? ? 2° MessageType

? ? ? ? ? ? ? ? 3° User


前言

? ? ? ? 本篇博文適合javaSE基礎較為扎實的小伙伴兒們閱讀,up會從實現(xiàn)層面和大家分享一個多用戶即時通信系統(tǒng),類似于QQ,微信這種可以實現(xiàn)登錄,聊天,發(fā)文件,下線等功能的程序。但是聲明一點,該多用戶即時通信系統(tǒng)不是項目(up之后會專門開新的專欄來出項目),而只是對已學的java知識的聯(lián)系和應用,可以理解為一個模擬項目,主要涉及到oop,集合IO流,多線程,網(wǎng)絡編程等內(nèi)容。如果你想進一步鞏固自己的java基礎,這篇博文或許會是很好的選擇。感謝閱讀!

一、拾枝雜談

? ? ? ? 1.項目開發(fā)大體流程 :?

? ? ? ? ①分析階段 : 需求分析師會從“技術實現(xiàn)”和“行業(yè)情況”兩方面綜合考慮,出一個需求分析報告(通常是白皮書),包含客戶的具體要求以及項目最終要實現(xiàn)的功能。需求分析在整個項目開發(fā)流程中所占用的時間和資源——往往與項目本身的大小成正比。

? ? ? ? ②設計階段 : 主要是架構師和項目經(jīng)理攬活兒,有些公司會將二者合并。架構師/項目經(jīng)理需要負責項目的設計工作(UML類圖,流程圖,模塊設計,數(shù)據(jù)庫,項目架構);并且要完成項目的原型開發(fā)——先在虛擬機上跑出一個預覽的項目效果(不過多考慮性能),與客戶進行對接,簽訂合約。一切就緒后,架構師/項目經(jīng)理就會在公司的各個部門物色人選;比方說,當前項目是用java來實現(xiàn)的,架構師/項目經(jīng)理就會挑選java技術牛逼的??。因此,有些時候會出現(xiàn)一個??同時在兩個甚至多個項目組的情況,這時候這只牛逼的??會很忙,但是卻痛并快樂著,因為它可以領到double甚至是multiple的工資(??們的工資往往由基本工資 + 項目提成構成),設計階段在整個項目流程中所占用的時間往往比分析階段短一些,但依然與項目本身的大小成正比。

? ? ? ? ③實現(xiàn)階段 : 不多解釋,??兒們的主場。??兒們要負責把架構師/項目經(jīng)理給的模塊功能進行一一實現(xiàn),完事兒后在自己run一run,看看自己負責的代碼有沒有bug。實現(xiàn)階段在整個項目流程中所占的比重和項目本身成反比,即項目越大,實現(xiàn)階段反而不如需求階段和分析階段重要。但是,實際情況是,小公司小項目的實現(xiàn)階段往往是占比最大的一個,而且還會出現(xiàn)一邊實現(xiàn)一邊改需求的情況,即設計階段和實現(xiàn)階段纏一塊兒了。

? ? ? ? ④測試階段 : 測試工程師,??兒們的天敵;負責把??兒們的代碼拿來做各種測試,例如黑白盒測試,集成測試,單元測試等;因此,測試工程師與開發(fā)工程師往往打成一片,不可開交。在測試階段,最怕的事情就是——高耦合性的代碼出現(xiàn)了bug。

? ? ? ? ⑤實施階段 : 實施工程師,需要將項目正確地部署到客戶的平臺,并保證其運行正常,需要有較強的開發(fā)能力和環(huán)境配置能力,以及較好的身體素質??蛻舻钠脚_可能部署在不同的省市,甚至國家,因此實施工程師需要東奔西走,把每個平臺的服務器,操作系統(tǒng),環(huán)境配置等問題都給搞定。打個比方,小公司的實施工程師——使命召喚;大公司的實施工程師——塞爾達傳說。

? ? ? ? ⑥維護階段 : 解決程序后期出現(xiàn)的bug,解決項目升級相關的問題。大公司——運維工程師;小公司——背鍋俠。

? ? ? ? 2.多用戶即時通信系統(tǒng)分析 :?

? ? ? ? ? ? ? ? 1° 需求分析

? ? ? ? ①用戶登錄????????

? ? ? ? ②在線檢測? ? ? ? (拉取在線用戶列表)

? ? ? ? ③退出系統(tǒng)? ? ? ? (客戶端與服務器端)

? ? ? ? ④私聊群聊? ? ? ? (實現(xiàn)單發(fā)和群發(fā)消息)

? ? ? ? ⑤傳送文件???????

? ? ? ? ? ? ? ? 2° 整體分析

? ? ? ??Δ對于服務端——

? ? ? ? 服務端上往往提供了不同的服務,因此服務端需要通過ServerSocket來監(jiān)聽不同的端口;

? ? ? ? 每當有客戶端成功連接到服務端,都會獲得一個Socket對象;此時,啟動一個線程,并令該線程持有Socket對象,即令每個線程都操縱一個自己的Socket對象,此舉可以實現(xiàn)消息的群發(fā);

? ? ? ? 可以使用HashMap集合來管理服務端的多個線程。

? ? ? ? Δ對于客戶端——

? ? ? ?客戶端采用對象的形式來與服務端進行通訊,此舉可以發(fā)送更多的信息;可以使用對象處理流 ObjectInputStream和ObjectOutputStream來進行數(shù)據(jù)的讀取。

? ? ? ? 當客戶端成功根據(jù)"IP + 端口"成功連接到服務端后,客戶端獲得自己的Socket對象;此時,類似地,也啟動一個線程,并令該線程持有Socket對象。

? ? ? ? 同樣使用HashMap集合來管理客戶端的多個線程。

? ? ? ? Δ如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? User對象可以驗證是否為合法的登錄用戶;Message對象則包含了要傳輸?shù)男畔?/span>。


二、用戶登錄

? ? ? ? 1.準備工作?:?

? ? ? ? ? ? ? ? 在IDEA中創(chuàng)建一個新項目“ChatServer”,用來模擬通信系統(tǒng)的服務端;并另建一個新項目“ChatClient_0”用來模擬通信系統(tǒng)的一個客戶端;如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 在服務端(ChatServer)項目中,src包下,創(chuàng)建一個mutual包,表示服務端和客戶端共有的內(nèi)容(用戶信息和發(fā)送的消息)。在mutual包下創(chuàng)建User類,并令其實現(xiàn)Serializable接口;實現(xiàn)Serializable接口后User對象就可以序列化,進行網(wǎng)絡傳輸,就可以被對象處理流操作。User類中定義用戶名和用戶密碼兩個屬性。User類代碼如下 :?

package mutual;

import java.io.Serializable;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @meaning : The shared User between Server and Client
 */
public class User implements Serializable {
    private static final long serialVersionUID = 1L;    //增強兼容性
    private String id;
    private String pwd;

    public User() {}
    public User(String id, String pwd) {
        this.id = id;
        this.pwd = pwd;
    }

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }

    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}

? ? ? ? ? ? ? ? 在mutual包下創(chuàng)建Message類,表示傳輸?shù)南㈩愋停⒘钇?strong>實現(xiàn)Serializable接口;Message類中應該包括消息的發(fā)送者,消息的接收者,消息的類型等屬性,這樣服務端解包后才知道這消息是發(fā)給誰的,以及消息的具體內(nèi)容是什么。Message類代碼如下 :?

package mutual;

import java.io.Serializable;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @message : information that are transmitted
 */
public class Message implements Serializable {
    private static final long serialVersionUID = 1L;    //增強兼容性
    private String sendTime;    //發(fā)送時間
    private String sender;      //發(fā)送者
    private String receiver;    //接收者
    private String content;     //消息內(nèi)容
    private String mesType;     //消息類型

    public String getSendTime() {
        return sendTime;
    }

    public void setSendTime(String sendTime) {
        this.sendTime = sendTime;
    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getReceiver() {
        return receiver;
    }

    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getMesType() {
        return mesType;
    }

    public void setMesType(String mesType) {
        this.mesType = mesType;
    }
}

? ? ? ? ? ? ? ? 還需要確定Message內(nèi)容的具體類型,可以定義MessageType接口,在接口中定義不同的常量,以表示不同的消息類型;MessageType接口代碼如下 :?

package mutual;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @meaning : Types of message
 */
public interface MessageType {
    //定義常量
    String MESSAGE_LOGIN_SUCCESS = "1";     //表示登錄成功
    String MESSAGE_LOGIN_FAIL = "0";        //表示登錄失敗
}

? ? ? ? ? ? ? ? 最后,將mutual包拷貝一份到客戶端,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

????????2.客戶端 :?

????????????????1° 菜單界面

? ? ? ? ? ? ? ? 在客戶端(ChatClient_0)新建一個包client,用戶存放用戶相關的類;在client包下,另建一個包menu,用于菜單的界面顯示。在menu包下新建一個View類,View類代碼如下 :?

package client.menu;

import client.service.UserClientService;
import java.io.IOException;
import java.util.Scanner;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 菜單界面的顯示
 * @PS : Run -> Edit Configurations -> Modify options -> allow multiple instances
 */
public class View {
    private boolean loop = true;        //控制是否顯示菜單
    private String key = "";            //接收用戶的鍵盤輸入
    private static Scanner sc = new Scanner(System.in);
    /*
        將UserClientService對象置為屬性,
        該對象用于執(zhí)行用戶登錄/注冊等操作(該步驟將功能與界面聯(lián)系起來)
     */
    private UserClientService userClientService = new UserClientService();

    public static void main(String[] args) throws IOException {
        new View().mainMenu();
        System.out.println("客戶端退出...");
    }

    private void mainMenu() throws IOException {
        while (loop) {
            System.out.println("===========Welcome to the system of chat:===========");
            System.out.println("\t\t1.登錄系統(tǒng)");
            System.out.println("\t\t9.退出系統(tǒng)");


            System.out.print("請輸入你的選擇:");
            key = sc.nextLine();

            switch (key) {
                case "1" :
                    //登錄操作
                    System.out.print("請輸入用戶名:");
                    String userID = sc.nextLine();
                    System.out.print("請輸入密  碼:");
                    String password = sc.nextLine();

                    //驗證登錄的用戶是否合法(封裝思想)
                    if (userClientService.check(userID, password)) {     //驗證成功
                        System.out.println("\n===========Welcome user " + userID + "===========");
                        //向用戶顯示二級菜單
                        while (loop) {
                            System.out.println("\n===========網(wǎng)絡通信系統(tǒng)二級菜單(user:" + userID + ")===========");
                            System.out.println("\t\t1.在線列表:");
                            System.out.println("\t\t2.群發(fā)消息:");
                            System.out.println("\t\t3.私發(fā)消息:");
                            System.out.println("\t\t4.文件發(fā)送:");
                            System.out.println("\t\t9.退出系統(tǒng):");

                            System.out.print("請輸入你的選擇:");
                            key = sc.nextLine();

                            switch (key) {
                                case "1" :
                                    System.out.println(1);
                                    break;
                                case "2" :
                                    System.out.println(2);
                                    break;
                                case "3" :
                                    System.out.println(3);
                                    break;
                                case "4" :
                                    System.out.println(4);
                                    break;
                                case "9" :
                                    loop = false;   //在二級菜單中用戶也可以直接選擇退出系統(tǒng)
                            }
                        }
                    } else {        //驗證失敗
                        System.out.println("登錄失??!請重新嘗試!");
                    }

                    break;
                case "9" :
                    sc.close();
                    loop = false;       //將控制while循環(huán)的布爾變量設置為false
                    break;
            }

        }
    }
}

? ? ? ? ? ? ? ? 為了實現(xiàn)多用戶登錄,需要對View類進行配置依次點擊Run -> Edit Configurations -> Modify options -> allow multiple instances,允許并行,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 2° 登錄驗證

? ? ? ? ? ? ? ? View類中有關“用戶登錄驗證”部分的代碼,利用封裝的思想,將其封裝到client.service包下的類UserClientService中,UserClientService類代碼如下 :?

package client.service;

import mutual.Message;
import mutual.MessageType;
import mutual.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 登錄驗證
 */
public class UserClientService {
    /*
        將User對象設置成一個屬性,可利用getter和setter修改User對象的引用,便于操作。
        Socket對象同樣也可能在其他類中使用,因此也設置為屬性。
     */
    private User user = new User();
    private Socket socket;

    public boolean check(String userID, String password) throws IOException {
        //局部變量
        boolean b = false;
        //初始化User對象
        user.setId(userID);
        user.setPwd(password);

        //向服務端發(fā)送信息
        try {
            //1.獲取Socket對象
            socket = new Socket(InetAddress.getByName("127.0.0.1"), 8888);
            //2.獲取與Socket對象相關聯(lián)的對象處理流(輸出流)
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            //3.序列化User對象,寫入數(shù)據(jù)通道(向服務端發(fā)送一個User對象,服務端會對這個User對象進行驗證)
            oos.writeObject(user);
            //.........

            //4.獲取與Socket對象相關聯(lián)的對象處理流(輸入流)
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            //5.讀取服務端傳輸過來的Message對象
            Message message = (Message) ois.readObject();   //類型強轉

            if (message.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCESS)) {
                //創(chuàng)建線程對象(目的是為了與服務端保持通訊)
                ClientConnectServiceThread ccst = new ClientConnectServiceThread(socket);
                //啟動線程
                ccst.start();
                //將線程放入集合中統(tǒng)一管理
                ControlClientConnectServiceThread.addClientConnectServiceThread(userID, ccst);
                b = true;
            } else {
                //如果沒有啟動線程,關閉Socket對象。
                socket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } 

        return b;
    }
}

? ? ? ? ? ? ? ? 3° 線程創(chuàng)建

? ? ? ? ? ? ? ? 為了保持通訊,需要讓線程持有Socket對象;同時,利用HashMap集合來管理多個線程。UserClientService類中有關線程的部分,同樣新建一個類ClientConnectServiceThread,在client.service包下,ClientConnectServiceThread類代碼如下 :?

package client.service;

import mutual.Message;

import java.io.ObjectInputStream;
import java.net.Socket;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 客戶端用于和服務端進行通訊的線程
 */
public class ClientConnectServiceThread extends Thread{
    //該線程需要持有Socket對象
    private Socket socket;

    public ClientConnectServiceThread(Socket socket) {
        this.socket = socket;
    }

    public Socket getSocket() {
        return socket;
    }

    @Override
    public void run() {
        //∵Thread需要在后臺與服務器通信,因此使用while循環(huán)
        while (true) {
            try {
                System.out.println("客戶端線程,等待讀取來自服務器端的消息...");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                /*
                    如果服務端沒有發(fā)送Message對象到數(shù)據(jù)通道中,線程就會阻塞在這里。
                 */
                Message message = (Message) ois.readObject();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

? ? ? ? ? ? ? ? 4° 線程管理

? ? ? ? ? ? ? ? UserClientService類中涉及到線程“管理”,將相關代碼進行封裝,在client.service包下新建一個ControlClientConnectServiceThread類,代碼如下 :?

package client.service;

import java.util.HashMap;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 管理客戶端的線程
 */
public class ControlClientConnectServiceThread {
    /*
        使用HashMap類來管理多個線程(模擬數(shù)據(jù)庫),key表示用戶的id,value表示線程。
     */
    private static HashMap<String, ClientConnectServiceThread> hashMap = new HashMap<>();

    //添加線程的方法
    public static void addClientConnectServiceThread(String userID, ClientConnectServiceThread ccst) {
        hashMap.put(userID, ccst);
    }

    //取出線程的方法
    public static ClientConnectServiceThread getClientConnectServiceThread(String userID) {
        return hashMap.get(userID);
    }
}

? ? ? ? 3.服務端 :?

? ? ? ? ? ? ? ? 1° 用戶驗證

? ? ? ? ? ? ? ? 服務端的代碼與客戶端類似,都需要創(chuàng)建一個類用于讀取數(shù)據(jù)通道中的數(shù)據(jù);還需要一個線程類來持有Socket對象;最后就是一個類來管理服務端的線程。
????????????????在ChatServer包下創(chuàng)建server.service包,在該包下創(chuàng)建ChatServer類,用于接收客戶端法來的User和Message對象,并給出回應。ChatServer類代碼如下 :?

package server.service;

import mutual.Message;
import mutual.MessageType;
import mutual.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 服務端
 */
public class ChatServer {
    //將ServerSocket設置為屬性,寫在main函數(shù)外
    private ServerSocket serverSocket = null;

    /**
        將合法的用戶放入集合中(使用“id + user”的泛型),
        建議使用ConcurrentHashMap集合,線程同步,可在多線程程序下安全使用。
     */
    private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>();
    static {    //在靜態(tài)代碼塊中初始化validUsers集合對象
        validUsers.put("Cyan", new User("Cyan", "RA9"));
        validUsers.put("Rain", new User("Rain", "flo"));
        validUsers.put("Ice", new User("Ice", "ais"));
        validUsers.put("Five", new User("Five", "55555"));
        validUsers.put("Kyrie", new User("Kyrie", "lrving"));
    }

    public boolean checkUser(String userID, String password) {
        User user = validUsers.get(userID);
        if (user == null) { //如果合法用戶集合中不存在當前用戶,直接返回false;
            return false;
        }
        if (!(user.getPwd().equals(password))) {    //如果存在該用戶,但密碼錯誤,返回false;
            return false;
        }

        return true;
    }
    public ChatServer() {
        //端口也可以寫在配置文件中
        try {
            System.out.println("服務端正在8888端口監(jiān)聽...");
            serverSocket = new ServerSocket(8888);

            /*
                監(jiān)聽是不間斷的,當服務端和某個客戶端建立連接后,服務端會繼續(xù)監(jiān)聽。
             */
            while (true) {
                //獲取Socket類對象(服務端是通過accept方法來獲取Socket對象的)
                Socket socket = serverSocket.accept();
                //獲取Socket對象關聯(lián)的輸入流與輸出流(對象處理流)
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                //客戶端第一次傳過來的是User對象
                User user = (User) ois.readObject();
                //暫時以單用戶登錄為例(id = Cyan, pwd = RA9)
                //創(chuàng)建一個Message對象,用于回復客戶端是否連接成功(Message對象寫在if-else語句外)
                Message message = new Message();

                if (checkUser(user.getId(), user.getPwd())) {   //登錄成功
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCESS);
                    //將包含“登錄成功與否”信息的Message對象寫入數(shù)據(jù)通道
                    oos.writeObject(message);
                    //創(chuàng)建一個線程,與客戶端保持通訊
                    ServerConnectClientThread scct = new ServerConnectClientThread(socket, user.getId());
                    //啟動線程
                    scct.start();
                    //將線程放入集合中
                    ControlServerConnectClientThread.addServerConnectClientThread(user.getId(), scct);
                } else {    //登錄失敗
                    System.out.println("id = " + user.getId() + ",pwd = " + user.getPwd() + " 驗證失??!");
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    socket.close(); //關閉Socket
                }
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //若退出while循環(huán),說明服務端不再監(jiān)聽,需要關閉ServerSocket對象
            try {
                serverSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

? ? ? ? ? ? ? ? 2° 線程創(chuàng)建

? ? ? ? ? ? ? ? 同樣,為了保持通訊,需要讓線程持有Socket對象,相關代碼封裝到service包下的ServerConnectClientThread類中,代碼如下 :?

package server.service;

import mutual.Message;

import java.io.ObjectInputStream;
import java.net.Socket;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 服務端的線程,用于和客戶端保持通訊
 */
public class ServerConnectClientThread extends Thread {
    private Socket socket;
    private String userID;  //當前連接到服務端的用戶的id
    public ServerConnectClientThread(Socket socket, String userID) {
        this.socket = socket;
        this.userID = userID;
    }

    public Socket getSocket() {
        return socket;
    }

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("服務端與客戶端" + userID + "保持通訊,讀取數(shù)據(jù)中...");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());

                Message message = (Message) ois.readObject();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

? ? ? ? ? ? ? ? 3° 線程管理

? ? ? ? ? ? ? ? 涉及到線程管理的代碼封裝到ControlServerConnectClientThread類中,代碼如下 :?

package server.service;

import java.util.HashMap;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 用于管理服務端的線程
 */
public class ControlServerConnectClientThread {
    private static  HashMap<String, ServerConnectClientThread> hashMap = new HashMap<>();

    //添加線程到集合中
    public static void addServerConnectClientThread(String userID, ServerConnectClientThread scct) {
        hashMap.put(userID, scct);
    }

    //根據(jù)用戶的id獲取對應的線程
    public static ServerConnectClientThread getServerConnectClientThread(String userID) {
        return hashMap.get(userID);
    }
}

? ? ? ? 4.登錄測試?:?

? ? ? ? ? ? ? ? 在服務器端新建一個frame包,在該包下新建一個ChatFrame類,用于啟動服務端(客戶端在View類中啟動)。ChatFrame類代碼如下 :?

package frame;

import server.service.ChatServer;

public class ChatFrame {
    public static void main(String[] args) {
        new ChatServer();
    }
}

? ? ? ? ? ? ? ? 同時啟動ChatFrame類和View類,效果如下GIF圖 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解


三、在線列表

? ? ? ? 1.擴充MessageType中的類型?:?

????????????????客戶端如果想獲取當前多用戶通訊系統(tǒng)中的在線成員列表,需要通過Message對象向服務端申請,服務端再通過Message對象的形式,將系統(tǒng)的在線用戶列表發(fā)送給客戶端
? ? ? ? ? ? ? ? 首先我們需要對MessageType中的類型進行擴充,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? 2.擴充UserClientService類中的方法 :?

? ? ? ? ? ? ? ? 在客戶端的UserClientService類中新增一個用于拉取在線用戶列表的onlineList方法,代碼如下 :?

    public void onlineList() {
        //向服務端發(fā)送一個Message對象,類型是MESSAGE_GET_ONLINE_FRIENDS
        Message message = new Message();
        message.setSender(user.getId());    //用戶登錄時已在check方法中設置了id的值,所以可直接用
        message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIENDS);

        try {
            //得到當前線程持有的Socket對象對應的對象處理流(輸出流)
            ObjectOutputStream oos =
                    new ObjectOutputStream(ControlClientConnectServiceThread.getClientConnectServiceThread(user.getId()).getSocket().getOutputStream());
            oos.writeObject(message);   //向服務端發(fā)送“拉取在線用戶列表”的請求
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

? ? ? ? 3.擴充客戶端線程類中的內(nèi)容 :?

? ? ? ? ? ? ? ? 在客戶端的ClientConnectServerThread類中,run方法里面,增加對于Message類型判斷和處理的邏輯語句,代碼如下 :?


                /*
                    判斷客戶端讀取到的Message的類型,并做出相應的業(yè)務處理。
                 */
                if (message.getMesType().equals(MessageType.MESSAGE_RETURN_ONLINE_FRIENDS)) {
                    //若Message的類型是返回的在線用戶列表,取出在線列表并顯示,使用空格分隔不同用戶的id
                    String[] onlineUsers = message.getContent().split(" ");
                    System.out.println("===========在線用戶列表如下:===========");
                    for (int i = 0; i < onlineUsers.length; i++) {
                        System.out.println("用戶: " + onlineUsers[i]);
                    }
                } else {
                    System.out.println("...other content");
                }

????????4.擴充ControlServerConnectClientThread類中的方法 :?

? ? ? ? ? ? ? ? 拉取在線用戶列表的操作要在服務端線程的run方法中進行,同樣可以利用oop思想,將相關代碼封裝起來;考慮每個線程都保存了當前用戶的id和對應的Socket對象,于是決定在服務端線程的管理類ControlServerConnectClientThread類中新增一個onlineList方法,用于拉取用戶的在線列表,返回一個String類型的字符串給客戶端,客戶端的線程再對該字符串進行處理。onlineList方法代碼如下 :?

    //獲取在線用戶列表
    public static String getOnlineFriends() {
        /*
            利用hashMap集合中的key是用戶id的特點,可以對hashMap對象進行遍歷,從而獲取用戶列表。
         */
        Iterator<String> iterator = hashMap.keySet().iterator();
        String onlineUsers = "";

        while (iterator.hasNext()) {
            onlineUsers += iterator.next() + " ";   //加空格對應客戶端的split方法。
        }

        return onlineUsers;
    }

? ? ? ? 5.擴充服務端線程類中的內(nèi)容 :?

? ? ? ? ? ? ? ? 有了拉取在線用戶的方法,就可以在服務端的線程類中調用該方法了,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? 6.拉取測試 :?

? ? ? ? ? ? ? ? 啟動ChatFrame類和多個View類,如下GIF圖所示 :??

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解


四、退出系統(tǒng)

? ? ? ? 1.需要解決的問題 :?

? ? ? ? 當用戶登錄成功后,即客戶端與服務端建立連接后,若我們在控制臺輸入9,整個進程并沒有退出,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

????????這是因為主線程退出后,負責聯(lián)絡服務端的子線程還沒有退出(Socket數(shù)據(jù)通道還沒有關閉),還在不停運行,等待服務端發(fā)送數(shù)據(jù),如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? 2.解決辦法 :?

????????? ??1° 總思路

? ? ? ? ? ? ? ? 如果我們可以直接令客戶端的整個進程關閉,就可以自動退出該進程下的所有線程;可以在客戶端的View類下增加一個方法的調用,若用戶輸入9,就給服務器端發(fā)送一個Mesage對象,令服務端退出與當前對象相關聯(lián)的線程

? ? ? ? ? ? ? ? 服務端的線程類中保存了與當前用戶相關聯(lián)的Socket對象和當前用戶的ID,如下所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 因此,服務端可以根據(jù)接收到的Message對象,關閉指定線程及Socket對象。
? ? ? ? ? ? ? ? 然后,在客戶端調用System.exit(0)方法退出當前進程。

????????? ??2° 客戶端

????????????????在UserClientService類中的新定義一個方法logout,用于完成對服務端發(fā)送“關閉線程”的Message對象的功能logout方法代碼如下 :?

    /** logout方法可以退出當前用戶 */
    public void logout() {
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
        message.setSender(user.getId());    //指定具體要退出的客戶端

        //發(fā)送Message對象
        try {
            ObjectOutputStream oos =
                    new ObjectOutputStream(ControlClientConnectServiceThread.getClientConnectServiceThread(user.getId()).getSocket().getOutputStream());
            oos.writeObject(message);
            
            System.out.println(user.getId() + " 退出系統(tǒng)...");
            System.exit(0);     //0表示正常退出當前“進程”。
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

? ? ? ? ? ? ? ? 同時,在View類中調用該方法,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

????????? ??3° 服務端

? ? ? ? ? ? ? ? 首先,在服務端的線程管理類ControlServerConnectClientThread類中,新定義一個方法用來刪除服務端指定的線程,如下所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 然后在服務端的線程類ServerConnectClientThread中新增一個else if的判斷,并在其中調用該方法(可在刪除前令線程休眠0.5s,以避免EOF異常),如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 測試結果如下(成功):

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解


五、私聊群聊

? ? ? ? 1.私發(fā)消息 :?

? ? ? ? ? ? ? ? 1° 思路分析

? ? ? ? ? ? ? ? 不管是私發(fā)還是群發(fā),一般情況下,一個客戶端與另一個客戶端都是無法直接通訊的,需要經(jīng)過服務端來轉發(fā)。
? ? ? ? ? ? ? ? 對于客戶端,它需要將要發(fā)送的信息打包成Message對象,然后發(fā)給服務端;同時,要接收來自其他用戶的(經(jīng)過服務端轉發(fā)的)消息
? ? ? ? ? ? ? ? 對于服務端,它需要讀取某個用戶發(fā)送給另一個用戶的消息,然后根據(jù)Message對象中的id信息獲取到對應線程,繼而獲取到該線程持有的Socket,最后通過Socket將信息發(fā)送給另一個用戶。

? ? ? ? ? ? ? ? 2° 代碼實現(xiàn)

? ? ? ? ? ? ? ? 客戶端 :?

? ? ? ? ? ? ? ? 在client.service包下新建一個MessageClientService類,用于管理消息代碼如下 :?

package client.service;

import mutual.Message;
import mutual.MessageType;

import java.io.IOException;
import java.io.ObjectOutputStream;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 提供與消息有關的服務
 */
public class MessageClientService {
    /**
     * @param receiver : 消息的接收者
     * @param content : 消息內(nèi)容
     * @param sender : 消息的發(fā)送者
     */
    public void sendMessageToOne(String receiver, String content, String sender) {
        Message message = new Message();

        message.setMesType(MessageType.MESSAGE_COMMON_MES);     //消息類型
        message.setReceiver(receiver);
        message.setContent(content);
        message.setSender(sender);
        message.setSendTime(new java.util.Date().toString());   //發(fā)送時間
        System.out.println(sender + " 對 " + receiver + " 說 \"" + content + "\"");

        try {
            //獲取發(fā)送消息的用戶的輸出流對象,并將上面的消息發(fā)給服務端
            ObjectOutputStream oos =
                    new ObjectOutputStream(ControlClientConnectServiceThread.getClientConnectServiceThread(sender).getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

? ? ? ? ? ? ? ? 在客戶端的線程類中,增加一條else if語句,打印出服務端轉發(fā)來的消息,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 最后,在客戶端界面的相關部分(View類),調用MessageCilentService類的發(fā)送消息的方法,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 服務端 :?

? ? ? ? ? ? ? ? 在服務端與“發(fā)消息用戶”通訊的線程中(ServerConnectClientThread類中),通過Message對象封裝的信息,獲取服務端與“要接收消息的用戶”通訊的線程,然后通過該線程獲取要接受消息的用戶的Socket以及其對應的對象處理流;最后將消息寫入到該Socket對應的數(shù)據(jù)通道中,實現(xiàn)消息的轉發(fā),如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 3° 運行測試?

? ? ? ? ? ? ? ? 如下GIF圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? 2.群發(fā)消息 :?

? ? ? ? ? ? ? ? 群發(fā)消息,就是在私發(fā)消息的基礎上,在服務端遍歷在線用戶列表;然后將發(fā)消息的用戶自己排除后,把Message對象轉發(fā)給其他所有的在線用戶。這里要對MessageType接口進行擴充,增加一個消息類型,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 1° 客戶端?

? ? ? ? ? ? ? ? 在MessageClientService類中定義一個群發(fā)消息的方法sendMessageToAll,代碼如下 :?

    public void sendMessageToAll(String content, String sender) {
        Message message = new Message();

        message.setMesType(MessageType.MESSAGE_COMMON_MES_ALL);     //消息類型
        message.setContent(content);                                //消息內(nèi)容
        message.setSender(sender);                                  //消息發(fā)送者
        message.setSendTime(new java.util.Date().toString());       //消息發(fā)送時間
        System.out.println(sender + " 對所有在線的??們說 \"" + content + "\"");

        try {
            //獲取發(fā)送消息的用戶的輸出流對象,并將上面的消息發(fā)給服務端
            ClientConnectServiceThread clientConnectServiceThread = ControlClientConnectServiceThread.getClientConnectServiceThread(sender);
            ObjectOutputStream oos = new ObjectOutputStream(clientConnectServiceThread.getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

? ? ? ? ? ? ? ? 然后在View類中的相應區(qū)域調用該方法,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 接著,在客戶端的線程類中增加一個else if 的判斷,用于接收來自服務端轉發(fā)的群發(fā)消息并顯示在控制臺,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 2° 服務端?

? ? ? ? ? ? ? ? 首先在管理線程的類中,定義一個可以返回hashMap的方法,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 然后,在服務端的線程類中,新增一個else if 的判斷,如果判斷Message類型是群發(fā)類型,就遍歷集合,實現(xiàn)群發(fā)。如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 3° 運行測試?

? ? ? ? ? ? ? ? 如下GIF圖 :??

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解


六、傳輸文件

? ? ? ? 1.思路分析 :?

? ? ? ? ? ? ? ? 發(fā)送文件與發(fā)送消息原理類似,都是以Message對象為載體;只不過發(fā)送文件時,Message對象中的內(nèi)容是一個保存了圖片的字節(jié)數(shù)組了。
? ? ? ? ? ? ? ? 對于客戶端——
? ? ? ? ? ? ? ? 先把要發(fā)送的文件讀取到客戶端(字節(jié)數(shù)組);然后把文件對應的字節(jié)數(shù)組封裝到Message對象中;最后將Message對象發(fā)送給服務端;當然,客戶端還需要接收來自服務端轉發(fā)過來的Message對象,并將其中的文件內(nèi)容保存到磁盤。
? ? ? ? ? ? ? ? 對于服務端——
? ? ? ? ? ? ? ? 服務端接收到來自某一個用戶發(fā)來的Message對象后,要進行拆包,獲取到具體的接收者,然后實現(xiàn)轉發(fā)即可。

? ? ? ? ? ? ? ? 還需要對MessageType接口進行擴充,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 對Message類進行擴充,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 提供一些新的屬性以及它們對應的getter, setter方法。??

? ? ? ? 2.客戶端 :?

? ? ? ? ? ? ? ? 在client.service包下新定義一個FileClientService類,用于文件發(fā)送功能的實現(xiàn),FileClientService類代碼如下 :?

package client.service;

import mutual.Message;
import mutual.MessageType;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 實現(xiàn)發(fā)送文件相關的功能
 */
public class FileClientService {

    /**
     * @param souPath : 數(shù)據(jù)源文件路徑
     * @param desPath : 目的地文件路徑
     * @param sender : 發(fā)送者(ID)
     * @param receiver : 接收者(ID)
     */
    public void setFileToOne(String souPath, String desPath, String sender, String receiver) {
        Message message = new Message();

        message.setMesType(MessageType.MESSAGE_FILE_TRANSMISSION);
        message.setSouPath(souPath);
        message.setDesPath(desPath);
        message.setSender(sender);
        message.setReceiver(receiver);

        //1.讀取文件
        /*
            利用File類的length方法(獲取當前文件的大小,以字節(jié)計算),
            可以得知要創(chuàng)建的字節(jié)數(shù)組的大??;
            因為length方法的返回值是long類型,所以此處需要類型強轉。
         */
        byte[] file = new byte[(int)new File(souPath).length()];

        //創(chuàng)建一個輸入流
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(souPath);
            fileInputStream.read(file);     //將file文件讀取到字節(jié)數(shù)組中。

            //將文件包裝到Message對象
            message.setFile(file);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //關閉輸入流
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        //2.提示信息
        System.out.println("\n" + sender + " 給 " + receiver + " 發(fā)送 " + souPath +
                "到對方電腦的目錄" + desPath + "下...");

        //3.發(fā)送文件
        try {
            ObjectOutputStream oos =
                    new ObjectOutputStream(ControlClientConnectServiceThread.getClientConnectServiceThread(sender).getSocket().getOutputStream());

            oos.writeObject(message);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

? ? ? ? ? ? ? ? 然后,還要在客戶端的線程類中擴展一個else if 的判斷語句,若接收到的Message對象為服務端轉發(fā)來的文件消息,就將其讀取并保存到本地磁盤中。如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 最后在View類中創(chuàng)建FileClientService對象,如下圖所示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

? ? ? ? ? ? ? ? 然后在對應的部分調用該對象的方法,如下圖所示:?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

????????3.服務端 :?

? ? ? ? ? ? ? ? 服務端還是老樣子,在服務端的線程中增加一個else if 的Message類型判斷,如果判斷為發(fā)送文件的Message對象,就和私發(fā)消息一樣給轉發(fā)一下就OK了。如下圖所示 :?
? ? ? ? ? ? ? ? 截圖沒截到的部分,就是之前的老樣子——先通過線程管理類的得到線程的方法,根據(jù)接收者的id獲取對應的線程;然后再獲取線程持有的Socket對象,最后再獲取與該Socket對象相關聯(lián)的輸出流。

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解

????????4.運行測試 :?

? ? ? ? ? ? ? ? 如下GIF圖演示 :?

java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解


七、最終代碼

? ? ? ? 1.客戶端 :?

? ? ? ? ? ? ? ? 1° View

package client.menu;

import client.service.FileClientService;
import client.service.MessageClientService;
import client.service.UserClientService;

import java.io.IOException;
import java.util.Scanner;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 菜單界面的顯示
 * @PS : Run -> Edit Configurations -> Modify options -> allow multiple instances
 */
public class View {
    private boolean loop = true;        //控制是否顯示菜單
    private String key = "";            //接收用戶的鍵盤輸入
    private static Scanner sc = new Scanner(System.in); //靜態(tài)掃描儀

    /*
        將userClientService對象置為屬性,
        該對象用于執(zhí)行用戶登錄/注冊等操作(該步驟將功能與界面聯(lián)系起來)
     */
    private UserClientService userClientService = new UserClientService();

    /*
        將messageClientService對象置為屬性,
        該對象用于消息的管理(該步驟將功能與界面聯(lián)系起來)
    */
    private MessageClientService messageClientService = new MessageClientService();

    /*
        將fileClientService對象置為屬性,
        該對象用于文件的發(fā)送(該步驟將功能與界面聯(lián)系起來)
    */
    private FileClientService fileClientService = new FileClientService();

    public static void main(String[] args) throws IOException {
        new View().mainMenu();
        System.out.println("客戶端退出...");
        sc.close();
    }

    private void mainMenu() throws IOException {
        while (loop) {
            System.out.println("===========Welcome to the system of chat:===========");
            System.out.println("\t\t1.登錄系統(tǒng)");
            System.out.println("\t\t9.退出系統(tǒng)");


            System.out.print("請輸入你的選擇:");
            key = sc.nextLine();

            switch (key) {
                case "1" :
                    //登錄操作
                    System.out.print("請輸入用戶名:");
                    String userID = sc.nextLine();
                    System.out.print("請輸入密  碼:");
                    String password = sc.nextLine();

                    //驗證登錄的用戶是否合法(封裝思想)
                    if (userClientService.check(userID, password)) {     //驗證成功
                        System.out.println("\n===========Welcome user " + userID + "===========");
                        //向用戶顯示二級菜單
                        while (loop) {
                            System.out.println("\n===========網(wǎng)絡通信系統(tǒng)二級菜單(user:" + userID + ")===========");
                            System.out.println("\t\t1.在線列表:");
                            System.out.println("\t\t2.群發(fā)消息:");
                            System.out.println("\t\t3.私發(fā)消息:");
                            System.out.println("\t\t4.文件發(fā)送:");
                            System.out.println("\t\t9.退出系統(tǒng):");

                            System.out.print("請輸入你的選擇:");
                            key = sc.nextLine();

                            switch (key) {
                                case "1" :
                                    userClientService.onlineList();
                                    break;
                                case "2" :
                                    System.out.println("請輸入你要對大家說的話:");
                                    String announcement = sc.nextLine();
                                    //調用群發(fā)消息的方法
                                    messageClientService.sendMessageToAll(announcement, userID);
                                    break;
                                case "3" :
                                    System.out.print("請輸入你想聊天的對象(在線),receiver = ");
                                    String receiver = sc.nextLine();
                                    System.out.print("請輸入你要說的話: ");
                                    String content = sc.nextLine();
                                    //調用私發(fā)消息的方法
                                    messageClientService.sendMessageToOne(receiver, content, userID);
                                    break;
                                case "4" :
                                    System.out.print("請輸入你想發(fā)送文件的對象(在線),receiver = ");
                                    String fileReceiver = sc.nextLine();
                                    System.out.print("請輸入數(shù)據(jù)源文件的路徑, souPath = ");
                                    String souPath = sc.nextLine();
                                    System.out.print("請輸入目的地文件的路徑, desPath = ");
                                    String desPath = sc.nextLine();
                                    fileClientService.setFileToOne(souPath, desPath, userID, fileReceiver);
                                    break;
                                case "9" :
                                    userClientService.logout();
                                    loop = false;   //在二級菜單中用戶也可以直接選擇退出系統(tǒng)
                                    break;
                            }
                        }
                    } else {        //驗證失敗
                        System.out.println("登錄失?。≌堉匦聡L試!");
                    }

                    break;
                case "9" :
                    loop = false;       //將控制while循環(huán)的布爾變量設置為false
                    break;
            }
        }
    }
}

? ? ? ? ? ? ? ? 2°?UserClientService

package client.service;

import mutual.Message;
import mutual.MessageType;
import mutual.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 登錄驗證
 */
public class UserClientService {
    /*
        將User對象設置成一個屬性,可利用getter和setter修改User對象的引用,便于操作。
        Socket對象同樣也可能在其他類中使用,因此也設置為屬性。
     */
    private User user = new User();
    private Socket socket;

    /** check方法可以向服務端發(fā)起用戶登錄的驗證 */
    public boolean check(String userID, String password) throws IOException {
        //局部變量
        boolean b = false;
        //初始化User對象
        user.setId(userID);
        user.setPwd(password);

        //向服務端發(fā)送信息
        try {
            //1.獲取Socket對象
            socket = new Socket(InetAddress.getByName("127.0.0.1"), 8888);
            //2.獲取與Socket對象相關聯(lián)的對象處理流(輸出流)
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            //3.序列化User對象,寫入數(shù)據(jù)通道(向服務端發(fā)送一個User對象,服務端會對這個User對象進行驗證)
            oos.writeObject(user);
            //.........

            //4.獲取與Socket對象相關聯(lián)的對象處理流(輸入流)
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            //5.讀取服務端傳輸過來的Message對象
            Message message = (Message) ois.readObject();   //類型強轉

            if (message.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCESS)) {
                //創(chuàng)建線程對象(目的是為了與服務端保持通訊)
                ClientConnectServiceThread ccst = new ClientConnectServiceThread(socket);
                //啟動線程
                ccst.start();
                //將線程放入集合中統(tǒng)一管理
                ControlClientConnectServiceThread.addClientConnectServiceThread(userID, ccst);
                b = true;
            } else {
                //如果沒有啟動線程,關閉Socket對象。
                socket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return b;
    }

    /** onlineList方法可以向服務端請求拉取在線列表 */
    public void onlineList() {
        //向服務端發(fā)送一個Message對象,類型是MESSAGE_GET_ONLINE_FRIENDS
        Message message = new Message();
        message.setSender(user.getId());    //用戶登錄時已在check方法中設置了id的值,所以可直接用
        message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIENDS);

        try {
            //得到當前線程持有的Socket對象對應的對象處理流(輸出流)
            ObjectOutputStream oos =
                    new ObjectOutputStream(ControlClientConnectServiceThread.getClientConnectServiceThread(user.getId()).getSocket().getOutputStream());
            oos.writeObject(message);   //向服務端發(fā)送“拉取在線用戶列表”的請求
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /** logout方法可以退出當前用戶 */
    public void logout() {
        Message message = new Message();
        message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);
        message.setSender(user.getId());    //指定具體要退出的客戶端

        //發(fā)送Message對象
        try {
            ObjectOutputStream oos =
                    new ObjectOutputStream(ControlClientConnectServiceThread.getClientConnectServiceThread(user.getId()).getSocket().getOutputStream());
            oos.writeObject(message);

            ControlClientConnectServiceThread.getClientConnectServiceThread(user.getId()).setLoop(false);
            System.out.println(user.getId() + " 退出系統(tǒng)...");
            System.exit(0);     //0表示正常退出當前“進程”。
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

? ? ? ? ? ? ? ? 3°?MessageClientService

package client.service;

import mutual.Message;
import mutual.MessageType;

import java.io.ObjectOutputStream;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 提供與消息有關的服務
 */
public class MessageClientService {
    /**
     * @param receiver : 消息的接收者
     * @param content : 消息內(nèi)容
     * @param sender : 消息的發(fā)送者
     */
    public void sendMessageToOne(String receiver, String content, String sender) {
        Message message = new Message();

        message.setMesType(MessageType.MESSAGE_COMMON_MES);     //消息類型
        message.setReceiver(receiver);                          //消息接收者
        message.setContent(content);                            //消息內(nèi)容
        message.setSender(sender);                              //消息發(fā)送者
        message.setSendTime(new java.util.Date().toString());   //發(fā)送時間
        System.out.println(sender + " 對 " + receiver + " 說 \"" + content + "\"");

        try {
            //獲取發(fā)送消息的用戶的輸出流對象,并將上面的消息發(fā)給服務端
            ClientConnectServiceThread clientConnectServiceThread = ControlClientConnectServiceThread.getClientConnectServiceThread(sender);
            ObjectOutputStream oos = new ObjectOutputStream(clientConnectServiceThread.getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * @param content : 群發(fā)消息的內(nèi)容
     * @param sender : 群發(fā)消息的發(fā)送者
     */
    public void sendMessageToAll(String content, String sender) {
        Message message = new Message();

        message.setMesType(MessageType.MESSAGE_COMMON_MES_ALL);     //消息類型
        message.setContent(content);                                //消息內(nèi)容
        message.setSender(sender);                                  //消息發(fā)送者
        message.setSendTime(new java.util.Date().toString());       //消息發(fā)送時間
        System.out.println(sender + " 對所有在線的??們說 \"" + content + "\"");

        try {
            //獲取發(fā)送消息的用戶的輸出流對象,并將上面的消息發(fā)給服務端
            ClientConnectServiceThread clientConnectServiceThread = ControlClientConnectServiceThread.getClientConnectServiceThread(sender);
            ObjectOutputStream oos = new ObjectOutputStream(clientConnectServiceThread.getSocket().getOutputStream());
            oos.writeObject(message);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

? ? ? ? ? ? ? ? 4°?FileClientService

package client.service;

import mutual.Message;
import mutual.MessageType;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 實現(xiàn)發(fā)送文件相關的功能
 */
public class FileClientService {

    /**
     * @param souPath : 數(shù)據(jù)源文件路徑
     * @param desPath : 目的地文件路徑
     * @param sender : 發(fā)送者(ID)
     * @param receiver : 接收者(ID)
     */
    public void setFileToOne(String souPath, String desPath, String sender, String receiver) {
        Message message = new Message();

        message.setMesType(MessageType.MESSAGE_FILE_TRANSMISSION);
        message.setSouPath(souPath);
        message.setDesPath(desPath);
        message.setSender(sender);
        message.setReceiver(receiver);

        //1.讀取文件
        /*
            利用File類的length方法(獲取當前文件的大小,以字節(jié)計算),
            可以得知要創(chuàng)建的字節(jié)數(shù)組的大?。?            因為length方法的返回值是long類型,所以此處需要類型強轉。
         */
        byte[] file = new byte[(int)new File(souPath).length()];

        //創(chuàng)建一個輸入流
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(souPath);
            fileInputStream.read(file);     //將file文件讀取到字節(jié)數(shù)組中。

            //將文件包裝到Message對象
            message.setFile(file);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            //關閉輸入流
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }

        //2.提示信息
        System.out.println("\n" + sender + " 給 " + receiver + " 發(fā)送 " + souPath +
                " 到對方電腦的目錄 " + desPath + " 下...");

        //3.發(fā)送文件
        try {
            ObjectOutputStream oos =
                    new ObjectOutputStream(ControlClientConnectServiceThread.getClientConnectServiceThread(sender).getSocket().getOutputStream());

            oos.writeObject(message);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

? ? ? ? ? ? ? ? 5°?ClientConnectServiceThread

package client.service;

import mutual.Message;
import mutual.MessageType;

import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.net.Socket;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 客戶端用于和服務端進行通訊的線程
 */
public class ClientConnectServiceThread extends Thread {
    //該線程需要持有Socket對象
    private Socket socket;
    private boolean loop = true;

    public ClientConnectServiceThread(Socket socket) {
        this.socket = socket;
    }

    public Socket getSocket() {
        return socket;
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }

    @Override
    public void run() {
        //∵Thread需要在后臺與服務器通信,因此使用while循環(huán)
        while (loop) {
            try {
                System.out.println("客戶端線程,等待讀取來自服務器端的消息...");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                /*
                    如果服務端沒有發(fā)送Message對象到數(shù)據(jù)通道中,線程就會阻塞在這里。
                 */
                Message message = (Message) ois.readObject();

                /**
                 判斷客戶端讀取到的Message的類型,并做出相應的業(yè)務處理。
                 */
                if (message.getMesType().equals(MessageType.MESSAGE_RETURN_ONLINE_FRIENDS)) {
                    //若Message的類型是返回的在線用戶列表,取出在線列表并顯示,使用空格分隔不同用戶的id
                    String[] onlineUsers = message.getContent().split(" ");
                    System.out.println("\n===========在線用戶列表如下:===========");
                    for (int i = 0; i < onlineUsers.length; i++) {
                        System.out.println("用戶: " + onlineUsers[i]);
                    }
                } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON_MES_ALL)) {
                    System.out.println("\n" + message.getSender() + " 對所有在線的??說 \"" +
                            message.getContent() + "\"");
                } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON_MES)) {
                    System.out.println("\n" + message.getSender() + " 對 " +
                            message.getReceiver() + " 說 \"" + message.getContent() + "\"");
                } else if (message.getMesType().equals(MessageType.MESSAGE_FILE_TRANSMISSION)) {
                    System.out.println("\n" + message.getSender() + " 給 " + message.getReceiver() + " 發(fā)送 " + message.getSouPath() +
                            " 到對方電腦的目錄 " + message.getDesPath() + "下...");
                    FileOutputStream fileOutputStream = new FileOutputStream(message.getDesPath());
                    fileOutputStream.write(message.getFile());
                    fileOutputStream.close();
                    System.out.println("\n保存文件成功!");
                } else {
                    System.out.println("...other content");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

? ? ? ? ? ? ? ? 6°?ControlClientConnectServiceThread

package client.service;

import java.util.HashMap;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 管理客戶端的線程
 */
public class ControlClientConnectServiceThread {
    /*
        使用HashMap類來管理多個線程(模擬數(shù)據(jù)庫),key表示用戶的id,value表示線程。
     */
    private static HashMap<String, ClientConnectServiceThread> hashMap = new HashMap<>();

    //添加線程的方法
    public static void addClientConnectServiceThread(String userID, ClientConnectServiceThread ccst) {
        hashMap.put(userID, ccst);
    }

    //取出線程的方法
    public static ClientConnectServiceThread getClientConnectServiceThread(String userID) {
        return hashMap.get(userID);
    }
}

? ? ? ? 2.服務端 :?

? ? ? ? ? ? ? ? 1°?ChatServer

package server.service;

import mutual.Message;
import mutual.MessageType;
import mutual.User;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 服務端
 */
public class ChatServer {
    //將ServerSocket設置為屬性,寫在main函數(shù)外
    private ServerSocket serverSocket = null;

    /**
        將合法的用戶放入集合中(使用“id + user”的泛型),
        建議使用ConcurrentHashMap集合,線程同步,可在多線程程序下安全使用。
     */
    private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>();
    static {    //在靜態(tài)代碼塊中初始化validUsers集合對象
        validUsers.put("Cyan", new User("Cyan", "RA9"));
        validUsers.put("Rain", new User("Rain", "flo"));
        validUsers.put("Ice", new User("Ice", "ais"));
        validUsers.put("Five", new User("Five", "55555"));
        validUsers.put("Kyrie", new User("Kyrie", "lrving"));
    }

    public boolean checkUser(String userID, String password) {
        User user = validUsers.get(userID);
        if (user == null) { //如果合法用戶集合中不存在當前用戶,直接返回false;
            return false;
        }
        if (!(user.getPwd().equals(password))) {    //如果存在該用戶,但密碼錯誤,返回false;
            return false;
        }

        return true;
    }
    public ChatServer() {
        //端口也可以寫在配置文件中
        try {
            System.out.println("服務端正在8888端口監(jiān)聽...");
            serverSocket = new ServerSocket(8888);

            /*
                監(jiān)聽是不間斷的,當服務端和某個客戶端建立連接后,服務端會繼續(xù)監(jiān)聽。
             */
            while (true) {
                //獲取Socket類對象(服務端是通過accept方法來獲取Socket對象的)
                Socket socket = serverSocket.accept();
                //獲取Socket對象關聯(lián)的輸入流與輸出流(對象處理流)
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                //客戶端第一次傳過來的是User對象
                User user = (User) ois.readObject();
                
                //創(chuàng)建一個Message對象,用于回復客戶端是否連接成功(Message對象寫在if-else語句外)
                Message message = new Message();

                if (checkUser(user.getId(), user.getPwd())) {   //登錄成功
                    message.setMesType(MessageType.MESSAGE_LOGIN_SUCCESS);
                    //將包含“登錄成功與否”信息的Message對象寫入數(shù)據(jù)通道
                    oos.writeObject(message);
                    //創(chuàng)建一個線程,與客戶端保持通訊
                    ServerConnectClientThread scct = new ServerConnectClientThread(socket, user.getId());
                    //啟動線程
                    scct.start();
                    //將線程放入集合中
                    ControlServerConnectClientThread.addServerConnectClientThread(user.getId(), scct);
                } else {    //登錄失敗
                    System.out.println("id = " + user.getId() + ",pwd = " + user.getPwd() + " 驗證失??!");
                    message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);
                    oos.writeObject(message);
                    socket.close(); //關閉Socket
                }
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            //若退出while循環(huán),說明服務端不再監(jiān)聽,需要關閉ServerSocket對象
            try {
                serverSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

? ? ? ? ? ? ? ? 2°?ServerConnectClientThread

package server.service;

import mutual.Message;
import mutual.MessageType;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 服務端的線程,用于和客戶端保持通訊
 */
public class ServerConnectClientThread extends Thread {
    private Socket socket;
    private String userID;  //當前連接到服務端的用戶的id

    public ServerConnectClientThread(Socket socket, String userID) {
        this.socket = socket;
        this.userID = userID;
    }

    public Socket getSocket() {
        return socket;
    }

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("服務端與客戶端" + userID + "保持通訊,讀取數(shù)據(jù)中...");
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());

                Message message = (Message) ois.readObject();

                /**
                 判斷服務端讀取到的Message的類型,并做出相應的業(yè)務處理。
                 */
                if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIENDS)) {
                    System.out.println(message.getSender() + " 請求拉取在線用戶列表。");
                    String onlineUsers = ControlServerConnectClientThread.getOnlineFriends();

                    //構建Message對象,將獲取到的在線用戶列表的信息發(fā)送給客戶端
                    Message message2 = new Message();
                    message2.setMesType(MessageType.MESSAGE_RETURN_ONLINE_FRIENDS);
                    message2.setContent(onlineUsers);
                    message2.setReceiver(message.getSender());  //發(fā)送者 ——> 接收者
                    /*
                        對象處理流寫在相應業(yè)務里面,各是各的,各用各的,不易沖突。
                     */
                    ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
                    oos.writeObject(message2);
                } else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {
                    //刪除負責與當前用戶通信的線程
                    System.out.println(message.getSender() + " 退出...");
                    Thread.sleep(500);    //刪除線程前讓當前線程休眠0.5秒,避免EOF異常
                    ControlServerConnectClientThread.removeServerConnectClientThread(userID);
                    //關閉Socket(若忽略此步驟,客戶端無異常退出,服務端仍然異常。
                    socket.close();
                    //退出while循環(huán)
                    break;
                } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON_MES_ALL)) {
                    //遍歷管理線程的集合
                    HashMap<String, ServerConnectClientThread> hashMap = ControlServerConnectClientThread.getHashMap();

                    Iterator<String> iterator = hashMap.keySet().iterator();
                    while (iterator.hasNext()) {
                        String onlUser = iterator.next();
                        //排除自己
                        if (!onlUser.equals(message.getSender())) {
                            ObjectOutputStream oos =
                                    new ObjectOutputStream(hashMap.get(onlUser).getSocket().getOutputStream());
                            oos.writeObject(message);
                        }
                    }
                } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON_MES)) {
                    /*
                        根據(jù)message對象中的receiver信息,獲取對應的線程;
                        進而獲取線程持有的Socket,以及與該Socket相關聯(lián)的對象處理流,
                        利用對象處理流將信息發(fā)送給另一個用戶
                     */
                    ObjectOutputStream oos =
                            new ObjectOutputStream(ControlServerConnectClientThread.getServerConnectClientThread(message.getReceiver()).getSocket().getOutputStream());
                    oos.writeObject(message);   //轉發(fā)(注意:要使用正確的輸出流)
                    /*
                        拓展 : 如果用戶不在線,可以將消息保存到數(shù)據(jù)庫,實現(xiàn)離線留言/離線發(fā)文件。
                     */
                } else if (message.getMesType().equals(MessageType.MESSAGE_FILE_TRANSMISSION)) {
                    ObjectOutputStream oos =
                            new ObjectOutputStream(ControlServerConnectClientThread.getServerConnectClientThread(message.getReceiver()).getSocket().getOutputStream());
                    oos.writeObject(message);
                } else {
                    System.out.println("...other content");
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

? ? ? ? ? ? ? ? 3°?ControlServerConnectClientThread

package server.service;

import java.util.HashMap;
import java.util.Iterator;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @function : 用于管理服務端的線程
 */
public class ControlServerConnectClientThread {
    private static HashMap<String, ServerConnectClientThread> hashMap = new HashMap<>();

    public static HashMap<String, ServerConnectClientThread> getHashMap() {
        return hashMap;
    }

    //添加線程到集合中
    public static void addServerConnectClientThread(String userID, ServerConnectClientThread scct) {
        hashMap.put(userID, scct);
    }

    //根據(jù)用戶的id獲取對應的線程
    public static ServerConnectClientThread getServerConnectClientThread(String userID) {
        return hashMap.get(userID);
    }

    //獲取在線用戶列表
    public static String getOnlineFriends() {
        /*
            利用hashMap集合中的key是用戶id的特點,可以對hashMap對象進行遍歷,從而獲取用戶列表。
         */
        Iterator<String> iterator = hashMap.keySet().iterator();
        String onlineUsers = "";

        while (iterator.hasNext()) {
            onlineUsers += iterator.next() + " ";   //加空格對應客戶端的split方法。
        }

        return onlineUsers;
    }

    //刪除指定線程
    public static void removeServerConnectClientThread(String userID) {
        hashMap.remove(userID);
    }
}

? ? ? ? ? ? ? ? 4°?ChatFrame

package frame;

import server.service.ChatServer;

public class ChatFrame {
    public static void main(String[] args) {
        new ChatServer();
    }
}

? ? ? ? 3.公共部分 :?

? ? ? ? ? ? ? ? 1° Message

package mutual;

import java.io.Serializable;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @message : information that are transmitted
 */
public class Message implements Serializable {
    private static final long serialVersionUID = 1L;    //增強兼容性
    private String sendTime;    //發(fā)送時間
    private String sender;      //發(fā)送者
    private String receiver;    //接收者
    private String content;     //消息內(nèi)容
    private String mesType;     //消息類型
    //與文件相關的屬性
    private byte[] file;        //文件
    private int fileLen;        //文件大小
    private String souPath;     //數(shù)據(jù)源文件路徑
    private String desPath;     //目的地文件路徑

    public byte[] getFile() {
        return file;
    }

    public void setFile(byte[] file) {
        this.file = file;
    }

    public int getFileLen() {
        return fileLen;
    }

    public void setFileLen(int fileLen) {
        this.fileLen = fileLen;
    }

    public String getSouPath() {
        return souPath;
    }

    public void setSouPath(String souPath) {
        this.souPath = souPath;
    }

    public String getDesPath() {
        return desPath;
    }

    public void setDesPath(String desPath) {
        this.desPath = desPath;
    }

    public String getSendTime() {
        return sendTime;
    }

    public void setSendTime(String sendTime) {
        this.sendTime = sendTime;
    }

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getReceiver() {
        return receiver;
    }

    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getMesType() {
        return mesType;
    }

    public void setMesType(String mesType) {
        this.mesType = mesType;
    }
}

? ? ? ? ? ? ? ? 2° MessageType

package mutual;

/**
 * @author : Cyan_RA9
 * @version : 21.0
 * @meaning : Types of message
 */
public interface MessageType {
    //定義常量
    String MESSAGE_LOGIN_SUCCESS = "1";     //表示登錄成功
    String MESSAGE_LOGIN_FAIL = "0";        //表示登錄失敗
    String MESSAGE_COMMON_MES = "2";                //表示普通信息包
    String MESSAGE_COMMON_MES_ALL = "6";            //表示群發(fā)的信息包
    String MESSAGE_GET_ONLINE_FRIENDS = "3";        //表示請求拉取在線用戶的列表
    String MESSAGE_RETURN_ONLINE_FRIENDS = "4";     //表示返回在線用戶的列表
    String MESSAGE_CLIENT_EXIT = "5";               //表示客戶端請求退出系統(tǒng)
    String MESSAGE_FILE_TRANSMISSION = "8";         //表示文件傳輸
}

? ? ? ? ? ? ? ? 3° User

? ? ? ? ? ? ? ? User類并無改動,準備工作中的User類,即是最終的User類。

? ? ? ? System.out.println("END-------------------------------------------------------------------------------");文章來源地址http://www.zghlxwxcb.cn/news/detail-459788.html

到了這里,關于java 多用戶即時通信系統(tǒng)的實現(xiàn) 萬字詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內(nèi)容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 十大排序算法(java實現(xiàn)萬字詳解)

    十大排序算法(java實現(xiàn)萬字詳解)

    排序 :所謂排序,就是使一串記錄,按照其中的某個或某些的大小,遞增或遞減的排列起來的操作。 八大排序都屬于內(nèi)部排序,也就是只考慮數(shù)據(jù)量較小僅需要使用內(nèi)存的排序算法,他們之間關系如下: 什么是排序的穩(wěn)定性? 穩(wěn)定性:假定在待排序的記錄序列中,存

    2024年02月20日
    瀏覽(17)
  • 前端實現(xiàn)消息推送、即時通信、http簡介

    前端實現(xiàn)消息推送、即時通信、http簡介

    服務端主動向客戶端推送消息,使客戶端能夠即時接收到信息。 場景 頁面接收到點贊,消息提醒 聊天功能 彈幕功能 實時更新數(shù)據(jù)功能 短輪詢 瀏覽器(客戶端)每隔一段時間向服務器發(fā)送http請求,服務器端在收到請求后,不論是否有數(shù)據(jù)更新,都直接進行響應。 本質:客

    2024年02月09日
    瀏覽(24)
  • 騰訊TIM實現(xiàn)即時通信 v3+ts實踐

    騰訊TIM實現(xiàn)即時通信 v3+ts實踐

    目錄 初始化sdk 功能描述 初始化 準備 SDKAppID 調用初始化接口 ?監(jiān)聽事件 發(fā)送消息 創(chuàng)建消息 創(chuàng)建文本消息 登錄登出 功能描述 登錄 登出 銷毀 登錄設置 獲取會話列表 功能描述 獲取會話列表 獲取全量的會話列表 ?歷史消息 功能描述 拉取消息列表 分頁拉取指定會話的消息列

    2024年02月04日
    瀏覽(23)
  • 前端實現(xiàn)消息推送、即時通信、SSE、WebSocket、http簡介

    前端實現(xiàn)消息推送、即時通信、SSE、WebSocket、http簡介

    服務端主動向客戶端推送消息,使客戶端能夠即時接收到信息。 場景 頁面接收到點贊,消息提醒 聊天功能 彈幕功能 實時更新數(shù)據(jù)功能 短輪詢 瀏覽器(客戶端)每隔一段時間向服務器發(fā)送http請求,服務器端在收到請求后,不論是否有數(shù)據(jù)更新,都直接進行響應。 本質:客

    2024年02月16日
    瀏覽(19)
  • 分布式websocket即時通信(IM)系統(tǒng)保證消息可靠性【第八期】

    分布式websocket即時通信(IM)系統(tǒng)保證消息可靠性【第八期】

    b站上面本期視頻版本,觀看視頻食用更佳!點擊即可跳轉,找不到視頻可以直接搜索我 目前叫 呆呆呆呆夢 目前已經(jīng)寫的文章有。并且有對應視頻版本。 git項目地址 【IM即時通信系統(tǒng)(企聊聊)】點擊可跳轉 sprinboot單體項目升級成springcloud項目 【第一期】 前端項目技術選型

    2024年01月22日
    瀏覽(22)
  • uniapp集成騰訊即時通信IM,實現(xiàn)一對一聊天,支持文字、表情、語音、圖片、視頻

    uniapp集成騰訊即時通信IM,實現(xiàn)一對一聊天,支持文字、表情、語音、圖片、視頻

    原則 介紹 效果圖 uniapp集成騰訊即時通信IM,實現(xiàn)一對一聊天,支持文字、 使用方式 將文件放到相應的位置 app配置 main.js 配置 pages.json 配置 GenerateTestUserSig.js配置SDKAPPID和SECRETKEY 安裝 npm i 運行 下載源碼 聯(lián)系方式 查看文章

    2024年02月09日
    瀏覽(23)
  • ASP.NET基于TCP協(xié)議的簡單即時通信軟件的設計與實現(xiàn)(源代碼+論文)

    即時通 信 ( I nstant M essage), 由于其具有 實時性、跨平臺性、成本低、效率高等優(yōu)點 而受到廣泛的使用。設計并實現(xiàn) 一個能夠處理 多用 戶進行實時、安全的即時通 信系統(tǒng) 具有較強的現(xiàn)實意義。即時 通信 的底層 通信是 通過SOCKE T套接 字接口實現(xiàn) 的 。當前的主流UNIX系統(tǒng)和

    2024年02月09日
    瀏覽(60)
  • Spring Boot進階(49):實時通信不再是夢想,SpringBoot+WebSocket助你輕松實現(xiàn)前后端即時通訊!

    Spring Boot進階(49):實時通信不再是夢想,SpringBoot+WebSocket助你輕松實現(xiàn)前后端即時通訊!

    ????????在上一期,我對WebSocket進行了基礎及理論知識普及學習,WebSocket是一種基于TCP協(xié)議實現(xiàn)的全雙工通信協(xié)議,使用它可以實現(xiàn)實時通信,不必擔心HTTP協(xié)議的短連接問題。Spring Boot作為一款微服務框架,也提供了輕量級的WebSocket集成支持,本文將介紹如何在Spring Boot項

    2024年02月11日
    瀏覽(21)
  • Java 網(wǎng)絡編程詳解:實現(xiàn)網(wǎng)絡通信的核心技術

    網(wǎng)絡編程是指利用計算機網(wǎng)絡進行數(shù)據(jù)交換和通信的過程。它涉及到在不同主機之間傳輸數(shù)據(jù),并允許不同設備之間進行連接和通信。網(wǎng)絡編程不僅限于互聯(lián)網(wǎng),也可以包括局域網(wǎng)或廣域網(wǎng)等各種網(wǎng)絡環(huán)境。 在當今的互聯(lián)網(wǎng)時代,幾乎所有的應用都需要在不同設備之間進行數(shù)

    2024年02月11日
    瀏覽(20)
  • 【物聯(lián)網(wǎng)】深入理解CAN通信:原理、應用和實現(xiàn)(超詳細,萬字警告)

    CAN(Controller Area Network)是一種廣泛應用于汽車和工業(yè)領域的多節(jié)點通信協(xié)議。它具有高可靠性、高實時性和抗干擾能力強等特點,能夠滿足復雜系統(tǒng)中節(jié)點之間的數(shù)據(jù)傳輸需求。本文將全面介紹CAN通信的原理、應用和實現(xiàn),并提供實際開發(fā)中常用的方法和技巧,幫助讀者更

    2024年02月13日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包