重構 JAVA 聊天室 —— CS 模式的簡單架構實現

前言

自從開始弄起數據挖掘以後,已經好久沒寫過技術類的博客了,最近學校 JAVA 課設要求實現一個聊天室,想一想去年本身已經寫了一個了,可是有些要求到的功能我也沒實現,但看着原有的代碼想了想加功能好像有那麼點點難,因而就想着重構,也正好以前有看到別人寫的CS架構的代碼,感受擴展性還不錯,就試着寫了寫,寫完這個聊天室後,還同時寫了一個教學白板,那個白板基於這個聊天室的代碼僅僅花了三四個小時就完成了!因此,有一個好的架構仍是很重要的。下面就開始介紹我重構後的聊天室(代碼已上傳到githubgit

功能介紹

1. 用Java圖形用戶界面編寫聊天室服務器端和客戶端, 支持多個客戶端鏈接到一個服務器。每一個客戶端可以輸入帳號,包括註冊功能github

2. 能夠實現羣聊(聊天記錄顯示在全部客戶端界面)。緩存

3. 完成好友列表在各個客戶端上顯示,包括頭像和用戶名服務器

4. 能夠實現私人聊天,用戶能夠選擇某個其餘用戶,單獨發送信息,同時實現了文件傳輸,還能發送窗口振動架構

5. 服務器可以羣發系統消息,可以對用戶私發消息,可以強行讓某些用戶下線。app

6. 客戶端的上線下線要求可以在其餘客戶端上面實時刷新。框架

7.服務器可以查看在線用戶和註冊用戶socket

(加了下劃線的是課設要求以外的)ide

總體思路

數了數,總共寫了27個類,看起來仍是蠻多的,可是仔細看一看仍是很簡單的,我將在下面對其中部分進行解釋工具

工具類

在我以前寫的幾個socket通訊有關的項目裏,客戶端和服務器傳輸的都是字符串,而此次,我把要傳輸的內容封裝成了兩個類 Response 和 Request,客戶端向服務器發起請求,服務器向客戶端迴應,經過兩個類中包含的請求類型來判斷須要進行的操做,傳輸採用ObjectStream。仔細以看其實會發現,這兩個類內容很類似

Request

 1 public class Request implements Serializable {
 2     private static final long serialVersionUID = -1237018286305074249L;
 3     /** 請求傳送的數據類型 */
 4     private ResponseType type;
 5     /** 請求動做 */
 6     private String action;
 7     /** 請求域中的數據,name-value */
 8     private Map<String, Object> attributesMap;
 9 
10     public Request(){
11         this.attributesMap = new HashMap<String, Object>();
12     }
13 
14     public ResponseType getType() {
15         return type;
16     }
17 
18     public void setType(ResponseType type) {
19         this.type = type;
20     }
21 
22     public String getAction() {
23         return action;
24     }
25 
26     public void setAction(String action) {
27         this.action = action;
28     }
29 
30     public Map<String, Object> getAttributesMap() {
31         return attributesMap;
32     }
33 
34     public Object getAttribute(String name){
35         return this.attributesMap.get(name);
36     }
37 
38     public void setAttribute(String name, Object value){
39         this.attributesMap.put(name, value);
40     }
41 
42     public void removeAttribute(String name){
43         this.attributesMap.remove(name);
44     }
45 
46     public void clearAttribute(){
47         this.attributesMap.clear();
48     }
49 }
Request

Response

 1 public class Response implements Serializable {
 2     private static final long serialVersionUID = 1689541820872288991L;
 3     /** 響應狀態 */
 4     private ResponseStatus status;
 5     /** 響應數據的類型 */
 6     private ResponseType type;
 7 
 8     private Map<String, Object> dataMap;
 9 
10     /** 響應輸出流 */
11     private OutputStream outputStream;
12 
13     public Response(){
14         this.status = ResponseStatus.OK;
15         this.dataMap = new HashMap<String, Object>();
16     }
17 
18 
19     public ResponseStatus getStatus() {
20         return status;
21     }
22 
23     public void setStatus(ResponseStatus status) {
24         this.status = status;
25     }
26 
27     public ResponseType getType() {
28         return type;
29     }
30 
31     public void setType(ResponseType type) {
32         this.type = type;
33     }
34 
35     public Map<String, Object> getDataMap() {
36         return dataMap;
37     }
38 
39     public void setDataMap(Map<String, Object> dataMap) {
40         this.dataMap = dataMap;
41     }
42 
43     public OutputStream getOutputStream() {
44         return outputStream;
45     }
46 
47     public void setOutputStream(OutputStream outputStream) {
48         this.outputStream = outputStream;
49     }
50 
51     public void setData(String name, Object value){
52         this.dataMap.put(name, value);
53     }
54 
55     public Object getData(String name){
56         return this.dataMap.get(name);
57     }
58 
59     public void removeData(String name){
60         this.dataMap.remove(name);
61     }
62 
63     public void clearData(){
64         this.dataMap.clear();
65     }
66 }
Response

在以上兩個類中,傳輸的內容會包括文件和消息,對於文件和消息,咱們須要直到發送者和接受者是誰,須要知道發送時間等等,因此一樣封裝成了兩個類

FileInfo

 1 public class FileInfo implements Serializable {
 2     private static final long serialVersionUID = -5394575332459969403L;
 3     /** 消息接收者 */
 4     private User toUser;
 5     /** 消息發送者 */
 6     private User fromUser;
 7     /** 源文件名 */
 8     private String srcName;
 9     /** 發送時間 */
10     private Date sendTime;
11     /** 目標地IP */
12     private String destIp;
13     /** 目標地端口 */
14     private int destPort;
15     /** 目標文件名 */
16     private String destName;
17     public User getToUser() {
18         return toUser;
19     }
20     public void setToUser(User toUser) {
21         this.toUser = toUser;
22     }
23     public User getFromUser() {
24         return fromUser;
25     }
26     public void setFromUser(User fromUser) {
27         this.fromUser = fromUser;
28     }
29     public String getSrcName() {
30         return srcName;
31     }
32     public void setSrcName(String srcName) {
33         this.srcName = srcName;
34     }
35     public Date getSendTime() {
36         return sendTime;
37     }
38     public void setSendTime(Date sendTime) {
39         this.sendTime = sendTime;
40     }
41     public String getDestIp() {
42         return destIp;
43     }
44     public void setDestIp(String destIp) {
45         this.destIp = destIp;
46     }
47     public int getDestPort() {
48         return destPort;
49     }
50     public void setDestPort(int destPort) {
51         this.destPort = destPort;
52     }
53     public String getDestName() {
54         return destName;
55     }
56     public void setDestName(String destName) {
57         this.destName = destName;
58     }
59 }
FileInfo

Message

 1 public class Message implements Serializable {
 2     private static final long serialVersionUID = 1820192075144114657L;
 3     /** 消息接收者 */
 4     private User toUser;
 5     /** 消息發送者 */
 6     private User fromUser;
 7     /** 消息內容 */
 8     private String message;
 9     /** 發送時間 */
10     private Date sendTime;
11 
12 
13     public User getToUser() {
14         return toUser;
15     }
16     public void setToUser(User toUser) {
17         this.toUser = toUser;
18     }
19     public User getFromUser() {
20         return fromUser;
21     }
22     public void setFromUser(User fromUser) {
23         this.fromUser = fromUser;
24     }
25     public String getMessage() {
26         return message;
27     }
28     public void setMessage(String message) {
29         this.message = message;
30     }
31 
32     public Date getSendTime() {
33         return sendTime;
34     }
35     public void setSendTime(Date sendTime) {
36         this.sendTime = sendTime;
37     }
38 }
Message

User

User 類則用於存儲用戶信息,由於會用於傳輸,需實現序列化傳輸

  1 public class User implements Serializable {
  2     private static final long serialVersionUID = 5942011574971970871L;
  3     private long id;
  4     private String password;
  5     private String nickname;
  6     private int head;
  7     private char sex;
  8 
  9     public User(String password, String nickname, char sex, int head){
 10         this.password = password;
 11         this.sex = sex;
 12         this.head = head;
 13         if(nickname.equals("")||nickname==null)
 14         {
 15             this.nickname = "未命名";
 16         }else{
 17             this.nickname = nickname;
 18         }
 19     }
 20 
 21     public User(long id, String password){
 22         this.id = id;
 23         this.password = password;
 24     }
 25 
 26     public long getId(){
 27         return  id;
 28     }
 29 
 30     public void setId(long id){
 31         this.id = id;
 32     }
 33 
 34     public void setPassword(String password){
 35         this.password = password;
 36     }
 37 
 38     public String getPassword(){
 39         return password;
 40     }
 41 
 42     public void setSex(char sex){
 43         this.sex=sex;
 44     }
 45 
 46     public char getSex(){
 47         return this.sex;
 48     }
 49 
 50     public void setNickname(String nickname){
 51         this.nickname = nickname;
 52     }
 53 
 54     public String getNickname(){
 55         return this.nickname;
 56     }
 57 
 58     public void setHead(int head){
 59         this.head = head;
 60     }
 61 
 62     public int getHead(){
 63         return this.head;
 64     }
 65 
 66     public ImageIcon getHeadIcon(){
 67         ImageIcon image = new ImageIcon("images/"+head+".png");
 68         return image;
 69     }
 70 
 71     @Override
 72     public int hashCode() {
 73         final int prime = 31;
 74         int result = 1;
 75         result = prime * result + head;
 76         result = prime * result + (int)(id ^ (id >> 32));
 77         result = prime * result + ((nickname == null) ? 0 : nickname.hashCode());
 78         result = prime * result + ((password == null) ? 0 : password.hashCode());
 79         result = prime * result + sex;
 80         return result;
 81     }
 82 
 83     @Override
 84     public boolean equals(Object obj) {
 85         if(this == obj)
 86             return true;
 87         if(obj == null)
 88             return false;
 89         if(getClass() != obj.getClass())
 90             return false;
 91         User other = (User) obj;
 92         if(head != other.head || id != other.id || sex != other.sex)
 93             return false;
 94         if(nickname == null){
 95             if(other.nickname != null)
 96                 return false;
 97         }else if(!nickname.equals(other.nickname))
 98             return false;
 99         if(password == null){
100             if(other.password != null)
101                 return false;
102         }else if(!password.equals(other.password))
103             return  false;
104         return true;
105     }
106 
107     @Override
108     public String toString() {
109         return this.getClass().getName()
110                 + "[id=" + this.id
111                 + ",pwd=" + this.password
112                 + ",nickname=" + this.nickname
113                 + ",head=" + this.head
114                 + ",sex=" + this.sex
115                 + "]";
116     }
117 }
User

剩餘的類就不一一介紹了,若是有須要能夠到個人github上找到源代碼。

Server端

服務器端的代碼用到的類如上所示,其中 entity 中的兩個類和 ServerInfoFrame 僅用於界面,因此不會進行介紹。

UserService

用於用戶帳號管理,預先建立幾個帳號,而後存到文件中,每次服務器執行時,都會將文件中的帳號信息讀入,同時新建立的用戶帳號也會存入到文件中去。

  1 public class UserService {
  2     private static int idCount = 3; //id
  3 
  4     /** 新增用戶 */
  5     public void addUser(User user){
  6         user.setId(++idCount);
  7         List<User> users = loadAllUser();
  8         users.add(user);
  9         saveAllUser(users);
 10     }
 11 
 12     /** 用戶登陸 */
 13     public User login(long id, String password){
 14         User result = null;
 15         List<User> users = loadAllUser();
 16         for (User user : users) {
 17             if(id == user.getId() && password.equals(user.getPassword())){
 18                 result = user;
 19                 break;
 20             }
 21         }
 22         return result;
 23     }
 24 
 25     /** 根據ID加載用戶 */
 26     public User loadUser(long id){
 27         User result = null;
 28         List<User> users = loadAllUser();
 29         for (User user : users) {
 30             if(id == user.getId()){
 31                 result = user;
 32                 break;
 33             }
 34         }
 35         return result;
 36     }
 37 
 38 
 39     /** 加載全部用戶 */
 40     @SuppressWarnings("unchecked")
 41     public List<User> loadAllUser() {
 42         List<User> list = null;
 43         ObjectInputStream ois = null;
 44         try {
 45             ois = new ObjectInputStream(
 46                     new FileInputStream(
 47                             DataBuffer.configProp.getProperty("dbpath")));
 48 
 49             list = (List<User>)ois.readObject();
 50         } catch (Exception e) {
 51             e.printStackTrace();
 52         }finally{
 53             IOUtil.close(ois);
 54         }
 55         return list;
 56     }
 57 
 58     private void saveAllUser(List<User> users) {
 59         ObjectOutputStream oos = null;
 60         try {
 61             oos = new ObjectOutputStream(
 62                     new FileOutputStream(
 63                             DataBuffer.configProp.getProperty("dbpath")));
 64             //寫回用戶信息
 65             oos.writeObject(users);
 66             oos.flush();
 67         } catch (Exception e) {
 68             e.printStackTrace();
 69         }finally{
 70             IOUtil.close(oos);
 71         }
 72     }
 73 
 74 
 75 
 76     /** 初始化幾個測試用戶 */
 77     public void initUser(){
 78         User user = new User("admin", "Admin", 'm', 0);
 79         user.setId(1);
 80 
 81         User user2 = new User("123", "yong", 'm', 1);
 82         user2.setId(2);
 83 
 84         User user3 = new User("123", "anni", 'f', 2);
 85         user3.setId(3);
 86 
 87         List<User> users = new CopyOnWriteArrayList<User>();
 88         users.add(user);
 89         users.add(user2);
 90         users.add(user3);
 91 
 92         this.saveAllUser(users);
 93     }
 94 
 95     public static void main(String[] args){
 96         new UserService().initUser();
 97         List<User> users = new UserService().loadAllUser();
 98         for (User user : users) {
 99             System.out.println(user);
100         }
101     }
102 }
UserService

DataBuffer

用於服務器端從文件中讀取數據,進行緩存

 1 public class DataBuffer {
 2     // 服務器端套接字
 3     public static ServerSocket serverSocket;
 4     //在線用戶的IO Map
 5     public static Map<Long, OnlineClientIOCache> onlineUserIOCacheMap;
 6     //在線用戶Map
 7     public static Map<Long, User> onlineUsersMap;
 8     //服務器配置參數屬性集
 9     public static Properties configProp;
10     // 已註冊用戶表的Model
11     public static RegistedUserTableModel registedUserTableModel;
12     // 當前在線用戶表的Model
13     public static OnlineUserTableModel onlineUserTableModel;
14     // 當前服務器所在系統的屏幕尺寸
15     public static Dimension screenSize;
16 
17     static{
18         // 初始化
19         onlineUserIOCacheMap = new ConcurrentSkipListMap<Long,OnlineClientIOCache>();
20         onlineUsersMap = new ConcurrentSkipListMap<Long, User>();
21         configProp = new Properties();
22         registedUserTableModel = new RegistedUserTableModel();
23         onlineUserTableModel = new OnlineUserTableModel();
24         screenSize = Toolkit.getDefaultToolkit().getScreenSize();
25 
26         // 加載服務器配置文件
27         try {
28             configProp.load(Thread.currentThread()
29                     .getContextClassLoader()
30                     .getResourceAsStream("serverconfig.properties"));
31         } catch (IOException e) {
32             e.printStackTrace();
33         }
34     }
35 
36 }
DataBuffer

RequestProcessor

這時服務器端最重要的一個類了,用於處理客戶端發來的消息,並進行回覆,對於每一項操做的實現原理無非就是服務器處理內部數據或是向指定客戶端發送消息,詳細看代碼註釋

  1 public class RequestProcessor implements Runnable {
  2     private Socket currentClientSocket;  //當前正在請求服務器的客戶端Socket
  3 
  4     public RequestProcessor(Socket currentClientSocket){
  5         this.currentClientSocket = currentClientSocket;
  6     }
  7 
  8     public void run() {
  9         boolean flag = true; //是否不間斷監聽
 10         try{
 11             OnlineClientIOCache currentClientIOCache = new OnlineClientIOCache(
 12                     new ObjectInputStream(currentClientSocket.getInputStream()),
 13                     new ObjectOutputStream(currentClientSocket.getOutputStream()));
 14             while(flag){ //不停地讀取客戶端發過來的請求對象
 15                 //從請求輸入流中讀取到客戶端提交的請求對象
 16                 Request request = (Request)currentClientIOCache.getOis().readObject();
 17                 System.out.println("Server讀取了客戶端的請求:" + request.getAction());
 18 
 19                 String actionName = request.getAction();   //獲取請求中的動做
 20                 if(actionName.equals("userRegiste")){      //用戶註冊
 21                     registe(currentClientIOCache, request);
 22                 }else if(actionName.equals("userLogin")){  //用戶登陸
 23                     login(currentClientIOCache, request);
 24                 }else if("exit".equals(actionName)){       //請求斷開鏈接
 25                     flag = logout(currentClientIOCache, request);
 26                 }else if("chat".equals(actionName)){       //聊天
 27                     chat(request);
 28                 }else if("shake".equals(actionName)){      //振動
 29                     shake(request);
 30                 }else if("toSendFile".equals(actionName)){ //準備發送文件
 31                     toSendFile(request);
 32                 }else if("agreeReceiveFile".equals(actionName)){ //贊成接收文件
 33                     agreeReceiveFile(request);
 34                 }else if("refuseReceiveFile".equals(actionName)){ //拒絕接收文件
 35                     refuseReceiveFile(request);
 36                 }
 37             }
 38         }catch(Exception e){
 39             e.printStackTrace();
 40         }
 41     }
 42 
 43     /** 拒絕接收文件 */
 44     private void refuseReceiveFile(Request request) throws IOException {
 45         FileInfo sendFile = (FileInfo)request.getAttribute("sendFile");
 46         Response response = new Response();  //建立一個響應對象
 47         response.setType(ResponseType.REFUSERECEIVEFILE);
 48         response.setData("sendFile", sendFile);
 49         response.setStatus(ResponseStatus.OK);
 50         //向請求方的輸出流輸出響應
 51         OnlineClientIOCache ocic = DataBuffer.onlineUserIOCacheMap.get(sendFile.getFromUser().getId());
 52         this.sendResponse(ocic, response);
 53     }
 54 
 55     /** 贊成接收文件 */
 56     private void agreeReceiveFile(Request request) throws IOException {
 57         FileInfo sendFile = (FileInfo)request.getAttribute("sendFile");
 58         //向請求方(發送方)的輸出流輸出響應
 59         Response response = new Response();  //建立一個響應對象
 60         response.setType(ResponseType.AGREERECEIVEFILE);
 61         response.setData("sendFile", sendFile);
 62         response.setStatus(ResponseStatus.OK);
 63         OnlineClientIOCache sendIO = DataBuffer.onlineUserIOCacheMap.get(sendFile.getFromUser().getId());
 64         this.sendResponse(sendIO, response);
 65 
 66         //向接收方發出接收文件的響應
 67         Response response2 = new Response();  //建立一個響應對象
 68         response2.setType(ResponseType.RECEIVEFILE);
 69         response2.setData("sendFile", sendFile);
 70         response2.setStatus(ResponseStatus.OK);
 71         OnlineClientIOCache receiveIO = DataBuffer.onlineUserIOCacheMap.get(sendFile.getToUser().getId());
 72         this.sendResponse(receiveIO, response2);
 73     }
 74 
 75     /** 客戶端退出 */
 76     public boolean logout(OnlineClientIOCache oio, Request request) throws IOException{
 77         System.out.println(currentClientSocket.getInetAddress().getHostAddress()
 78                 + ":" + currentClientSocket.getPort() + "走了");
 79 
 80         User user = (User)request.getAttribute("user");
 81         //把當前上線客戶端的IO從Map中刪除
 82         DataBuffer.onlineUserIOCacheMap.remove(user.getId());
 83         //從在線用戶緩存Map中刪除當前用戶
 84         DataBuffer.onlineUsersMap.remove(user.getId());
 85 
 86         Response response = new Response();  //建立一個響應對象
 87         response.setType(ResponseType.LOGOUT);
 88         response.setData("logoutUser", user);
 89         oio.getOos().writeObject(response);  //把響應對象往客戶端寫
 90         oio.getOos().flush();
 91         currentClientSocket.close();  //關閉這個客戶端Socket
 92 
 93         DataBuffer.onlineUserTableModel.remove(user.getId()); //把當前下線用戶從在線用戶表Model中刪除
 94         iteratorResponse(response);//通知全部其它在線客戶端
 95 
 96         return false;  //斷開監聽
 97     }
 98     /** 註冊 */
 99     public void registe(OnlineClientIOCache oio, Request request) throws IOException {
100         User user = (User)request.getAttribute("user");
101         UserService userService = new UserService();
102         userService.addUser(user);
103 
104         Response response = new Response();  //建立一個響應對象
105         response.setStatus(ResponseStatus.OK);
106         response.setData("user", user);
107 
108         oio.getOos().writeObject(response);  //把響應對象往客戶端寫
109         oio.getOos().flush();
110 
111         //把新註冊用戶添加到RegistedUserTableModel中
112         DataBuffer.registedUserTableModel.add(new String[]{
113                 String.valueOf(user.getId()),
114                 user.getPassword(),
115                 user.getNickname(),
116                 String.valueOf(user.getSex())
117         });
118     }
119 
120     /** 登陸 */
121     public void login(OnlineClientIOCache currentClientIO, Request request) throws IOException {
122         String idStr = (String)request.getAttribute("id");
123         String password = (String) request.getAttribute("password");
124         UserService userService = new UserService();
125         User user = userService.login(Long.parseLong(idStr), password);
126 
127         Response response = new Response();  //建立一個響應對象
128         if(null != user){
129             if(DataBuffer.onlineUsersMap.containsKey(user.getId())){ //用戶已經登陸了
130                 response.setStatus(ResponseStatus.OK);
131                 response.setData("msg", "該 用戶已經在別處上線了!");
132                 currentClientIO.getOos().writeObject(response);  //把響應對象往客戶端寫
133                 currentClientIO.getOos().flush();
134             }else { //正確登陸
135                 DataBuffer.onlineUsersMap.put(user.getId(), user); //添加到在線用戶
136 
137                 //設置在線用戶
138                 response.setData("onlineUsers",
139                         new CopyOnWriteArrayList<User>(DataBuffer.onlineUsersMap.values()));
140 
141                 response.setStatus(ResponseStatus.OK);
142                 response.setData("user", user);
143                 currentClientIO.getOos().writeObject(response);  //把響應對象往客戶端寫
144                 currentClientIO.getOos().flush();
145 
146                 //通知其它用戶有人上線了
147                 Response response2 = new Response();
148                 response2.setType(ResponseType.LOGIN);
149                 response2.setData("loginUser", user);
150                 iteratorResponse(response2);
151 
152                 //把當前上線的用戶IO添加到緩存Map中
153                 DataBuffer.onlineUserIOCacheMap.put(user.getId(),currentClientIO);
154 
155                 //把當前上線用戶添加到OnlineUserTableModel中
156                 DataBuffer.onlineUserTableModel.add(
157                         new String[]{String.valueOf(user.getId()),
158                                 user.getNickname(),
159                                 String.valueOf(user.getSex())});
160             }
161         }else{ //登陸失敗
162             response.setStatus(ResponseStatus.OK);
163             response.setData("msg", "帳號或密碼不正確!");
164             currentClientIO.getOos().writeObject(response);
165             currentClientIO.getOos().flush();
166         }
167     }
168 
169     /** 聊天 */
170     public void chat(Request request) throws IOException {
171         Message msg = (Message)request.getAttribute("msg");
172         Response response = new Response();
173         response.setStatus(ResponseStatus.OK);
174         response.setType(ResponseType.CHAT);
175         response.setData("txtMsg", msg);
176 
177         if(msg.getToUser() != null){ //私聊:只給私聊的對象返回響應
178             OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());
179             sendResponse(io, response);
180         }else{  //羣聊:給除了發消息的全部客戶端都返回響應
181             for(Long id : DataBuffer.onlineUserIOCacheMap.keySet()){
182                 if(msg.getFromUser().getId() == id ){    continue; }
183                 sendResponse(DataBuffer.onlineUserIOCacheMap.get(id), response);
184             }
185         }
186     }
187 
188     /*廣播*/
189     public static void board(String str) throws IOException {
190         User user = new User(1,"admin");
191         Message msg = new Message();
192         msg.setFromUser(user);
193         msg.setSendTime(new Date());
194 
195         DateFormat df = new SimpleDateFormat("HH:mm:ss");
196         StringBuffer sb = new StringBuffer();
197         sb.append(" ").append(df.format(msg.getSendTime())).append(" ");
198         sb.append("系統通知\n  "+str+"\n");
199         msg.setMessage(sb.toString());
200 
201         Response response = new Response();
202         response.setStatus(ResponseStatus.OK);
203         response.setType(ResponseType.BOARD);
204         response.setData("txtMsg", msg);
205 
206         for (Long id : DataBuffer.onlineUserIOCacheMap.keySet()) {
207             sendResponse_sys(DataBuffer.onlineUserIOCacheMap.get(id), response);
208         }
209     }
210 
211     /*踢除用戶*/
212     public static void remove(User user_) throws IOException{
213         User user = new User(1,"admin");
214         Message msg = new Message();
215         msg.setFromUser(user);
216         msg.setSendTime(new Date());
217         msg.setToUser(user_);
218 
219         StringBuffer sb = new StringBuffer();
220         DateFormat df = new SimpleDateFormat("HH:mm:ss");
221         sb.append(" ").append(df.format(msg.getSendTime())).append(" ");
222         sb.append("系統通知您\n  "+"您被強制下線"+"\n");
223         msg.setMessage(sb.toString());
224 
225         Response response = new Response();
226         response.setStatus(ResponseStatus.OK);
227         response.setType(ResponseType.REMOVE);
228         response.setData("txtMsg", msg);
229 
230         OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());
231         sendResponse_sys(io, response);
232     }
233 
234     /*私信*/
235     public static void chat_sys(String str,User user_) throws IOException{
236         User user = new User(1,"admin");
237         Message msg = new Message();
238         msg.setFromUser(user);
239         msg.setSendTime(new Date());
240         msg.setToUser(user_);
241 
242         DateFormat df = new SimpleDateFormat("HH:mm:ss");
243         StringBuffer sb = new StringBuffer();
244         sb.append(" ").append(df.format(msg.getSendTime())).append(" ");
245         sb.append("系統通知您\n  "+str+"\n");
246         msg.setMessage(sb.toString());
247 
248         Response response = new Response();
249         response.setStatus(ResponseStatus.OK);
250         response.setType(ResponseType.CHAT);
251         response.setData("txtMsg", msg);
252 
253         OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());
254         sendResponse_sys(io, response);
255     }
256 
257     /** 發送振動 */
258     public void shake(Request request)throws IOException {
259         Message msg = (Message) request.getAttribute("msg");
260 
261         DateFormat df = new SimpleDateFormat("HH:mm:ss");
262         StringBuffer sb = new StringBuffer();
263         sb.append(" ").append(msg.getFromUser().getNickname())
264                 .append("(").append(msg.getFromUser().getId()).append(") ")
265                 .append(df.format(msg.getSendTime())).append("\n  給您發送了一個窗口抖動\n");
266         msg.setMessage(sb.toString());
267 
268         Response response = new Response();
269         response.setStatus(ResponseStatus.OK);
270         response.setType(ResponseType.SHAKE);
271         response.setData("ShakeMsg", msg);
272 
273         OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());
274         sendResponse(io, response);
275     }
276 
277     /** 準備發送文件 */
278     public void toSendFile(Request request)throws IOException{
279         Response response = new Response();
280         response.setStatus(ResponseStatus.OK);
281         response.setType(ResponseType.TOSENDFILE);
282         FileInfo sendFile = (FileInfo)request.getAttribute("file");
283         response.setData("sendFile", sendFile);
284         //給文件接收方轉發文件發送方的請求
285         OnlineClientIOCache ioCache = DataBuffer.onlineUserIOCacheMap.get(sendFile.getToUser().getId());
286         sendResponse(ioCache, response);
287     }
288 
289     /** 給全部在線客戶都發送響應 */
290     private void iteratorResponse(Response response) throws IOException {
291         for(OnlineClientIOCache onlineUserIO : DataBuffer.onlineUserIOCacheMap.values()){
292             ObjectOutputStream oos = onlineUserIO.getOos();
293             oos.writeObject(response);
294             oos.flush();
295         }
296     }
297 
298     /** 向指定客戶端IO的輸出流中輸出指定響應 */
299     private void sendResponse(OnlineClientIOCache onlineUserIO, Response response)throws IOException {
300         ObjectOutputStream oos = onlineUserIO.getOos();
301         oos.writeObject(response);
302         oos.flush();
303     }
304 
305     /** 向指定客戶端IO的輸出流中輸出指定響應 */
306     private static void sendResponse_sys(OnlineClientIOCache onlineUserIO, Response response)throws IOException {
307         ObjectOutputStream oos = onlineUserIO.getOos();
308         oos.writeObject(response);
309         oos.flush();
310     }
311 }
RequestProcessor

Client端

我的感受作這類項目時,難點是在客戶端,以前考慮了好久關於界面的切換,由於涉及到了登錄界面、註冊界面、聊天界面,因此如何將客戶端的socket與這幾個界面聯繫起來是個值得思考的問題。同時,也思考了很久好友列表的展現方法,最後想到了TIM。下面介紹一下其中的幾個類

ClientThread

客戶端線程,一個線程表示一個用戶,處理服務器發來的消息,在裏面用了 currentFrame 這個變量來表示當前窗口。

  1 public class ClientThread extends Thread {
  2     private JFrame currentFrame;  //當前窗體
  3 
  4     public ClientThread(JFrame frame){
  5         currentFrame = frame;
  6     }
  7 
  8     public void run() {
  9         try {
 10             while (DataBuffer.clientSeocket.isConnected()) {
 11                 Response response = (Response) DataBuffer.ois.readObject();
 12                 ResponseType type = response.getType();
 13 
 14                 System.out.println("獲取了響應內容:" + type);
 15                 if (type == ResponseType.LOGIN) {
 16                     User newUser = (User)response.getData("loginUser");
 17                     DataBuffer.onlineUserListModel.addElement(newUser);
 18 
 19                     ChatFrame.onlineCountLbl.setText(
 20                             "在線用戶列表("+ DataBuffer.onlineUserListModel.getSize() +")");
 21                     ClientUtil.appendTxt2MsgListArea("【系統消息】用戶"+newUser.getNickname() + "上線了!\n");
 22                 }else if(type == ResponseType.LOGOUT){
 23                     User newUser = (User)response.getData("logoutUser");
 24                     DataBuffer.onlineUserListModel.removeElement(newUser);
 25 
 26                     ChatFrame.onlineCountLbl.setText(
 27                             "在線用戶列表("+ DataBuffer.onlineUserListModel.getSize() +")");
 28                     ClientUtil.appendTxt2MsgListArea("【系統消息】用戶"+newUser.getNickname() + "下線了!\n");
 29 
 30                 }else if(type == ResponseType.CHAT){ //聊天
 31                     Message msg = (Message)response.getData("txtMsg");
 32                     ClientUtil.appendTxt2MsgListArea(msg.getMessage());
 33                 }else if(type == ResponseType.SHAKE){ //振動
 34                     Message msg = (Message)response.getData("ShakeMsg");
 35                     ClientUtil.appendTxt2MsgListArea(msg.getMessage());
 36                     new JFrameShaker(this.currentFrame).startShake();
 37                 }else if(type == ResponseType.TOSENDFILE){ //準備發送文件
 38                     toSendFile(response);
 39                 }else if(type == ResponseType.AGREERECEIVEFILE){ //對方贊成接收文件
 40                     sendFile(response);
 41                 }else if(type == ResponseType.REFUSERECEIVEFILE){ //對方拒絕接收文件
 42                     ClientUtil.appendTxt2MsgListArea("【文件消息】對方拒絕接收,文件發送失敗!\n");
 43                 }else if(type == ResponseType.RECEIVEFILE){ //開始接收文件
 44                     receiveFile(response);
 45                 }else if(type == ResponseType.BOARD){
 46                     Message msg = (Message)response.getData("txtMsg");
 47                     ClientUtil.appendTxt2MsgListArea(msg.getMessage());
 48                 }else if(type == ResponseType.REMOVE){
 49                     ChatFrame.remove();
 50                 }
 51             }
 52         } catch (IOException e) {
 53             //e.printStackTrace();
 54         } catch (ClassNotFoundException e) {
 55             e.printStackTrace();
 56         }
 57     }
 58 
 59     /** 發送文件 */
 60     private void sendFile(Response response) {
 61         final FileInfo sendFile = (FileInfo)response.getData("sendFile");
 62 
 63         BufferedInputStream bis = null;
 64         BufferedOutputStream bos = null;
 65         Socket socket = null;
 66         try {
 67             socket = new Socket(sendFile.getDestIp(),sendFile.getDestPort());//套接字鏈接
 68             bis = new BufferedInputStream(new FileInputStream(sendFile.getSrcName()));//文件讀入
 69             bos = new BufferedOutputStream(socket.getOutputStream());//文件寫出
 70 
 71             byte[] buffer = new byte[1024];
 72             int n = -1;
 73             while ((n = bis.read(buffer)) != -1){
 74                 bos.write(buffer, 0, n);
 75             }
 76             bos.flush();
 77             synchronized (this) {
 78                 ClientUtil.appendTxt2MsgListArea("【文件消息】文件發送完畢!\n");
 79             }
 80         } catch (IOException e) {
 81             e.printStackTrace();
 82         }finally{
 83             IOUtil.close(bis,bos);
 84             SocketUtil.close(socket);
 85         }
 86     }
 87 
 88     /** 接收文件 */
 89     private void receiveFile(Response response) {
 90         final FileInfo sendFile = (FileInfo)response.getData("sendFile");
 91 
 92         BufferedInputStream bis = null;
 93         BufferedOutputStream bos = null;
 94         ServerSocket serverSocket = null;
 95         Socket socket = null;
 96         try {
 97             serverSocket = new ServerSocket(sendFile.getDestPort());
 98             socket = serverSocket.accept(); //接收
 99             bis = new BufferedInputStream(socket.getInputStream());//緩衝讀
100             bos = new BufferedOutputStream(new FileOutputStream(sendFile.getDestName()));//緩衝寫出
101 
102             byte[] buffer = new byte[1024];
103             int n = -1;
104             while ((n = bis.read(buffer)) != -1){
105                 bos.write(buffer, 0, n);
106             }
107             bos.flush();
108             synchronized (this) {
109                 ClientUtil.appendTxt2MsgListArea("【文件消息】文件接收完畢!存放在["
110                         + sendFile.getDestName()+"]\n");
111             }
112 
113         } catch (IOException e) {
114             e.printStackTrace();
115         }finally{
116             IOUtil.close(bis,bos);
117             SocketUtil.close(socket);
118             SocketUtil.close(serverSocket);
119         }
120     }
121 
122     /** 準備發送文件     */
123     private void toSendFile(Response response) {
124         FileInfo sendFile = (FileInfo)response.getData("sendFile");
125 
126         String fromName = sendFile.getFromUser().getNickname()
127                 + "(" + sendFile.getFromUser().getId() + ")";
128         String fileName = sendFile.getSrcName()
129                 .substring(sendFile.getSrcName().lastIndexOf(File.separator)+1);
130 
131         int select = JOptionPane.showConfirmDialog(this.currentFrame,
132                 fromName + " 向您發送文件 [" + fileName+ "]!\n贊成接收嗎?",
133                 "接收文件", JOptionPane.YES_NO_OPTION);
134         try {
135             Request request = new Request();
136             request.setAttribute("sendFile", sendFile);
137 
138             if (select == JOptionPane.YES_OPTION) {
139                 JFileChooser jfc = new JFileChooser();
140                 jfc.setSelectedFile(new File(fileName));
141                 int result = jfc.showSaveDialog(this.currentFrame);
142 
143                 if (result == JFileChooser.APPROVE_OPTION){
144                     //設置目的地文件名
145                     sendFile.setDestName(jfc.getSelectedFile().getCanonicalPath());
146                     //設置目標地的IP和接收文件的端口
147                     sendFile.setDestIp(DataBuffer.ip);
148                     sendFile.setDestPort(DataBuffer.RECEIVE_FILE_PORT);
149 
150                     request.setAction("agreeReceiveFile");
151 //                    receiveFile(response);
152                     ClientUtil.appendTxt2MsgListArea("【文件消息】您已贊成接收來自 "
153                             + fromName +" 的文件,正在接收文件 ...\n");
154                 } else {
155                     request.setAction("refuseReceiveFile");
156                     ClientUtil.appendTxt2MsgListArea("【文件消息】您已拒絕接收來自 "
157                             + fromName +" 的文件!\n");
158                 }
159             } else {
160                 request.setAction("refuseReceiveFile");
161                 ClientUtil.appendTxt2MsgListArea("【文件消息】您已拒絕接收來自 "
162                         + fromName +" 的文件!\n");
163             }
164 
165             ClientUtil.sendTextRequest2(request);
166         } catch (IOException e) {
167             e.printStackTrace();
168         }
169     }
170 }
ClientThread

ClientUtil

用於客戶端向服務器發送消息

 1 public class ClientUtil {
 2 
 3     /** 發送請求對象,主動接收響應 */
 4     public static Response sendTextRequest(Request request) throws IOException {
 5         Response response = null;
 6         try {
 7             // 發送請求
 8             DataBuffer.oos.writeObject(request);
 9             DataBuffer.oos.flush();
10             System.out.println("客戶端發送了請求對象:" + request.getAction());
11 
12             if(!"exit".equals(request.getAction())){
13                 // 獲取響應
14                 response = (Response) DataBuffer.ois.readObject();
15                 System.out.println("客戶端獲取到了響應對象:" + response.getStatus());
16             }else{
17                 System.out.println("客戶端斷開鏈接了");
18             }
19         } catch (IOException e) {
20             throw e;
21         } catch (ClassNotFoundException e) {
22             e.printStackTrace();
23         }
24         return response;
25     }
26 
27     /** 發送請求對象,不主動接收響應 */
28     public static void sendTextRequest2(Request request) throws IOException {
29         try {
30             DataBuffer.oos.writeObject(request); // 發送請求
31             DataBuffer.oos.flush();
32             System.out.println("客戶端發送了請求對象:" + request.getAction());
33         } catch (IOException e) {
34             throw e;
35         }
36     }
37 
38     /** 把指定文本添加到消息列表文本域中 */
39     public static void appendTxt2MsgListArea(String txt) {
40         ChatFrame.msgListArea.append(txt);
41         //把光標定位到文本域的最後一行
42         ChatFrame.msgListArea.setCaretPosition(ChatFrame.msgListArea.getDocument().getLength());
43     }
44 }
ClientUtil

總結

大致上的細節我就介紹這些,剩下的大部分都是界面相關的代碼,我把整個項目放到github上了,感受如今用的這個框架能夠適應學校內佈置的涉及到CS架構的一切任務,學會了,別人要好幾天搞定的本身幾個小時就好了,並且看起來還會比別人的舒服的多。下一篇將會介紹利用這個框架實現另外一個項目——教學白板。

相關文章
相關標籤/搜索