Java實現(xiàn)簡易即時通訊系統(tǒng)的示例詳解
引言
我們想要實現(xiàn)一個類似QQ的即時通訊程序。由于這是一個復(fù)雜的項目,我們將分步驟進行,并只實現(xiàn)核心功能。
核心功能包括:
- 用戶注冊與登錄
- 添加好友
- 發(fā)送消息(點對點)
- 接收消息
我們將使用Socket編程來實現(xiàn)。為了簡化,我們不使用數(shù)據(jù)庫,而是使用文件存儲用戶信息和好友關(guān)系。
我們將創(chuàng)建兩個主要部分:服務(wù)器和客戶端。
服務(wù)器端:
- 處理用戶注冊、登錄請求
- 管理用戶連接(在線狀態(tài))
- 轉(zhuǎn)發(fā)消息
客戶端:
- 提供用戶界面(命令行界面)
- 連接服務(wù)器
- 發(fā)送注冊、登錄、添加好友、發(fā)送消息等請求
- 接收來自服務(wù)器的消息并顯示
由于時間限制,我們將簡化一些設(shè)計:
- 用戶信息存儲:用戶名、密碼(明文存儲,實際中應(yīng)加密)
- 好友關(guān)系存儲:每個用戶的好友列表
- 消息轉(zhuǎn)發(fā):服務(wù)器接收到消息后,查找目標(biāo)用戶是否在線,如果在線則轉(zhuǎn)發(fā),否則存儲為離線消息(離線消息功能我們暫不實現(xiàn),但會留出擴展)
步驟:
- 設(shè)計服務(wù)器和客戶端之間的通信協(xié)議(自定義簡單協(xié)議)
- 實現(xiàn)服務(wù)器端
- 實現(xiàn)客戶端
通信協(xié)議設(shè)計:
我們使用文本協(xié)議,以行為單位,每行一個指令或數(shù)據(jù)。每條消息以換行符結(jié)束。
消息類型:
1.注冊:REGISTER::服務(wù)器回復(fù):REGISTER_SUCCESS 或 REGISTER_FAILURE:
2.登錄:LOGIN::服務(wù)器回復(fù):LOGIN_SUCCESS 或 LOGIN_FAILURE:
3.添加好友:ADD_FRIEND:<friend_username>
服務(wù)器回復(fù):ADD_FRIEND_SUCCESS:<friend_username> 或 ADD_FRIEND_FAILURE:
同時,如果對方在線,服務(wù)器會向?qū)Ψ桨l(fā)送一個通知:FRIEND_REQUEST:
4.發(fā)送消息:MESSAGE::<message_content>服務(wù)器轉(zhuǎn)發(fā)給接收者:MESSAGE::<message_content>
5.退出:LOGOUT服務(wù)器還需要維護在線用戶的Socket連接。
注意:在實際中,客戶端需要能夠同時接收消息(即使用單獨的線程接收消息并顯示)
由于是簡化版,我們不考慮安全性和性能。
開始實現(xiàn):
服務(wù)器端:
- 使用ServerSocket監(jiān)聽端口。
- 每個客戶端連接使用一個線程處理(ClientHandler)。
- 維護一個在線用戶列表(ConcurrentHashMap):用戶名->輸出流(用于發(fā)送消息)
客戶端:
連接到服務(wù)器,開啟兩個線程:一個用于發(fā)送命令,一個用于接收服務(wù)器消息并顯示。
我們先實現(xiàn)注冊和登錄功能。
由于不使用數(shù)據(jù)庫,我們使用文件存儲用戶信息。每個用戶一個文件,存儲在users目錄下,文件名為用戶名.txt,內(nèi)容為密碼(第一行)和好友列表(后續(xù)每行一個好友用戶名)。
同樣,好友關(guān)系我們也會在用戶文件中存儲,但這樣可能不是最優(yōu),但為了簡單。
具體步驟:
- 創(chuàng)建服務(wù)器項目結(jié)構(gòu)。
- 編寫服務(wù)器主程序,監(jiān)聽端口,接受客戶端連接,為每個客戶端啟動一個線程。
- 編寫ClientHandler,處理客戶端發(fā)來的各種請求。
- 實現(xiàn)注冊、登錄功能。
- 客戶端程序:連接服務(wù)器,提供命令行界面輸入指令。
由于代碼較長,我們將分部分實現(xiàn)。
我們先寫服務(wù)器端:
Java即時通訊系統(tǒng)(類似QQ)
下面是一個使用Java實現(xiàn)的簡易即時通訊系統(tǒng),模擬QQ的核心功能。這個實現(xiàn)包括圖形界面、用戶管理、好友列表和實時聊天功能。
設(shè)計思路
1.客戶端-服務(wù)器架構(gòu):
- 服務(wù)器處理用戶注冊、登錄、消息路由
- 客戶端提供用戶界面和通訊功能
2.核心功能:
- 用戶注冊與登錄
- 好友管理(添加、刪除)
- 實時文本聊天
- 消息通知
- 用戶狀態(tài)顯示(在線/離線)
3.技術(shù)實現(xiàn):
- Java Swing用于GUI
- Socket編程實現(xiàn)網(wǎng)絡(luò)通信
- 多線程處理并發(fā)請求
完整代碼實現(xiàn)
1. 服務(wù)器端代碼
import java.io.*;
import java.net.*;
import java.util.*;
public class ChatServer {
private static final int PORT = 12345;
private static final Map<String, PrintWriter> clients = new HashMap<>();
private static final Map<String, Set<String>> friends = new HashMap<>();
public static void main(String[] args) {
System.out.println("QQ服務(wù)器啟動中...");
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服務(wù)器已啟動,監(jiān)聽端口: " + PORT);
while (true) {
new ClientHandler(serverSocket.accept()).start();
}
} catch (IOException e) {
System.err.println("服務(wù)器異常: " + e.getMessage());
}
}
private static class ClientHandler extends Thread {
private Socket socket;
private PrintWriter out;
private BufferedReader in;
private String username;
public ClientHandler(Socket socket) {
this.socket = socket;
}
public void run() {
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new PrintWriter(socket.getOutputStream(), true);
// 用戶認(rèn)證
while (true) {
String command = in.readLine();
if (command == null) {
return;
}
String[] parts = command.split(":", 3);
if (parts[0].equals("REGISTER")) {
String username = parts[1];
String password = parts[2];
if (clients.containsKey(username)) {
out.println("REGISTER_FAIL:用戶名已存在");
} else {
clients.put(username, out);
friends.put(username, new HashSet<>());
out.println("REGISTER_SUCCESS");
System.out.println("用戶注冊: " + username);
break;
}
} else if (parts[0].equals("LOGIN")) {
String username = parts[1];
String password = parts[2];
if (clients.containsKey(username)) {
this.username = username;
clients.put(username, out);
out.println("LOGIN_SUCCESS");
sendFriendList(username);
System.out.println("用戶登錄: " + username);
break;
} else {
out.println("LOGIN_FAIL:用戶名不存在");
}
}
}
// 處理客戶端請求
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println(username + " 發(fā)送: " + inputLine);
if (inputLine.startsWith("ADD_FRIEND:")) {
String friend = inputLine.substring(11);
if (clients.containsKey(friend)) {
friends.get(username).add(friend);
friends.get(friend).add(username);
out.println("FRIEND_ADDED:" + friend);
notifyUser(friend, "FRIEND_REQUEST:" + username);
} else {
out.println("FRIEND_NOT_FOUND:" + friend);
}
} else if (inputLine.startsWith("MESSAGE:")) {
String[] msgParts = inputLine.split(":", 3);
if (msgParts.length >= 3) {
String to = msgParts[1];
String message = msgParts[2];
sendMessage(username, to, message);
}
} else if (inputLine.startsWith("GET_FRIENDS")) {
sendFriendList(username);
}
}
} catch (IOException e) {
System.err.println("客戶端連接異常: " + e.getMessage());
} finally {
if (username != null) {
clients.remove(username);
System.out.println(username + " 已斷開連接");
}
try {
socket.close();
} catch (IOException e) {
// 忽略
}
}
}
private void sendFriendList(String username) {
Set<String> friendSet = friends.get(username);
if (friendSet != null) {
StringBuilder friendList = new StringBuilder("FRIEND_LIST:");
for (String friend : friendSet) {
friendList.append(friend).append(",");
if (clients.containsKey(friend)) {
friendList.append("online,");
} else {
friendList.append("offline,");
}
}
out.println(friendList.toString());
}
}
private void sendMessage(String from, String to, String message) {
PrintWriter writer = clients.get(to);
if (writer != null) {
writer.println("MESSAGE:" + from + ":" + message);
} else {
// 用戶離線,存儲消息待發(fā)送
out.println("USER_OFFLINE:" + to);
}
}
private void notifyUser(String username, String message) {
PrintWriter writer = clients.get(username);
if (writer != null) {
writer.println(message);
}
}
}
}
2. 客戶端代碼
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;
public class QQClient extends JFrame {
private JTextField usernameField, passwordField, friendField;
private JPasswordField passwordFieldHidden;
private JButton loginButton, registerButton, addFriendButton;
private JTextArea chatArea, messageArea;
private JList<String> friendList;
private DefaultListModel<String> friendListModel;
private PrintWriter out;
private BufferedReader in;
private String currentUser;
private Map<String, JFrame> chatWindows = new HashMap<>();
public QQClient() {
setTitle("QQ即時通訊");
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout());
// 登錄面板
JPanel loginPanel = new JPanel(new GridLayout(3, 2));
loginPanel.add(new JLabel("用戶名:"));
usernameField = new JTextField();
loginPanel.add(usernameField);
loginPanel.add(new JLabel("密碼:"));
passwordFieldHidden = new JPasswordField();
loginPanel.add(passwordFieldHidden);
loginButton = new JButton("登錄");
registerButton = new JButton("注冊");
JPanel buttonPanel = new JPanel(new GridLayout(1, 2));
buttonPanel.add(loginButton);
buttonPanel.add(registerButton);
loginPanel.add(buttonPanel);
add(loginPanel, BorderLayout.NORTH);
// 主面板
JPanel mainPanel = new JPanel(new GridLayout(1, 2));
friendListModel = new DefaultListModel<>();
friendList = new JList<>(friendListModel);
friendList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mainPanel.add(new JScrollPane(friendList));
chatArea = new JTextArea();
chatArea.setEditable(false);
mainPanel.add(new JScrollPane(chatArea));
add(mainPanel, BorderLayout.CENTER);
// 底部面板
JPanel bottomPanel = new JPanel(new BorderLayout());
bottomPanel.add(new JLabel("添加好友:"), BorderLayout.WEST);
friendField = new JTextField();
bottomPanel.add(friendField, BorderLayout.CENTER);
addFriendButton = new JButton("添加");
bottomPanel.add(addFriendButton, BorderLayout.EAST);
add(bottomPanel, BorderLayout.SOUTH);
// 事件監(jiān)聽
loginButton.addActionListener(e -> login());
registerButton.addActionListener(e -> register());
addFriendButton.addActionListener(e -> addFriend());
friendList.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent evt) {
if (evt.getClickCount() == 2) {
openChatWindow();
}
}
});
setVisible(true);
}
private void connectToServer() {
try {
Socket socket = new Socket("localhost", 12345);
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 啟動消息接收線程
new Thread(this::receiveMessages).start();
} catch (IOException e) {
JOptionPane.showMessageDialog(this, "無法連接到服務(wù)器: " + e.getMessage(), "連接錯誤", JOptionPane.ERROR_MESSAGE);
}
}
private void login() {
String username = usernameField.getText();
String password = new String(passwordFieldHidden.getPassword());
if (username.isEmpty() || password.isEmpty()) {
JOptionPane.showMessageDialog(this, "用戶名和密碼不能為空", "輸入錯誤", JOptionPane.WARNING_MESSAGE);
return;
}
connectToServer();
out.println("LOGIN:" + username + ":" + password);
}
private void register() {
String username = usernameField.getText();
String password = new String(passwordFieldHidden.getPassword());
if (username.isEmpty() || password.isEmpty()) {
JOptionPane.showMessageDialog(this, "用戶名和密碼不能為空", "輸入錯誤", JOptionPane.WARNING_MESSAGE);
return;
}
connectToServer();
out.println("REGISTER:" + username + ":" + password);
}
private void addFriend() {
String friend = friendField.getText();
if (friend.isEmpty()) {
JOptionPane.showMessageDialog(this, "請輸入好友用戶名", "輸入錯誤", JOptionPane.WARNING_MESSAGE);
return;
}
if (friend.equals(currentUser)) {
JOptionPane.showMessageDialog(this, "不能添加自己為好友", "錯誤", JOptionPane.ERROR_MESSAGE);
return;
}
out.println("ADD_FRIEND:" + friend);
friendField.setText("");
}
private void openChatWindow() {
String selectedFriend = friendList.getSelectedValue();
if (selectedFriend == null) return;
// 提取用戶名(去掉狀態(tài)信息)
String friendName = selectedFriend.split(" \(")[0];
if (chatWindows.containsKey(friendName)) {
chatWindows.get(friendName).toFront();
return;
}
JFrame chatFrame = new JFrame("與 " + friendName + " 聊天");
chatFrame.setSize(400, 300);
chatFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
JTextArea chatHistory = new JTextArea();
chatHistory.setEditable(false);
chatFrame.add(new JScrollPane(chatHistory), BorderLayout.CENTER);
JPanel inputPanel = new JPanel(new BorderLayout());
messageArea = new JTextArea(3, 20);
inputPanel.add(new JScrollPane(messageArea), BorderLayout.CENTER);
JButton sendButton = new JButton("發(fā)送");
sendButton.addActionListener(e -> {
String message = messageArea.getText();
if (!message.isEmpty()) {
out.println("MESSAGE:" + friendName + ":" + message);
chatHistory.append("我: " + message + "\n");
messageArea.setText("");
}
});
inputPanel.add(sendButton, BorderLayout.EAST);
chatFrame.add(inputPanel, BorderLayout.SOUTH);
chatFrame.setVisible(true);
chatWindows.put(friendName, chatFrame);
chatFrame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosed(WindowEvent e) {
chatWindows.remove(friendName);
}
});
}
private void receiveMessages() {
try {
String message;
while ((message = in.readLine()) != null) {
System.out.println("收到消息: " + message);
if (message.startsWith("LOGIN_SUCCESS")) {
currentUser = usernameField.getText();
chatArea.append("登錄成功!歡迎 " + currentUser + "\n");
} else if (message.startsWith("REGISTER_SUCCESS")) {
chatArea.append("注冊成功!請登錄\n");
} else if (message.startsWith("LOGIN_FAIL") || message.startsWith("REGISTER_FAIL")) {
chatArea.append(message.substring(message.indexOf(':') + 1) + "\n");
} else if (message.startsWith("FRIEND_LIST:")) {
friendListModel.clear();
String[] friends = message.substring(12).split(",");
for (int i = 0; i < friends.length - 1; i += 2) {
String friend = friends[i];
String status = friends[i + 1];
friendListModel.addElement(friend + " (" + status + ")");
}
} else if (message.startsWith("MESSAGE:")) {
String[] parts = message.split(":", 3);
if (parts.length >= 3) {
String from = parts[1];
String content = parts[2];
if (chatWindows.containsKey(from)) {
JFrame frame = chatWindows.get(from);
Component comp = frame.getContentPane().getComponent(0);
if (comp instanceof JScrollPane) {
JScrollPane scrollPane = (JScrollPane) comp;
JViewport viewport = scrollPane.getViewport();
Component comp2 = viewport.getView();
if (comp2 instanceof JTextArea) {
((JTextArea) comp2).append(from + ": " + content + "\n");
}
}
} else {
chatArea.append(from + " 發(fā)來消息: " + content + "\n");
}
}
} else if (message.startsWith("FRIEND_REQUEST:")) {
String from = message.substring(15);
int option = JOptionPane.showConfirmDialog(this,
from + " 想添加你為好友,是否接受?",
"好友請求",
JOptionPane.YES_NO_OPTION);
if (option == JOptionPane.YES_OPTION) {
friends.get(currentUser).add(from);
friends.get(from).add(currentUser);
out.println("ADD_FRIEND:" + from);
}
}
}
} catch (IOException e) {
chatArea.append("與服務(wù)器斷開連接\n");
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(QQClient::new);
}
}
系統(tǒng)功能說明
1.用戶管理:
- 新用戶注冊
- 已有用戶登錄
- 密碼保護(實際應(yīng)用中應(yīng)加密存儲)
2.好友管理:
- 添加好友(雙向確認(rèn))
- 顯示好友在線狀態(tài)
- 好友列表管理
3.即時通訊:
- 雙擊好友打開聊天窗口
- 實時文本消息發(fā)送與接收
- 消息歷史記錄
4.界面設(shè)計:
- 簡潔直觀的登錄界面
- 好友列表顯示在線狀態(tài)
- 獨立的聊天窗口
- 消息通知系統(tǒng)
使用說明
1.啟動服務(wù)器:
java ChatServer
2.啟動客戶端:
java QQClient
3.操作流程:
- 注冊新賬號或登錄已有賬號
- 在底部輸入框中輸入好友用戶名,點擊"添加"
- 雙擊好友列表中的用戶名打開聊天窗口
- 在聊天窗口中輸入消息并點擊"發(fā)送"
擴展建議
1.增強安全性:
- 密碼加密存儲
- 添加SSL/TLS加密通信
2.功能擴展:
- 添加群聊功能
- 支持文件傳輸
- 添加離線消息存儲
- 實現(xiàn)消息歷史記錄
3.界面優(yōu)化:
- 添加好友分組功能
- 實現(xiàn)消息提醒聲音
- 添加表情符號支持
4.性能優(yōu)化:
- 使用線程池管理連接
- 添加數(shù)據(jù)庫支持
- 實現(xiàn)負(fù)載均衡
這個即時通訊系統(tǒng)實現(xiàn)了類似QQ的核心功能,包括用戶注冊登錄、好友管理、實時聊天等。代碼結(jié)構(gòu)清晰,易于擴展,可以作為學(xué)習(xí)Java網(wǎng)絡(luò)編程和GUI開發(fā)的良好示例。
相關(guān)文章
關(guān)于springboot集成swagger及knife4j的增強問題
這篇文章主要介紹了springboot集成swagger以及knife4j的增強,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
使用SpringBoot搭建一個郵件發(fā)送系統(tǒng)
在當(dāng)今數(shù)字化時代,郵件作為一種重要的通信方式,SpringBoot作為一款熱門的Java開發(fā)框架,為我們搭建郵件發(fā)送系統(tǒng)提供了便捷高效的解決方案,接下來,就讓我們一起深入探索如何使用 Spring Boot 搭建強大的郵件發(fā)送系統(tǒng),需要的朋友可以參考下2026-04-04
詳解IDEA使用Maven項目不能加入本地Jar包的解決方法
這篇文章主要介紹了詳解IDEA使用Maven項目不能加入本地Jar包的解決方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
MyBatis-Plus使用動態(tài)表名分表查詢的實現(xiàn)
本文主要介紹了MyBatis-Plus使用動態(tài)表名分表查詢,主要是動態(tài)修改表名的幾種常見場景,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-11-11
序列化版本號serialVersionUID的作用_動力節(jié)點Java學(xué)院整理
Java序列化是將一個對象編碼成一個字節(jié)流,反序列化將字節(jié)流編碼轉(zhuǎn)換成一個對象,這篇文章主要介紹了序列化版本號serialVersionUID的作用,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05
MyBatis異常-Property ''configLocation'' not specified, using d
今天小編就為大家分享一篇關(guān)于MyBatis異常-Property 'configLocation' not specified, using default MyBatis Configuration,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03
Spring Boot容器加載時執(zhí)行特定操作(推薦)
這篇文章主要介紹了Spring Boot容器加載時執(zhí)行特定操作及spring內(nèi)置的事件,需要的朋友可以參考下2018-01-01

