Java聊天室之實現客戶端一對一聊天功能
一、題目描述
題目實現:不同的客戶端之間需要進行通信,一個客戶端與指定的另一客戶端進行通信,實現一對一聊天功能。
實現一個客戶端與指定的另一客戶端進行通信,運行程序,服務器啟動后,啟動3個客戶端程序,分別以小小,虛虛,竹竹,登錄 ,然后在左側的用戶列表中選擇接收信息用戶,輸入聊天信息,發(fā)送到目標用戶。
二、解題思路
創(chuàng)建一個服務類:ClientOneToOneServerFrame,繼承JFrame類
定義ServerThread線程類,用于為客戶端添加用戶列表。有一部分代碼用于轉發(fā)客戶端發(fā)送的消息。
創(chuàng)建一個客戶端類:ClientOneToOneClientFrame,繼承JFrame類
定義ClientThread線程類,用于對接收到服務器的信息,進行處理。如果是登錄用戶,就添加到用戶列表中。
如果是消息,就追加到文本域中。
技術重點:
? 在服務器端通過線程對客戶端發(fā)送的信息進行監(jiān)聽,并對登錄用戶和消息分別進行處理。如果是登錄用戶,就將所有用戶添加到客戶端的用戶列表中;如果是消息,就轉發(fā)給指定的用戶;客戶端則通過線程對接收到的信息進行處理,如果是登錄用戶就添加到用戶列表中,如果是消息就追加到文本域中。 ? (1)在服務器端創(chuàng)建線程類ServerThread,用于對登錄用戶和消息分別進行處理。如果是登錄用戶,就將所有用戶添加到客戶端的用戶列表中;如果是消息就轉發(fā)給指定的用戶。
? (2)在客戶端創(chuàng)建線程類ClientThread,用于對接收到的信息進行處理,如果是登錄用戶就添加到用戶列表中,如果是消息就追加到文本域中。
啟動多個客戶端:
1、把項目打成jar包:利用maven 的clean install

會在target目錄下生成jar包

2、進入target目錄,使用java -cp的命令運行指定的類
java -cp 命令中 cp 指的就是classpath。使用該命令可以運行jar中的某個指定的類(要包含全路徑的包名)
進入cmd命令模式

運行服務端
java -cp basics98-1.0-SNAPSHOT.jar com.xiaoxuzhu.ClientOneToOneServerFrame
運行多個客戶端
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>
* 修改記錄:
* 修改后版本 修改人 修改日期 修改內容
* 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>();// 用于存儲連接到服務器的用戶和客戶端套接字對象
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);// 獲得與該鍵對應的套接字對象
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 {// 轉發(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);// 獲得與該鍵對應的套接字對象
PrintWriter out = new PrintWriter(s
.getOutputStream(), true);// 創(chuàng)建輸出流對象
out.println("MSG:"+info);// 發(fā)送信息
out.flush();// 刷新輸出緩沖區(qū)
}
}
}
}
} catch (IOException e) {
ta_info.append(socket + "已經退出。\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("客戶端一對一通信——服務器端程序");
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>
* 修改記錄:
* 修改后版本 修改人 修改日期 修改內容
* 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時表示已經登錄,為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();// 調用方法創(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;// 標記是否為列表框添加列表項,為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;// 結束線程
}
}
}
} 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("輸入聊天內容:");
panel.add(label);
tf_send = new JTextField();
tf_send.addActionListener(new ActionListener() {
public void actionPerformed(final ActionEvent e) {
send();// 調用方法發(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();// 調用方法發(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);
}
}服務器啟動

客戶端1和客戶端2登錄

客戶端小小向客戶端虛虛發(fā)送消息

客戶端虛虛向客戶端小小發(fā)送消息

注:小小發(fā)給虛虛時,小小自己的界面不顯示自己發(fā)出的內容。本示例主要是為了演示客戶端向指定客戶端發(fā)送消息。
多學一個知識點
swing的開發(fā)過程,要了解3種線程的概念:
1、初始化線程 :此類線程將執(zhí)行初始化應用代碼。
2、事件調度線程 :所有的事件處理代碼在這里執(zhí)行。大多數與Swing框架 交互的代碼也必須執(zhí)行這個線程。
事件調度線程是單線程的:因為 Swing里面的各種組件類,比如JTextField,JButton 都不是線程安全的,這就意味著,如果有多個線程,那么同一個JTextField的setText方法,可能會被多個線程同時調用,這會導致同步問題以及錯誤數據的發(fā)生
3、工作線程 :也稱作background threads(后臺線程),此類線程將執(zhí)行所有消耗時間的任務。
比如的事件監(jiān)聽——在actionPerformed 里放一個長耗時任務,如:數據庫訪問連接 建立網絡連接 文件復制等等 就會自動進入事件調度線程。 而事件調度線程又是單線程模式,其結果就會是在執(zhí)行這些長耗時任務的時候,界面就無響應了。
為了解決這個問題,Swing提供了一個SwingWorker類來解決。 SwingWorker是一個抽象類,為了使用,必須實現方法 doInBackground,在doInBackground中,就可以編寫我們的任務,然后執(zhí)行SwingWorker的execute方法,放在專門的工作線程中去運行。
上面題目里,ClientOneToOneClientFrame類中的createClientSocket()里就用到了SwingWorker
以上就是Java聊天室之實現客戶端一對一聊天功能的詳細內容,更多關于Java聊天室的資料請關注腳本之家其它相關文章!

