Java聊天室之實現(xiàn)客戶端一對一聊天功能
一、題目描述
題目實現(xiàn):不同的客戶端之間需要進(jìn)行通信,一個客戶端與指定的另一客戶端進(jìn)行通信,實現(xiàn)一對一聊天功能。
實現(xiàn)一個客戶端與指定的另一客戶端進(jìn)行通信,運(yùn)行程序,服務(wù)器啟動后,啟動3個客戶端程序,分別以小小,虛虛,竹竹,登錄 ,然后在左側(cè)的用戶列表中選擇接收信息用戶,輸入聊天信息,發(fā)送到目標(biāo)用戶。
二、解題思路
創(chuàng)建一個服務(wù)類:ClientOneToOneServerFrame,繼承JFrame類
定義ServerThread線程類,用于為客戶端添加用戶列表。有一部分代碼用于轉(zhuǎn)發(fā)客戶端發(fā)送的消息。
創(chuàng)建一個客戶端類:ClientOneToOneClientFrame,繼承JFrame類
定義ClientThread線程類,用于對接收到服務(wù)器的信息,進(jìn)行處理。如果是登錄用戶,就添加到用戶列表中。
如果是消息,就追加到文本域中。
技術(shù)重點(diǎn):
? 在服務(wù)器端通過線程對客戶端發(fā)送的信息進(jìn)行監(jiān)聽,并對登錄用戶和消息分別進(jìn)行處理。如果是登錄用戶,就將所有用戶添加到客戶端的用戶列表中;如果是消息,就轉(zhuǎn)發(fā)給指定的用戶;客戶端則通過線程對接收到的信息進(jìn)行處理,如果是登錄用戶就添加到用戶列表中,如果是消息就追加到文本域中。 ? (1)在服務(wù)器端創(chuàng)建線程類ServerThread,用于對登錄用戶和消息分別進(jìn)行處理。如果是登錄用戶,就將所有用戶添加到客戶端的用戶列表中;如果是消息就轉(zhuǎn)發(fā)給指定的用戶。
? (2)在客戶端創(chuàng)建線程類ClientThread,用于對接收到的信息進(jìn)行處理,如果是登錄用戶就添加到用戶列表中,如果是消息就追加到文本域中。
啟動多個客戶端:
1、把項目打成jar包:利用maven 的clean install
會在target目錄下生成jar包
2、進(jìn)入target目錄,使用java -cp的命令運(yùn)行指定的類
java -cp 命令中 cp 指的就是classpath。使用該命令可以運(yùn)行jar中的某個指定的類(要包含全路徑的包名)
進(jìn)入cmd命令模式
運(yùn)行服務(wù)端
java -cp basics98-1.0-SNAPSHOT.jar com.xiaoxuzhu.ClientOneToOneServerFrame
運(yùn)行多個客戶端
java -cp basics98-1.0-SNAPSHOT.jar com.xiaoxuzhu.ClientOneToOneClientFrame
三、代碼詳解
ClientOneToOneServerFrame
package com.xiaoxuzhu; import java.awt.BorderLayout; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Hashtable; import java.util.Iterator; import java.util.Set; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTextArea; /** * Description: * * @author xiaoxuzhu * @version 1.0 * * <pre> * 修改記錄: * 修改后版本 修改人 修改日期 修改內(nèi)容 * 2022/6/5.1 xiaoxuzhu 2022/6/5 Create * </pre> * @date 2022/6/5 */ public class ClientOneToOneServerFrame extends JFrame{ private JTextArea ta_info; private ServerSocket server; // 聲明ServerSocket對象 private Socket socket; // 聲明Socket對象socket private Hashtable<String, Socket> map = new Hashtable<String, Socket>();// 用于存儲連接到服務(wù)器的用戶和客戶端套接字對象 public void createSocket() { try { server = new ServerSocket(9527); while (true) { ta_info.append("等待新客戶連接......\n"); socket = server.accept();// 創(chuàng)建套接字對象 ta_info.append("客戶端連接成功。" + socket + "\n"); new ServerThread(socket).start();// 創(chuàng)建并啟動線程對象 } } catch (IOException e) { e.printStackTrace(); } } class ServerThread extends Thread { Socket socket; public ServerThread(Socket socket) { this.socket = socket; } public void run() { try { BufferedReader in = new BufferedReader(new InputStreamReader( socket.getInputStream()));// 創(chuàng)建輸入流對象 while (true) { String info = in.readLine();// 讀取信息 String key = ""; if (info.startsWith("用戶:")) {// 添加登錄用戶到客戶端列表 key = info.substring(3, info.length());// 獲得用戶名并作為鍵使用 map.put(key, socket);// 添加鍵值對 Set<String> set = map.keySet();// 獲得集合中所有鍵的Set視圖 Iterator<String> keyIt = set.iterator();// 獲得所有鍵的迭代器 while (keyIt.hasNext()) { String receiveKey = keyIt.next();// 獲得表示接收信息的鍵 Socket s = map.get(receiveKey);// 獲得與該鍵對應(yīng)的套接字對象 PrintWriter out = new PrintWriter(s .getOutputStream(), true);// 創(chuàng)建輸出流對象 Iterator<String> keyIt1 = set.iterator();// 獲得所有鍵的迭代器 while (keyIt1.hasNext()) { String receiveKey1 = keyIt1.next();// 獲得鍵,用于向客戶端添加用戶列表 out.println(receiveKey1);// 發(fā)送信息 out.flush();// 刷新輸出緩沖區(qū) } } } else {// 轉(zhuǎn)發(fā)接收的消息 key = info.substring(info.indexOf(":發(fā)送給:") + 5, info .indexOf(":的信息是:"));// 獲得接收方的key值,即接收方的用戶名 String sendUser = info.substring(0, info .indexOf(":發(fā)送給:"));// 獲得發(fā)送方的key值,即發(fā)送方的用戶名 Set<String> set = map.keySet();// 獲得集合中所有鍵的Set視圖 Iterator<String> keyIt = set.iterator();// 獲得所有鍵的迭代器 while (keyIt.hasNext()) { String receiveKey = keyIt.next();// 獲得表示接收信息的鍵 if (key.equals(receiveKey) && !sendUser.equals(receiveKey)) {// 如果是發(fā)送方,但不是用戶本身 Socket s = map.get(receiveKey);// 獲得與該鍵對應(yīng)的套接字對象 PrintWriter out = new PrintWriter(s .getOutputStream(), true);// 創(chuàng)建輸出流對象 out.println("MSG:"+info);// 發(fā)送信息 out.flush();// 刷新輸出緩沖區(qū) } } } } } catch (IOException e) { ta_info.append(socket + "已經(jīng)退出。\n"); } } } /** * Launch the application * * @param args */ public static void main(String args[]) { ClientOneToOneServerFrame frame = new ClientOneToOneServerFrame(); frame.setVisible(true); frame.createSocket(); } /** * Create the frame */ public ClientOneToOneServerFrame() { super(); setTitle("客戶端一對一通信——服務(wù)器端程序"); setBounds(100, 100, 385, 266); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JScrollPane scrollPane = new JScrollPane(); getContentPane().add(scrollPane, BorderLayout.CENTER); ta_info = new JTextArea(); scrollPane.setViewportView(ta_info); } }
ClientOneToOneClientFrame
package com.xiaoxuzhu; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; import javax.swing.*; /** * Description: * * @author xiaoxuzhu * @version 1.0 * * <pre> * 修改記錄: * 修改后版本 修改人 修改日期 修改內(nèi)容 * 2022/6/5.1 xiaoxuzhu 2022/6/5 Create * </pre> * @date 2022/6/5 */ public class ClientOneToOneClientFrame extends JFrame{ private JTextField tf_newUser; private JList user_list; private JTextArea ta_info; private JTextField tf_send; PrintWriter out;// 聲明輸出流對象 private boolean loginFlag = false;// 為true時表示已經(jīng)登錄,為false時表示未登錄 private Socket socket; /** * Launch the application * * @param args */ public static void main(String args[]) { EventQueue.invokeLater(new Runnable() { public void run() { try { ClientOneToOneClientFrame frame = new ClientOneToOneClientFrame(); frame.setVisible(true); frame.createClientSocket();// 調(diào)用方法創(chuàng)建套接字對象 } catch (Exception e) { e.printStackTrace(); } } }); } public void createClientSocket() { try { socket = new Socket("127.0.0.1", 9527);// 創(chuàng)建套接字對象 out = new PrintWriter(socket.getOutputStream(), true);// 創(chuàng)建輸出流對象 SwingWorker<Void,Void> worker=new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { try { BufferedReader in = new BufferedReader(new InputStreamReader( socket.getInputStream()));// 創(chuàng)建輸入流對象 DefaultComboBoxModel model = (DefaultComboBoxModel) user_list .getModel();// 獲得列表框的模型 while (true) { String info = in.readLine().trim();// 讀取信息 if (!info.startsWith("MSG:")) { boolean itemFlag = false;// 標(biāo)記是否為列表框添加列表項,為true不添加,為false添加 for (int i = 0; i < model.getSize(); i++) { if (info.equals((String) model.getElementAt(i))) { itemFlag = true; } } if (!itemFlag) { model.addElement(info);// 添加列表項 } else { itemFlag = false; } } else { ta_info.append(info + "\n");// 在文本域中顯示信息 if (info.equals("88")) { break;// 結(jié)束線程 } } } } catch (IOException e) { e.printStackTrace(); } return null; } }; worker.execute(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } private void send() { if (!loginFlag) { JOptionPane.showMessageDialog(null, "請先登錄。"); return; } String sendUserName = tf_newUser.getText().trim(); String info = tf_send.getText();// 獲得輸入的信息 if (info.equals("")) { return;// 如果沒輸入信息則返回,即不發(fā)送 } String receiveUserName = (String) user_list.getSelectedValue();// 獲得接收信息的用戶 String msg = sendUserName + ":發(fā)送給:" + receiveUserName + ":的信息是: " + info;// 定義發(fā)送的信息 if (info.equals("88")) { System.exit(0);// 如果沒輸入信息是88,則退出 } out.println(msg);// 發(fā)送信息 out.flush();// 刷新輸出緩沖區(qū) tf_send.setText(null);// 清空文本框 } /** * Create the frame */ public ClientOneToOneClientFrame() { super(); setTitle("客戶端一對一通信——客戶端程序"); setBounds(100, 100, 385, 288); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); final JPanel panel = new JPanel(); getContentPane().add(panel, BorderLayout.SOUTH); final JLabel label = new JLabel(); label.setText("輸入聊天內(nèi)容:"); panel.add(label); tf_send = new JTextField(); tf_send.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { send();// 調(diào)用方法發(fā)送信息 } }); tf_send.setPreferredSize(new Dimension(180, 25)); panel.add(tf_send); final JButton button = new JButton(); button.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { send();// 調(diào)用方法發(fā)送信息 } }); button.setText("發(fā) 送"); panel.add(button); final JSplitPane splitPane = new JSplitPane(); splitPane.setDividerLocation(100); getContentPane().add(splitPane, BorderLayout.CENTER); final JScrollPane scrollPane = new JScrollPane(); splitPane.setRightComponent(scrollPane); ta_info = new JTextArea(); scrollPane.setViewportView(ta_info); final JScrollPane scrollPane_1 = new JScrollPane(); splitPane.setLeftComponent(scrollPane_1); user_list = new JList(); user_list.setModel(new DefaultComboBoxModel(new String[] { "" })); scrollPane_1.setViewportView(user_list); final JPanel panel_1 = new JPanel(); getContentPane().add(panel_1, BorderLayout.NORTH); final JLabel label_1 = new JLabel(); label_1.setText("輸入用戶名稱:"); panel_1.add(label_1); tf_newUser = new JTextField(); tf_newUser.setPreferredSize(new Dimension(180, 25)); panel_1.add(tf_newUser); final JButton button_1 = new JButton(); button_1.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { if (loginFlag) { JOptionPane.showMessageDialog(null, "在同一窗口只能登錄一次。"); return; } String userName = tf_newUser.getText().trim();// 獲得登錄用戶名 out.println("用戶:" + userName);// 發(fā)送登錄用戶的名稱 out.flush();// 刷新輸出緩沖區(qū) tf_newUser.setEnabled(false); loginFlag = true; } }); button_1.setText("登 錄"); panel_1.add(button_1); } }
服務(wù)器啟動
客戶端1和客戶端2登錄
客戶端小小向客戶端虛虛發(fā)送消息
客戶端虛虛向客戶端小小發(fā)送消息
注:小小發(fā)給虛虛時,小小自己的界面不顯示自己發(fā)出的內(nèi)容。本示例主要是為了演示客戶端向指定客戶端發(fā)送消息。
多學(xué)一個知識點(diǎn)
swing的開發(fā)過程,要了解3種線程的概念:
1、初始化線程 :此類線程將執(zhí)行初始化應(yīng)用代碼。
2、事件調(diào)度線程 :所有的事件處理代碼在這里執(zhí)行。大多數(shù)與Swing框架 交互的代碼也必須執(zhí)行這個線程。
事件調(diào)度線程是單線程的:因為 Swing里面的各種組件類,比如JTextField,JButton 都不是線程安全的,這就意味著,如果有多個線程,那么同一個JTextField的setText方法,可能會被多個線程同時調(diào)用,這會導(dǎo)致同步問題以及錯誤數(shù)據(jù)的發(fā)生
3、工作線程 :也稱作background threads(后臺線程),此類線程將執(zhí)行所有消耗時間的任務(wù)。
比如的事件監(jiān)聽——在actionPerformed 里放一個長耗時任務(wù),如:數(shù)據(jù)庫訪問連接 建立網(wǎng)絡(luò)連接 文件復(fù)制等等 就會自動進(jìn)入事件調(diào)度線程。 而事件調(diào)度線程又是單線程模式,其結(jié)果就會是在執(zhí)行這些長耗時任務(wù)的時候,界面就無響應(yīng)了。
為了解決這個問題,Swing提供了一個SwingWorker類來解決。 SwingWorker是一個抽象類,為了使用,必須實現(xiàn)方法 doInBackground,在doInBackground中,就可以編寫我們的任務(wù),然后執(zhí)行SwingWorker的execute方法,放在專門的工作線程中去運(yùn)行。
上面題目里,ClientOneToOneClientFrame類中的createClientSocket()里就用到了SwingWorker
以上就是Java聊天室之實現(xiàn)客戶端一對一聊天功能的詳細(xì)內(nèi)容,更多關(guān)于Java聊天室的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java Thread之Sleep()使用方法及總結(jié)
這篇文章主要介紹了Java Thread之Sleep()使用方法及總結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11使用AOP+反射實現(xiàn)自定義Mybatis多表關(guān)聯(lián)查詢
這篇文章主要介紹了使用AOP+反射實現(xiàn)自定義Mybatis多表關(guān)聯(lián),目前的需求是增強(qiáng)現(xiàn)有的查詢,使用簡單的注解即可實現(xiàn)多表關(guān)聯(lián),本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05SpringBoot+Mybatis實現(xiàn)登錄注冊的示例代碼
這篇文章主要介紹了SpringBoot+Mybatis實現(xiàn)登錄注冊的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03String s = new String(''a '') 到底產(chǎn)生幾個對象
這篇文章主要介紹了String s = new String(" a ") 到底產(chǎn)生幾個對象,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05