該程序是基於C/S架構模式,即服務端/客戶端模式(這種架構模式維護起來既耗時又耗人力物力,不過也不是絕對的哈),其中使用了Java多線程中的一些經常使用API,例如ConcurrentHashMap(併發HashMap)、還有javax.swing包和java.awt包中的一些GUI組件,由於這是一個基於Java的GUI圖形界面聊天室,雖說如今已經不多有人用GUI來開發軟件了。java
其實有兩大因素:數據庫
一、開發週期長,圖形界面的繪製太耗時,並且移植性也不太好編程
二、佈局繁瑣,樣式不太美觀,若是非要調整的好看,那也要花很大功夫,不利於現代軟件的快速開發,由於如今的軟件更新迭代太快服務器
一、登陸:登陸聊天室,每次登陸只需填寫一個暱稱便可網絡
二、羣聊:全部登陸的用戶處於一個聊天室內,你們能夠羣聊,即每一個人發送的消息都會被全部人可見,後續會增長私聊功能和@指定用戶多線程
三、服務器端能夠看到每一個用戶登陸以後的信息,還能夠看到每一個人發送的羣消息架構
這個程序包含了六個類,分別是:Server.java、ClientState.java、Message.java、Type.java、UserThread.java、Client.java併發
Server.javaapp
1 package chat; 2 3 import static java.util.concurrent.Executors.newFixedThreadPool; 4 import java.awt.Font; 5 import java.awt.event.ActionEvent; 6 import java.awt.event.ActionListener; 7 import java.awt.event.KeyAdapter; 8 import java.awt.event.KeyEvent; 9 import java.io.IOException; 10 import java.io.ObjectOutputStream; 11 import java.net.ServerSocket; 12 import java.net.Socket; 13 import java.util.Map; 14 import java.util.Set; 15 import java.util.concurrent.ConcurrentHashMap; 16 import java.util.concurrent.ExecutorService; 17 import javax.swing.BorderFactory; 18 import javax.swing.DefaultListModel; 19 import javax.swing.JButton; 20 import javax.swing.JFrame; 21 import javax.swing.JList; 22 import javax.swing.JOptionPane; 23 import javax.swing.JScrollPane; 24 import javax.swing.JTextArea; 25 import javax.swing.JTextField; 26 import javax.swing.SwingUtilities; 27 import javax.swing.WindowConstants; 28 29 /** 30 * 單例服務器 31 * 32 * 需求:實現服務器與客戶端互發消息 33 * 步驟: 34 * 1.開啓服務器 35 * 2.監聽客戶端鏈接 36 * 3.提示客戶端鏈接成功 37 * 4.開啓線程給每一個鏈接該服務器的用戶 38 * 5.轉發各個用戶的消息 39 * 40 * 服務器主要功能: 41 * 1.廣播全部在線客戶端 42 * 2.羣聊(轉發一個客戶端的消息到其餘有客戶端上) 43 * 3.私聊(暫時未寫) 44 * 45 * @author 14715 46 * 47 */ 48 public class Server { 49 50 // 服務器套接字 51 private ServerSocket server = null; 52 53 private static Map<String,UserThread> onlineUsers = new ConcurrentHashMap<>();; 54 55 private static JFrame f = null; 56 57 private JButton startBtn = null; 58 59 private JButton stopBtn = null; 60 61 private static JTextArea msgTa = null; 62 63 private static DefaultListModel<String> dlm = null; 64 65 private static JList<?> onlineList = null; 66 67 private static JTextField sendTf = null; 68 69 private JScrollPane spMsg = null; 70 71 private JScrollPane spOnline = null; 72 73 private JButton sendBtn = null; 74 75 private ExecutorService es = newFixedThreadPool(5); 76 77 //標識服務器的狀態,爲false表示關閉狀態,反之,則開啓狀態 78 private boolean state = false; 79 80 /** 81 * 啓動服務器 82 */ 83 private void startServer() { 84 try { 85 // 開啓服務 86 server = new ServerSocket(12345); 87 //打印信息到控制檯 88 System.out.println("QQ服務器已啓動------------正在等待客戶端鏈接!!!"); 89 //刷新UI 90 SwingUtilities.invokeLater(() -> { 91 Server.flushUI(null, Type.SERVERSTART, null); 92 }); 93 //開啓線程,監聽客戶端 94 //修改標識 95 state = true; 96 es.execute(() -> { 97 acceptClient(); 98 }); 99 //將啓動服務器按鈕設置爲不可用 100 startBtn.setEnabled(false); 101 //將關閉服務器按鈕設置爲可用 102 stopBtn.setEnabled(true); 103 } catch (IOException e) { 104 JOptionPane.showMessageDialog(f,"服務器已經啓動過了,不可重複啓動","舒適提示",1); 105 } 106 } 107 108 /** 109 * 關閉服務器 110 * 注意:關閉服務器必須將全部除了繪製UI界面的線程都中止,才中止了服務 111 */ 112 public void closeServer() { 113 try { 114 //修改狀態 115 state = false; 116 //關閉服務器套接字 117 server.close(); 118 server = null; 119 //將啓動服務器按鈕設置爲可用 120 startBtn.setEnabled(true); 121 //將關閉服務器按鈕設置爲不可用 122 stopBtn.setEnabled(false); 123 //遍歷HashMap 124 Set<String> keys = onlineUsers.keySet(); 125 // 關閉全部用戶線程 126 // 不要在遍歷集合的時候去刪除該集合,這樣程序會拋出java.util.ConcurrentModificationException併發修改異常 127 // 注意:這裏咱們必定不能使用之前的HashMap了,而是要使用jdk中爲咱們提供的juc併發包下的ConcurrentHashMap 128 for (String s : keys) { 129 UserThread ut = (UserThread) onlineUsers.get(s); 130 //此處要特別注意:絕對不能用線程來判斷爲null,由於調用這個方法時,線程還沒結束。 131 if (ut != null) { 132 // 注意:修改標誌必定要首先執行,否則該用戶線程又會繼續等待客戶端發送消息 133 ut.setStop(true); 134 // 關閉該線程對應的客戶端套接字 135 Socket client = ut.getClient(); 136 try { 137 if (client != null) { 138 client.close(); 139 System.out.println(client + "已斷開"); 140 ut.setClient(null); 141 } 142 } catch (IOException e) { 143 System.out.println("2222222222222222"); 144 e.printStackTrace(); 145 } 146 System.out.println(ut.getNickName() + "用戶線程正在被關閉!"); 147 } 148 } 149 // 移除Map集合中的全部用戶線程 150 for (String s : keys) { 151 onlineUsers.remove(s); 152 } 153 System.out.println("服務器正在關閉-----"); 154 //刷新UI 155 SwingUtilities.invokeLater(() -> { 156 Server.flushUI(null, Type.SERVERCLOSE, null); 157 }); 158 System.out.println("服務器已關閉-----"); 159 } catch (IOException e) { 160 System.out.println("111111"); 161 e.printStackTrace(); 162 } 163 } 164 /** 165 * 監聽客戶端鏈接 166 */ 167 private void acceptClient() { 168 while (state) { 169 Socket client = null; 170 try { 171 if (server != null) { 172 client = server.accept(); 173 // 新一個客戶端進來 174 // 開啓線程 175 es.execute(new UserThread(client,onlineUsers)); 176 } 177 } catch (IOException e) { 178 continue; 179 } 180 } 181 } 182 183 /** 184 * 初始化界面 185 */ 186 public void init() { 187 // 建立JFrame對象 188 f = new JFrame(); 189 190 // 設置窗體基本屬性 191 f.setTitle("QQ服務器"); 192 f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 193 f.setBounds(300, 150, 580, 400); 194 f.setResizable(false); 195 f.setLayout(null); 196 f.setVisible(true); 197 198 //實例化啓動服務器和關閉服務器按鈕 199 startBtn = new JButton("開啓服務器"); 200 startBtn.setBounds(50,10,100,35); 201 202 stopBtn = new JButton("關閉服務器"); 203 stopBtn.setBounds(260,10,100,35); 204 stopBtn.setEnabled(false); 205 206 msgTa = new JTextArea(); 207 msgTa.setEditable(false); 208 msgTa.setFont(new Font("微軟雅黑", Font.CENTER_BASELINE, 13)); 209 210 spMsg = new JScrollPane(msgTa); 211 spMsg.setBounds(5, 45, 405, 260); 212 //設置標題邊框 213 spMsg.setBorder(BorderFactory.createTitledBorder("消息列表")); 214 215 dlm = new DefaultListModel<>(); 216 217 onlineList = new JList<>(dlm); 218 219 spOnline = new JScrollPane(onlineList); 220 spOnline.setBounds(425, 5, 145, 300); 221 //設置標題邊框 222 spOnline.setBorder(BorderFactory.createTitledBorder("在線列表")); 223 224 sendTf = new JTextField(); 225 sendTf.setBounds(20, 320, 380, 35); 226 sendTf.setFont(new Font("宋體", Font.BOLD, 20)); 227 228 sendBtn = new JButton("發送"); 229 sendBtn.setBounds(445, 320, 100, 35); 230 231 f.add(startBtn); 232 f.add(stopBtn); 233 f.add(spMsg); 234 f.add(spOnline); 235 f.add(sendTf); 236 f.add(sendBtn); 237 238 //註冊事件 239 regEvent(); 240 } 241 242 /** 243 * 註冊事件 244 */ 245 private void regEvent() { 246 //註冊啓動服務器事件 247 startBtn.addActionListener(new ActionListener() { 248 @Override 249 public void actionPerformed(ActionEvent e) { 250 startServer(); 251 } 252 }); 253 254 //關閉服務器事件 255 stopBtn.addActionListener(new ActionListener() { 256 @Override 257 public void actionPerformed(ActionEvent e) { 258 closeServer(); 259 } 260 }); 261 262 //按鈕動做事件 263 sendBtn.addActionListener(new ActionListener() { 264 @Override 265 public void actionPerformed(ActionEvent e) { 266 //獲取文本框中的即將要發送的消息內容 267 String content = sendTf.getText().trim(); 268 if (!("".equals(content))) { 269 serverInform(content); 270 //清空文本框 271 sendTf.setText(""); 272 } else { 273 JOptionPane.showMessageDialog(f, "發送內容不能爲空", "提示", 1); 274 } 275 } 276 }); 277 278 //文本框鍵盤事件 279 sendTf.addKeyListener(new KeyAdapter() 280 { 281 @Override 282 public void keyPressed(KeyEvent e) { 283 if (e.getKeyCode() == KeyEvent.VK_ENTER) { 284 //獲取文本框中的即將要發送的消息內容 285 String content = sendTf.getText(); 286 if (!("".equals(content))) { 287 serverInform(content); 288 //清空文本框 289 sendTf.setText(""); 290 } else { 291 JOptionPane.showMessageDialog(f, "發送內容不能爲空", "提示", 1); 292 } 293 } 294 } 295 }); 296 } 297 298 /** 299 * 服務器通知全部客戶端消息 300 */ 301 private void serverInform(final String msg) { 302 ObjectOutputStream oos = null; 303 try { 304 //判斷服務器是否已啓動 305 if (state) { 306 if (onlineUsers.size() > 0) { 307 //遍歷HashMap 308 Set<String> keys = onlineUsers.keySet(); 309 for (String s : keys) { 310 UserThread ut = (UserThread)onlineUsers.get(s); 311 Socket client = ut.getClient(); 312 if (client != null) { 313 oos = new ObjectOutputStream(client.getOutputStream()); 314 //建立Message對象 315 Message m = new Message(); 316 m.setInfo("服務器:" + msg); 317 m.setType(Type.SERVERINFORM); 318 oos.writeObject(m); 319 oos.flush(); 320 } 321 } 322 SwingUtilities.invokeLater(new Runnable() { 323 @Override 324 public void run() { 325 //刷新UI 326 flushUI(null, Type.SERVERINFORM, msg); 327 } 328 }); 329 } else { 330 //此時沒有客戶端在線 331 //彈出提示框提示服務器 332 SwingUtilities.invokeLater(new Runnable() { 333 @Override 334 public void run() { 335 JOptionPane.showMessageDialog(f, "當前無客戶端在線", "提示", 3); 336 } 337 }); 338 } 339 } else { 340 //彈出提示框提示服務器未啓動 341 SwingUtilities.invokeLater(new Runnable() { 342 @Override 343 public void run() { 344 JOptionPane.showMessageDialog(f, "服務器未啓動", "提示", 2); 345 } 346 }); 347 } 348 } catch (IOException e) { 349 e.printStackTrace(); 350 } 351 } 352 353 /** 354 * 發送在線用戶集合給每一個在線的客戶端 355 */ 356 public static void sendMap() { 357 ObjectOutputStream oos = null; 358 try { 359 //遍歷Map,響應其餘客戶端 360 Set<String> keys = onlineUsers.keySet(); 361 for (String s : keys) { 362 UserThread temp = (UserThread)onlineUsers.get(s); 363 if (temp != null) { 364 oos = new ObjectOutputStream(temp.getClient().getOutputStream()); 365 oos.writeObject(onlineUsers); 366 oos.flush(); 367 } 368 } 369 } catch (IOException e) { 370 e.printStackTrace(); 371 } 372 } 373 374 /** 375 * 羣發消息 376 * @param from 377 * @param msg 378 */ 379 public static void groupSend(final UserThread from,final String msg) { 380 ObjectOutputStream oos = null; 381 try { 382 //遍歷HashMap 383 Set<String> keys = onlineUsers.keySet(); 384 for (String s : keys) { 385 UserThread ut = (UserThread) onlineUsers.get(s); 386 if (ut != null && ut != from) { 387 oos = new ObjectOutputStream(ut.getClient().getOutputStream()); 388 //建立Message對象 389 Message m = new Message(); 390 m.setInfo(from.getNickName() + ":" + msg); 391 m.setType(Type.GROUPRECEIVE); 392 oos.writeObject(m); 393 oos.flush(); 394 } 395 } 396 } catch (IOException e) { 397 e.printStackTrace(); 398 } 399 } 400 401 /** 402 * 刷新UI控件 403 * 注意:服務器關閉屬於特殊狀況,這時候必須將List控件中的全部用戶信息所有移除 404 */ 405 public static void flushUI(UserThread u,int state,String msg) { 406 //判斷客戶端的鏈接狀態 407 switch (state) { 408 case Type.SERVERSTART : 409 // 顯示服務器啓動提示信息 410 msgTa.append("QQ服務器已啓動----------正在等待客戶端鏈接!!!\n"); 411 break; 412 case Type.SERVERCLOSE : 413 // 移除List控件中的全部用戶信息 414 onlineList.removeAll(); 415 // 顯示服務器啓動提示信息 416 msgTa.append("QQ服務器已關閉------稍後再爲您服務!!!\n"); 417 break; 418 case Type.CONNECT : 419 msgTa.append("客戶端【" + u.getLocalHost() + "】已鏈接\n"); 420 break; 421 case Type.LOGIN : 422 msgTa.append("客戶端" + u.getLocalHost() + "【 " + u.getNickName() + "】已登陸\n"); 423 dlm.addElement(u.getLocalHost() + "/" + u.getNickName()); 424 break; 425 case Type.GROUPSEND : 426 msgTa.append("客戶端" + u.getLocalHost() + "【 " + u.getNickName() + "】羣發了一條消息:" + msg + "\n"); 427 break; 428 case Type.PRIVATESEND : 429 break; 430 case Type.SERVERINFORM : 431 msgTa.append("服務器向全部客戶端發送了一條消息:" + msg + "\n"); 432 break; 433 case Type.CLIENTEXIT : 434 msgTa.append("客戶端【" + u.getLocalHost() + "】已斷開鏈接\n"); 435 dlm.removeElement(u.getLocalHost() + "/" + u.getNickName()); 436 break; 437 } 438 //1.刷新消息面板 439 msgTa.updateUI(); 440 //2.更新List面板 441 onlineList.updateUI(); 442 } 443 444 /** 445 * 主方法 446 * @param args 447 */ 448 public static void main(String[] args) { 449 Server server = new Server(); 450 server.init(); 451 } 452 }
ClientState.javasocket
1 package chat; 2 3 /** 4 * 客戶端消息常量類 5 * @author Administrator 6 * 7 */ 8 public class ClientState { 9 10 //發送的消息 11 public static final int SEND = 0x001; 12 13 //接收的消息 14 public static final int RECEIVE = 0x002; 15 16 }
Message.java
1 package chat; 2 3 import java.io.Serializable; 4 5 6 @SuppressWarnings("serial") 7 public class Message implements Serializable{ 8 9 private String from; 10 11 private String to; 12 13 private String info; 14 15 private int type; 16 17 public Message() { 18 19 } 20 21 public Message(String from, String to, String info, int type) { 22 this.from = from; 23 this.to = to; 24 this.info = info; 25 this.type = type; 26 } 27 28 public String getFrom() { 29 return from; 30 } 31 32 public void setFrom(String from) { 33 this.from = from; 34 } 35 36 public String getTo() { 37 return to; 38 } 39 40 public void setTo(String to) { 41 this.to = to; 42 } 43 44 public String getInfo() { 45 return info; 46 } 47 48 public void setInfo(String info) { 49 this.info = info; 50 } 51 52 public int getType() { 53 return type; 54 } 55 56 public void setType(int type) { 57 this.type = type; 58 } 59 60 61 62 }
Type.java
1 package chat; 2 3 import java.io.Serializable; 4 5 /** 6 * @author Administrator 7 * 8 */ 9 @SuppressWarnings("serial") 10 public class Type implements Serializable{ 11 12 public static final int LOGIN = 0x001; 13 14 public static final int GROUPSEND = 0x002; 15 16 public static final int GROUPRECEIVE = 0x003; 17 18 public static final int PRIVATESEND = 0x004; 19 20 public static final int PRIVATERECEIVE = 0x005; 21 22 public static final int SERVERINFORM = 0x006; 23 24 public static final int CLIENTEXIT = 0x007; 25 26 public static final int CONNECT = 0x008; 27 28 public static final int SERVERCLOSE = 0x009; 29 30 public static final int SERVERSTART = 0; 31 32 }
UserThread.java
1 package chat; 2 3 import java.io.IOException; 4 import java.io.ObjectInputStream; 5 import java.io.Serializable; 6 import java.net.Socket; 7 import java.util.Map; 8 import javax.swing.SwingUtilities; 9 10 /** 11 * 用戶處理線程 12 * 功能一:接受用戶的消息 13 * 功能二:回覆用戶的消息 14 * 功能三:刷新UI控件 15 * 16 * 用ObjectOutputStream和ObjectInputStream這兩個流對象傳輸時,必定要注意哪些對象是沒有實現Serializable接口, 17 * 因此在傳輸過程當中,不能被傳輸,會拋異常NotSerilizableException 18 * 19 * 解決辦法:將不能被序列化的對象加上transient關鍵字 20 * 21 * 這次用ObjectOutputStream和ObjectInputStream對象輸入輸出流注意事項:socket對象是不能被序列化得,自身能夠做爲傳輸的工具,可是自身不能被序列化, 22 * 因此在此特別要注意。本人在這個地方困惑了將近兩天,最後在一篇博客上纔看到有人說socket對象是不能被序列化的, 23 * 24 * 注意:GUI網絡編程的兩個關鍵步驟: 25 * 一、必定要記得時時更新集合中的數據 26 * 二、必定要記得刷新UI控件,作到及時響應客戶端 27 * @author Administrator 28 * 29 */ 30 @SuppressWarnings("serial") 31 public class UserThread implements Runnable,Serializable{ 32 33 private transient Socket client; 34 35 private String localHost = null; 36 37 private UserThread user = this; 38 39 private Map<String,UserThread> onlineUsers; 40 41 private String nickName; 42 43 private boolean isStop = false; 44 45 public boolean isStop() { 46 return isStop; 47 } 48 49 public void setStop(boolean isStop) { 50 this.isStop = isStop; 51 } 52 53 /** 54 * 處理客戶端線程的構造方法 55 * @param client 56 * @param onlineUsers 57 */ 58 public UserThread(Socket client,Map<String,UserThread> onlineUsers) { 59 //鏈接成功 60 this.client = client; 61 this.localHost = client.getInetAddress().getHostAddress() + "::" + client.getPort(); 62 this.onlineUsers = onlineUsers; 63 // 刷新List 64 Server.flushUI(this, Type.CONNECT, null); 65 } 66 67 /** 68 * 線程方法 69 */ 70 @Override 71 public void run() { 72 while (!isStop) { 73 if (!hear()) { 74 // 進入,表示客戶端本身退出 75 // 若是返回false,則跳出循環 76 onlineUsers.remove(nickName); 77 break; 78 } 79 } 80 // 鏈接斷開緣由 :多是客戶端本身斷開,也多是服務器關閉 81 // 經過該線程的暱稱鍵名來從Map集合中移除 該鍵值對 82 // 發送存儲在線用戶的Map集合給每一個在線的客戶端 83 // 響應客戶端 84 if (!isStop) { 85 Server.sendMap(); 86 } 87 // 響應服務端 88 SwingUtilities.invokeLater(() -> { 89 Server.flushUI(user, Type.CLIENTEXIT, null); 90 }); 91 } 92 93 /** 94 * 負責接收客戶端發來的全部類型消息 95 * @return 返回的boolean值標識這客戶端的鏈接狀態 96 * 若是返回false則標識客戶端已斷開鏈接 97 * 反之,則標識客戶端沒斷開鏈接 98 */ 99 private boolean hear() { 100 ObjectInputStream ois = null; 101 try { 102 ois = new ObjectInputStream(client.getInputStream()); 103 Object obj = ois.readObject(); 104 if (obj instanceof Message) { 105 Message msg = (Message) obj; 106 //判斷消息類型 107 switch (msg.getType()) { 108 case Type.LOGIN : 109 String nickName = msg.getInfo(); 110 this.nickName = nickName; 111 onlineUsers.put(this.nickName, this); 112 //發送存儲在線用戶的HashMap集合給每一個在線的客戶端 113 Server.sendMap(); 114 SwingUtilities.invokeLater(() -> { 115 Server.flushUI(user, Type.LOGIN, null); 116 }); 117 break; 118 case Type.GROUPSEND : 119 final String info = msg.getInfo(); 120 Server.groupSend(this, info); 121 SwingUtilities.invokeLater(new Runnable() { 122 @Override 123 public void run() { 124 Server.flushUI(user, Type.GROUPSEND, info); 125 } 126 }); 127 break; 128 case Type.PRIVATESEND : 129 break; 130 } 131 } 132 } catch (ClassNotFoundException e) { 133 //此時表示客戶端斷開鏈接 134 return false; 135 } catch (IOException e) { 136 //此時表示客戶端斷開鏈接 137 return false; 138 } 139 return true; 140 } 141 142 public Socket getClient() { 143 return client; 144 } 145 146 public void setClient(Socket client) { 147 this.client = client; 148 } 149 150 public String getLocalHost() { 151 return localHost; 152 } 153 154 public String getNickName() { 155 return nickName; 156 } 157 }
Client.java
1 package chat; 2 3 import static java.util.concurrent.Executors.newFixedThreadPool; 4 import java.awt.Font; 5 import java.awt.event.ActionEvent; 6 import java.awt.event.ActionListener; 7 import java.awt.event.KeyAdapter; 8 import java.awt.event.KeyEvent; 9 import java.io.IOException; 10 import java.io.ObjectInputStream; 11 import java.io.ObjectOutputStream; 12 import java.net.Socket; 13 import java.util.Map; 14 import java.util.Set; 15 import java.util.concurrent.ExecutorService; 16 import javax.swing.DefaultComboBoxModel; 17 import javax.swing.JButton; 18 import javax.swing.JComboBox; 19 import javax.swing.JFrame; 20 import javax.swing.JLabel; 21 import javax.swing.JOptionPane; 22 import javax.swing.JScrollPane; 23 import javax.swing.JTextArea; 24 import javax.swing.JTextField; 25 import javax.swing.SwingUtilities; 26 import javax.swing.WindowConstants; 27 28 /** 29 * 客戶端 30 * 31 * 需求:多客戶端互發消息 32 * 步驟: 33 * 1.鏈接服務器 34 * 2.嘗試鏈接,接收服務器的提示消息 35 * 3.若成功鏈接,則開始與好友進行聊天 36 * 若失敗,則嘗試重連 37 * 4.這裏我採用Socket對象來區分多個客戶端,而沒有鏈接數據庫像QQ同樣每一個客戶端都能用QQ惟一標識, 38 * 我先作個簡便的聊天工具。實現羣聊 39 * 5.注意:客戶端刷新UI控件是必需要經過服務器的響應才能實時刷新的。 40 * 41 * @author 14715 42 * 43 */ 44 public class Client implements Runnable{ 45 46 // 客戶端套接字 47 private Socket client = null; 48 49 private JFrame f = null; 50 51 public static JTextArea msgTa = null; 52 53 private JScrollPane sp = null; 54 55 private JLabel onlineLa = null; 56 57 private DefaultComboBoxModel<String> dcmbModel = null; 58 59 private JComboBox<String> cmbFriends = null; 60 61 private JTextField sendTf = null; 62 63 private JButton sendBtn = null; 64 65 private ExecutorService es = newFixedThreadPool(4); 66 67 private String nickName = null; 68 69 /** 70 * 開啓客戶端 71 */ 72 public void startClient() { 73 try { 74 // 開啓服務 75 client = new Socket("127.0.0.1",12345); 76 String input = null; 77 while (true) { 78 //只有鏈接上服務器,才彈出JOPtionPane的輸入框模式 79 input = JOptionPane.showInputDialog("請輸入您的暱稱"); 80 //判斷用戶輸入 81 if (input != null) { 82 if (input.equals("")) { 83 JOptionPane.showMessageDialog(f, "暱稱不能爲空"); 84 } else { 85 break; 86 } 87 } else { 88 System.exit(0); 89 } 90 } 91 //開始登陸 92 login(input); 93 //初始化界面 94 init(); 95 //打印信息到控制檯 96 System.out.println("已成功鏈接上服務器\n"); 97 // 顯示服務器啓動提示信息 98 msgTa.append("已成功鏈接上服務器,能夠開始與好友聊天啦!\n"); 99 //開啓線程接收服務器端消息的線程 100 es.execute(this); 101 } catch (IOException e) { 102 JOptionPane.showMessageDialog(f,"未鏈接到遠程服務器","黃色警告",2); 103 //未鏈接到服務器,則退出程序 104 System.exit(0); 105 } 106 } 107 108 /** 109 * 初始化界面 110 */ 111 public void init() { 112 // 建立JFrame對象 113 f = new JFrame(); 114 115 // 設置窗體基本屬性 116 f.setTitle("QQ客戶端--------暱稱:" + nickName); 117 f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 118 f.setBounds(700, 100, 570, 400); 119 f.setResizable(false); 120 f.setLayout(null); 121 f.setVisible(true); 122 123 msgTa = new JTextArea(); 124 msgTa.setEditable(false); 125 msgTa.setFont(new Font("微軟雅黑", Font.BOLD, 20)); 126 127 sp = new JScrollPane(msgTa); 128 sp.setBounds(5, 5, 440, 300); 129 130 onlineLa = new JLabel("在線用戶"); 131 onlineLa.setBounds(475,5,60,15); 132 133 dcmbModel = new DefaultComboBoxModel<String>(); 134 135 cmbFriends = new JComboBox<String>(dcmbModel); 136 cmbFriends.setBounds(450,30,110,20); 137 138 sendTf = new JTextField(); 139 sendTf.setBounds(20, 320, 390, 35); 140 sendTf.setFont(new Font("宋體", Font.BOLD, 20)); 141 142 sendBtn = new JButton("發送"); 143 sendBtn.setBounds(450, 320, 110, 35); 144 145 f.add(sp); 146 f.add(onlineLa); 147 f.add(cmbFriends); 148 f.add(sendTf); 149 f.add(sendBtn); 150 151 //註冊事件監聽 152 regEvent(); 153 } 154 155 /** 156 * 註冊事件 157 */ 158 private void regEvent() { 159 sendBtn.addActionListener(new ActionListener() { 160 @Override 161 public void actionPerformed(ActionEvent e) { 162 //判斷是否成功鏈接上服務器 163 if (client != null) { 164 //獲取文本框中的即將要發送的消息內容 165 String content = sendTf.getText().trim(); 166 if (!("".equals(content))) { 167 sendGroupMessage(content); 168 //清空文本框 169 sendTf.setText(""); 170 } else { 171 JOptionPane.showMessageDialog(f, "發送內容不能爲空", "提示", 1); 172 } 173 } else { 174 JOptionPane.showMessageDialog(f,"請先鏈接QQ服務器再與好友聊天吧","警告",2); 175 } 176 } 177 }); 178 179 sendTf.addKeyListener(new KeyAdapter() { 180 @Override 181 public void keyPressed(KeyEvent e) { 182 //判斷是否成功鏈接上服務器 183 if (client != null) { 184 if (e.getKeyCode() == KeyEvent.VK_ENTER) { 185 //獲取文本框中的即將要發送的消息內容 186 String content = sendTf.getText().trim(); 187 if (!("".equals(content))) { 188 sendGroupMessage(content); 189 //清空文本框 190 sendTf.setText(""); 191 } else { 192 JOptionPane.showMessageDialog(f, "發送內容不能爲空", "提示", 1); 193 } 194 } 195 } else { 196 JOptionPane.showMessageDialog(f,"請先鏈接QQ服務器再與好友聊天吧","警告",2); 197 } 198 } 199 }); 200 } 201 202 /** 203 * QQ登陸 204 * @param nickName 登陸暱稱 205 */ 206 private void login(String nickName) { 207 ObjectOutputStream oos = null; 208 try { 209 oos = new ObjectOutputStream(client.getOutputStream()); 210 //建立Message對象 211 Message m = new Message(); 212 m.setInfo(nickName); 213 m.setType(Type.LOGIN); 214 //發送登陸數據 215 oos.writeObject(m); 216 oos.flush(); 217 this.nickName = nickName; 218 } catch (IOException e) { 219 e.printStackTrace(); 220 } 221 } 222 223 /** 224 * 羣發消息給服務器 225 */ 226 private void sendGroupMessage(final String msg) { 227 ObjectOutputStream oos = null; 228 try { 229 oos = new ObjectOutputStream(client.getOutputStream()); 230 //建立Message對象 231 Message m = new Message(); 232 m.setInfo(msg); 233 m.setType(Type.GROUPSEND); 234 oos.writeObject(m); 235 oos.flush(); 236 //刷新UI 237 SwingUtilities.invokeLater(new Runnable() { 238 @Override 239 public void run() { 240 flushUI(msg, Type.GROUPSEND); 241 } 242 }); 243 } catch (IOException e) { 244 e.printStackTrace(); 245 } 246 } 247 248 /** 249 * 接收在線客戶的Map集合 250 */ 251 @SuppressWarnings({ "unchecked" }) 252 private void receiveMap(Object obj) { 253 //讀取ArrayList集合 254 Map<String, UserThread> onlineUsers = (Map<String, UserThread>) obj; 255 //先將以前的JComboBox控件中的內容所有清空 256 dcmbModel.removeAllElements(); 257 //遍歷HashMap 258 Set<String> keys = onlineUsers.keySet(); 259 for (String s : keys) { 260 dcmbModel.addElement(s); 261 } 262 SwingUtilities.invokeLater(new Runnable() { 263 @Override 264 public void run() { 265 cmbFriends.updateUI(); 266 } 267 }); 268 } 269 270 /** 271 * 接收到服務器發送來的羣消息 272 * @param obj 273 */ 274 private void receiveMessage(Object obj) { 275 Message m = (Message) obj; 276 //獲取消息內容 277 final String info = m.getInfo(); 278 //判斷是羣發仍是服務器的通知消息 279 switch (m.getType()) { 280 case Type.GROUPRECEIVE : 281 //刷新UI 282 SwingUtilities.invokeLater(new Runnable() { 283 @Override 284 public void run() { 285 flushUI(info, Type.GROUPRECEIVE); 286 } 287 }); 288 break; 289 case Type.SERVERINFORM : 290 //刷新UI 291 SwingUtilities.invokeLater(new Runnable() { 292 @Override 293 public void run() { 294 flushUI(info, Type.SERVERINFORM); 295 } 296 }); 297 break; 298 } 299 } 300 301 /** 302 * 讀 303 * @return 返回的boolean值標識這客戶端的鏈接狀態 304 * 若是返回false則標識客戶端已斷開鏈接 305 * 反之,則標識客戶端沒斷開鏈接 306 */ 307 private boolean hear() { 308 ObjectInputStream ois = null; 309 try { 310 ois = new ObjectInputStream(client.getInputStream()); 311 //readObject()該方法是個阻塞式方法,若是沒有讀取到對象,就會一直等到,不須要考慮其出現null的狀況 312 Object obj = ois.readObject(); 313 if (obj instanceof Map) { 314 //若是接收到的是Map對象 315 receiveMap(obj); 316 } else if (obj instanceof Message) { 317 //羣發消息 318 receiveMessage(obj); 319 } 320 } catch (IOException e) { 321 //此時表示服務器斷開鏈接 322 return false; 323 } catch (ClassNotFoundException e) { 324 //此時表示服務器斷開鏈接 325 return false; 326 } 327 return true; 328 } 329 330 /** 331 * 線程方法 332 */ 333 @Override 334 public void run() { 335 while (hear()) { 336 } 337 SwingUtilities.invokeLater(new Runnable() { 338 @Override 339 public void run() { 340 flushUI(null,Type.CLIENTEXIT); 341 } 342 }); 343 } 344 345 /** 346 * 刷新UI控件 347 */ 348 private void flushUI(String msg, int state) { 349 switch (state) { 350 case Type.GROUPSEND : 351 msgTa.append("我:" + msg + "\n"); 352 break; 353 case Type.GROUPRECEIVE : 354 msgTa.append(msg + "\n"); 355 break; 356 case Type.SERVERINFORM : 357 msgTa.append(msg + "\n"); 358 break; 359 case Type.CLIENTEXIT : 360 JOptionPane.showMessageDialog(f, "服務器已斷開", "提示", 0); 361 //強制退出程序 362 System.exit(0); 363 break; 364 } 365 //刷新消息面板 366 msgTa.updateUI(); 367 } 368 369 /** 370 * 主方法 371 * @param args 372 */ 373 public static void main(String[] args) { 374 //建立客戶端對象 375 Client client = new Client(); 376 //啓動客戶端,鏈接服務器 377 client.startClient(); 378 } 379 }