QO聊天室

QO聊天室——C/S架构


一、项目简介

QO聊天室是一款基于 Java Swing(客户端 GUI)与 Socket 网络编程实现的即时通讯系统,采用经典的客户端-服务器(C/S)架构,旨在满足多人实时通信的核心需求。系统支持用户注册与登录、好友管理、实时文本聊天以及跨终端文件传输等关键功能。客户端通过图形化界面提供直观友好的交互体验,服务器端则负责消息路由、用户状态维护及全量数据的持久化存储,整体架构在功能性与易用性之间取得了良好平衡。

项目 内容
系统简介 基于 Java Swing + Socket 的 C/S 架构即时通讯系统,支持登录、好友管理、实时聊天、文件传输
参考资料 1. Java Swing 官方文档:https://docs.oracle.com/javase 2. Java Socket 编程指南:https://www.oracle.com/java/ 3. MySQL JDBC 教程:https://dev.mysql.com/doc/connector-j/8.0/en/

二、系统功能简介

2.1 系统功能列表及概述

功能 概述
客户端 UI 与交互逻辑 实现登录/注册界面、好友列表展示、聊天窗口交互、消息输入与实时显示、好友增删等可视化操作
客户端网络通信 与服务器建立长连接,封装并发送用户指令,监听并解析服务器响应
服务器端逻辑 完成用户身份认证、好友关系管理、消息存储与转发、用户在线状态实时维护
数据持久化 基于 MySQL 实现用户信息、好友关系、聊天记录的持久化存储与高效查询

2.2 功能结构图

基于 Socket 的聊天室系统分为前端(客户端)与后端(服务器端)两大模块:

  • 前端:核心为 GUI 界面层,包含用户界面模块(登录/聊天/好友列表等视图)与交互控制模块(响应用户操作、更新界面状态);
  • 后端:核心为网络与数据层,包含网络监听模块(监听客户端连接、管理 Socket 通道)与业务处理模块(用户认证、消息转发、数据库交互)

聊天室系统功能结构图
0f93a091c132bb1edfa6abf06c2cb0e4_1768502450029-28dc44fd-4c16-46c3-99de-ee166fb6f508_x-oss-process=image%2Fformat%2Cwebp

2.3 包结构图

3b4bc27b344d6ee5a984595bb12157b6_1768522167737-5937a779-4536-438b-9654-dba2de849387_x-oss-process=image%2Fformat%2Cwebp

三、个人任务简述

3.1 负责的任务与功能

序号 完成功能与任务 描述
1 网络通信与消息处理 实现客户端与服务器 Socket 长连接、自定义协议解析、消息实时收发与界面刷新
2 用户状态与会话管理 设计当前聊天对象切换机制,实现历史消息加载与聊天界面同步更新
3 好友管理与会话切换 完成好友列表动态加载、好友增删、聊天会话切换及历史消息拉取
4 文件传输功能 实现文件选择、Socket 流传输、文件下载与本地保存功能

四、个人负责功能详解

4.1 网络通信与消息处理

核心实现客户端与服务器的 Socket 长连接通信逻辑,通过自定义协议前缀(如 MSG: 表示文本消息、FRIEND_LIST: 表示好友列表、FILE: 表示文件传输)区分指令类型,保障数据传输的规范性与可解析性。

关键实现要点:

  • 启动独立的消息监听线程,以阻塞方式读取服务器输入流,确保消息的实时接收;
  • 解析服务器返回的不同类型响应,分发至对应业务逻辑(如更新好友列表、渲染聊天消息);
  • 封装用户操作指令(登录、注册、添加好友、发送消息等),通过 Socket 输出流可靠发送至服务器。
ServerHandler.java
package chat.service;

import chat.dao.UserInfoDaoImpl;
import chat.entity.Friend;
import chat.entity.User;

import java.io.*;
import java.net.Socket;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ServerHandler implements Runnable {
private Socket clientSocket;
private PrintWriter out;
private BufferedReader in;
public static final Map<String,Socket> ONLINE_USER_MAP = new ConcurrentHashMap<>();
private String currentLoginUser = null;
private UserInfoDaoImpl UserInfoDaoImpl=new UserInfoDaoImpl();
private User user=new User();

public ServerHandler(Socket clientSocket) {
    this.clientSocket = clientSocket;
}

@Override
public void run() {
    try {
        in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        out = new PrintWriter(clientSocket.getOutputStream(), true);

        String inputLine;
        while ((inputLine = in.readLine()) != null) {
            System.out.println(&quot;收到指令: &quot; + inputLine);
            processCommand(inputLine);
        }
    } catch (IOException e) {
        System.out.println(&quot;连接异常断开: &quot; + e.getMessage());
    } finally {
        // 用户下线处理
        if (currentLoginUser != null) {
            ONLINE_USER_MAP.remove(currentLoginUser);
            System.out.println(&quot;用户 &quot; + currentLoginUser + &quot; 已下线&quot;);
        }
        try {
            clientSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public void processCommand(String inputLine) {
    // 分割指令
    String[] parts = inputLine.split(&quot; @####@ &quot;);
    if (parts.length == 0) return;
    boolean result = false;
    String friendName;
    try {
        int commandType = Integer.parseInt(parts[0]);

        switch (commandType) {
            case 1://保存数据
                result = UserInfoDaoImpl.saveMessage(parts);
                if (result) {
                    sendResponse(&quot;成功发送信息&quot;);
                }
                break;
            case 2://添加朋友
                user.setUsername(parts[1]);
                friendName = parts[2];
                String passwordUser = parts[3];
                result = UserInfoDaoImpl.addFriend(user.getUsername(), friendName, passwordUser);
                if (result) {
                    Socket receiverSocket = ONLINE_USER_MAP.get(parts[2]);
                    if (ONLINE_USER_MAP.containsKey(parts[2])) {
                        PrintWriter receiverOut = new PrintWriter(receiverSocket.getOutputStream(), true);
                        receiverOut.println(&quot;CMD_SUCCESS:成功添加朋友&quot;+parts[1]);
                        receiverOut.flush();
                    }
                    sendResponse(&quot;CMD_SUCCESS:成功添加朋友&quot;+parts[2]);
                }
                break;
            case 3://删除朋友
                user.setUsername(parts[1]);
                friendName = parts[2];
                result = UserInfoDaoImpl.deleteFriend(friendName, user.getUsername());
                if (result) {
                    Socket receiverSocket = ONLINE_USER_MAP.get(parts[2]);
                    if (ONLINE_USER_MAP.containsKey(parts[2])) {
                        PrintWriter receiverOut = new PrintWriter(receiverSocket.getOutputStream(), true);
                        receiverOut.println(&quot;CMD_SUCCESS:成功删除&quot;+parts[1]);
                        receiverOut.flush();
                    }
                    sendResponse(&quot;CMD_SUCCESS:成功删除&quot;+parts[2]);
                }
                break;
            case 4://注册
                user.setUsername(parts[1]);
                user.setPassword(parts[2]);
                user.setPasswordUser(parts[3]);
                try {
                    result = UserInfoDaoImpl.register(user);
                    out.println(&quot;成功注册&quot;);
                } catch (RuntimeException e) {
                    if (e.getMessage().contains(&quot;用户名已存在&quot;)) {
                        out.println(&quot;注册失败:用户名已存在&quot;);
                    } else {
                        out.println(&quot;注册失败:系统异常&quot;);
                    }
                }
                break;
            case 5: // 登录
                boolean success = UserInfoDaoImpl.login(parts[1], parts[2], parts[3]);
                if (success) {
                    this.currentLoginUser =parts[1];
                    ONLINE_USER_MAP.put(parts[1], clientSocket);
                    sendResponse(&quot;登录成功&quot;);
                } else {
                    out.println(&quot;登录失败&quot;);
                }
                break;
            case 6://获取朋友列表
                List&lt;String&gt; friendList = UserInfoDaoImpl.getFriends(parts[1]);
                for (String friend : friendList) {
                    out.println(&quot;FRIEND_LIST:&quot;+friend);
                }
                out.println(&quot;CMD_END_LIST&quot;);
                break;
            case 7: // 发送消息
                String sender = parts[1];
                String receiver = parts[2];
                String msg = parts[3];
                boolean saved = UserInfoDaoImpl.saveMessage(parts);
                if (saved) {
                    Socket receiverSocket = ONLINE_USER_MAP.get(receiver);
                    if (ONLINE_USER_MAP.containsKey(receiver)) {
                        PrintWriter receiverOut = new PrintWriter(receiverSocket.getOutputStream(), true);
                        receiverOut.println(&quot;MSG:&quot; + sender + &quot;: &quot; + msg);
                        receiverOut.flush();
                        sendResponse(&quot;CMD_SUCCESS:实时消息发送成功&quot;);
                    } else {
                        sendResponse(&quot;CMD_SUCCESS:信息保存成功(对方离线)&quot;);
                    }
                } else {
                    sendResponse(&quot;CMD_ERROR:消息保存失败&quot;);
                }
                break;
            case 8: // 获取历史消息
                List&lt;Friend&gt; historyList = UserInfoDaoImpl.sendMessage(parts[1], parts[2]);
                StringBuilder sb = new StringBuilder();
                for (Friend msg1 : historyList) {
                    // 发送格式:MSG_HISTORY:发送者:内容 (时间)
                    sb.append(&quot;MSG_HISTORY:&quot;).append(msg1.getSendname()).append(&quot;:&quot;).append(msg1.getMessage()).append(&quot; (&quot;).append(msg1.getSendtime()).append(&quot;)&quot;);
                    sendResponse(sb.toString());
                    sb.setLength(0);
                }
                sendResponse(&quot;CMD_END_HISTORY&quot;);
                break;
            case 9: // 退出
                UserInfoDaoImpl.logout(parts[1]);
                ONLINE_USER_MAP.remove(parts[1]);
                this.currentLoginUser = null;
                sendResponse(&quot;CMD_SUCCESS:退出成功&quot;);
                break;
            case 11:
                File dir = new File(&quot;server_files&quot;);
                if (!dir.exists()) dir.mkdirs();
                File serverFile = new File(dir,parts[3]);
                InputStream socketIn = clientSocket.getInputStream();
                FileOutputStream fileOut = new FileOutputStream(serverFile);
                byte[] buffer = new byte[1024];
                long totalRead = 0;
                int len;
                while (totalRead &lt; Long.parseLong(parts[4])) {
                    len = socketIn.read(buffer);
                    if (len == -1) break;
                    fileOut.write(buffer, 0, len);
                    totalRead += len;
                }
                fileOut.close();
                boolean saved2 = UserInfoDaoImpl.saveMessage(parts);
                if (saved2) {
                    Socket receiverSocket = ONLINE_USER_MAP.get(parts[2]);
                    if (ONLINE_USER_MAP.containsKey(parts[2])) {
                        PrintWriter receiverOut = new PrintWriter(receiverSocket.getOutputStream(), true);
                        receiverOut.println(&quot;FILE_MSG:&quot; + parts[1] + &quot;:&quot; + parts[3]);
                        receiverOut.flush();
                    }
                    sendResponse(&quot;FILE_MSG:文件上传成功&quot;);
                }
                break;
            case 12:
                boolean saved3 = UserInfoDaoImpl.Filesearch(parts[2], parts[1], parts[3]);
                if(saved3) {// 假设 receiverOut 是 PrintWriter
                    if (clientSocket != null) {
                        System.out.println(&quot;hello&quot;);
                        try {
                            File serverFile1 = new File(&quot;server_files&quot;, parts[3]);
                            FileInputStream fis = new FileInputStream(serverFile1);
                            long realFileSize = serverFile1.length();

                            OutputStream socketOut = clientSocket.getOutputStream();
                            String header = &quot;CMD_FILE_START:&quot; + parts[3] + &quot;:&quot; + realFileSize + &quot;\n&quot;;
                            socketOut.write(header.getBytes(&quot;UTF-8&quot;));

                            socketOut.flush();

                            byte[] buffer1 = new byte[4096]; // 使用更大的缓冲区
                            int len1;
                            while ((len1 = fis.read(buffer1)) != -1) {
                                System.out.println(len1);
                                socketOut.write(buffer1, 0, len1);
                            }
                            fis.close();
                            socketOut.flush();
                            System.out.println(&quot;文件 &quot; + parts[3] + &quot; 发送完毕&quot;);
                        } catch (FileNotFoundException e) {
                            PrintWriter errorOut = new PrintWriter(clientSocket.getOutputStream(), true);
                            errorOut.println(&quot;CMD_ERROR:文件不存在&quot;);
                            errorOut.flush();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                break;

            default:
                sendResponse(&quot;CMD_ERROR:未知指令&quot;);
                break;
        }
    } catch (Exception e) {
        sendResponse(&quot;CMD_ERROR:服务器处理异常: &quot; + e.getMessage());
        e.printStackTrace();
    }
}
private void sendResponse(String msg) {
    if (currentLoginUser != null) {
        Socket userSocket = ONLINE_USER_MAP.get(currentLoginUser);
        if (userSocket != null) {
            try {
                PrintWriter userOut = new PrintWriter(userSocket.getOutputStream(), true);
                userOut.println(msg);
                userOut.flush();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

}

ChatFrame.java(数据接收)
private void startListenThread() {
    new Thread(() -> {
        try {
            String line;
            // 只要 socket 没有关闭,就一直读
            while ((line =clientSocket.readMessage()) != null) {
                if (line.startsWith("FRIEND_LIST:")) {
                    // 处理好友列表
                    String friendName = line.substring(12); // 去掉 "FRIEND_LIST:"
                    if (!friendListModel.contains(friendName)) {
                        friendListModel.addElement(friendName);
                        user.addFriend(friendName);
                    }
                }
            if (line.startsWith(&quot;MSG:&quot;)) {
                // 实时消息
                String content = line.substring(4);
                appendMessage(content);

            } else if (line.startsWith(&quot;MSG_HISTORY:&quot;)) {
                // 历史消息
                String content = line.substring(12);
                appendMessage(content);

            } else if (line.startsWith(&quot;CMD_SUCCESS:&quot;)) {
                // 服务器反馈成功
                String msg = line.substring(12);
                if (msg.contains(&quot;成功添加朋友&quot;)) {
                    String friendName1 = msg.substring(6);
                    friendListModel.addElement(friendName1);
                    user.addFriend(friendName1);
                    JOptionPane.showMessageDialog(this, &quot;添加好友成功&quot;);
                } else if (msg.contains(&quot;成功删除&quot;)) {
                    String friendName1 = msg.substring(4);
                    friendListModel.removeElement(friendName1);
                    user.removeFriend(friendName1);
                    JOptionPane.showMessageDialog(this, &quot;删除好友成功&quot;);
                }
            } else if (line.startsWith(&quot;CMD_ERROR:&quot;)) {
                // 服务器反馈失败
                String errorMsg = line.substring(10);
                JOptionPane.showMessageDialog(this, &quot;错误: &quot; + errorMsg);
            } else if (line.startsWith(&quot;FRIEND_LIST:&quot;)) {
                // 好友列表数据
                String friendName = line.substring(12);
                // 简单去重
                if (!friendListModel.contains(friendName)) {
                    friendListModel.addElement(friendName);
                    user.addFriend(friendName);
                }
            }else if (line.startsWith(&quot;FILE_MSG:&quot;)) {
                // 格式: FILE_MSG:林:photo.jpg
                String[] parts = line.split(&quot;:&quot;);
                String senderName = parts[1];
                String fileName = parts[2];

                // 显示提示
                appendMessage(&quot;系统: &quot; + senderName + &quot; 给你发了一个文件 [&quot; + fileName + &quot;]&quot;);
                appendMessage(&quot;系统: 请输入文件名并点击【下载文件】按钮保存。&quot;);
            }
        }
    } catch (Exception e) {
        // 连接断开时不打印堆栈,正常退出
    }
}).start();

}

4.2 用户状态与会话管理

设计轻量高效的会话切换机制,确保多好友聊天场景下界面与数据的一致性:

  • 定义 currentChatTarget 变量记录当前聊天好友 ID,作为会话唯一标识;
  • 切换好友时清空聊天窗口,并通过服务器指令拉取该好友的历史聊天记录进行渲染;
  • 监听服务器推送的实时消息,仅当消息接收方为 currentChatTarget 时才更新当前聊天界面,避免消息错位。
ChatFrame.java
package chat.Frame;

import chat.client.ClientSocket;
import chat.entity.User;

import javax.swing.;
import javax.swing.border.EmptyBorder;
import java.awt.
;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class ChatFrame extends JFrame {
private User user;
private JTextArea taDisplay;
private JTextField tfInput;
private JButton btnSend;
ClientSocket clientSocket;
String newFriend = null;

// 好友列表相关组件
private DefaultListModel&lt;String&gt; friendListModel;
private JList&lt;String&gt; friendList;
private String currentChatTarget; // 当前正在聊天的对象
private JLabel lblCurrent;

public ChatFrame(User user,ClientSocket clientSocket) {
    this.user=user;
    this.clientSocket = clientSocket;
    friendListModel = new DefaultListModel&lt;&gt;();
    startListenThread();
    initUI();

}

private void initUI() {
    setTitle(&quot;Chatroom - &quot; + user.getUsername());
    setSize(800, 600);
    setLocationRelativeTo(null);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    addWindowListener(new WindowAdapter() {
        @Override
        public void windowClosing(WindowEvent e) {
            logOut();
            dispose();
        }
    });

    // 使用 JSplitPane 分割窗口
    JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
    splitPane.setDividerLocation(200); // 左侧宽度200
    splitPane.setOneTouchExpandable(true);

    //左侧面板:好友列表
    JPanel leftPanel = createLeftPanel();

    // 右侧面板:聊天区域
    JPanel rightPanel = createRightPanel();

    splitPane.setLeftComponent(leftPanel);
    splitPane.setRightComponent(rightPanel);

    add(splitPane, BorderLayout.CENTER);

    // 欢迎语
    taDisplay.append(&quot;系统: 欢迎你,&quot; + user.getUsername() + &quot;!连接服务器成功\n&quot;);
    taDisplay.append(&quot;系统: 请从左侧列表选择好友开始聊天。\n&quot;);

}

private JPanel createLeftPanel() {
    JPanel leftPanel = new JPanel(new BorderLayout());
    leftPanel.setBackground(new Color(240, 242, 245));
    leftPanel.setBorder(new EmptyBorder(10, 10, 10, 0));

    // 顶部标题
    JLabel lblFriends = new JLabel(&quot;好友列表&quot;, SwingConstants.CENTER);
    lblFriends.setFont(new Font(&quot;微软雅黑&quot;, Font.BOLD, 16));
    lblFriends.setBorder(new EmptyBorder(0, 0, 10, 0));
    leftPanel.add(lblFriends, BorderLayout.NORTH);

    // 中间好友列表
    loadFriendListFromResponse();

    friendList = new JList&lt;&gt;(friendListModel);
    friendList.setFont(new Font(&quot;微软雅黑&quot;, Font.PLAIN, 14));
    friendList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    friendList.setBorder(new EmptyBorder(5, 5, 5, 5));

    JScrollPane scrollPane = new JScrollPane(friendList);
    scrollPane.setBorder(null);
    leftPanel.add(scrollPane, BorderLayout.CENTER);

    // 底部按钮区
    JPanel btnPanel = new JPanel(new GridLayout(1, 2, 5, 5));
    btnPanel.setBorder(new EmptyBorder(10, 0, 0, 0));
    btnPanel.setBackground(new Color(240, 242, 245));

    JButton btnAddFriend = createSmallButton(&quot;添加&quot;, new Color(52, 199, 89));
    JButton btnDelFriend = createSmallButton(&quot;删除&quot;, new Color(255, 59, 48));
    JButton logOut=createSmallButton(&quot;登出&quot;,new Color(244, 226, 21, 221));

    btnPanel.add(btnAddFriend);
    btnPanel.add(btnDelFriend);
    btnPanel.add(logOut);
    leftPanel.add(btnPanel, BorderLayout.SOUTH);

    //左侧面板事件监听

    // 点击好友切换聊天对象
    friendList.addListSelectionListener(e -&gt; {
        if (!e.getValueIsAdjusting()) { // 防止触发两次
            String selected = friendList.getSelectedValue();
            switchChatTarget(selected);
        }
    });

    // 添加好友
    btnAddFriend.addActionListener(e -&gt; addFriend());

    // 删除好友
    btnDelFriend.addActionListener(e -&gt; deleteFriend());
    //登出
    logOut.addActionListener(e-&gt;logOut());

    return leftPanel;
}

private JPanel createRightPanel() {
    JPanel rightPanel = new JPanel(new BorderLayout());
    rightPanel.setBorder(new EmptyBorder(10, 0, 10, 10));

    // 顶部当前聊天对象显示
    lblCurrent = new JLabel(&quot;当前聊天: 未选择&quot;, SwingConstants.CENTER);
    lblCurrent.setFont(new Font(&quot;微软雅黑&quot;, Font.BOLD, 14));
    lblCurrent.setBorder(new EmptyBorder(0, 0, 10, 0));
    rightPanel.add(lblCurrent, BorderLayout.NORTH);

    // 消息显示区域
    taDisplay = new JTextArea();
    taDisplay.setEditable(false);
    taDisplay.setFont(new Font(&quot;微软雅黑&quot;, Font.PLAIN, 14));
    taDisplay.setLineWrap(true);
    taDisplay.setWrapStyleWord(true);

    JScrollPane scrollPane = new JScrollPane(taDisplay);
    rightPanel.add(scrollPane, BorderLayout.CENTER);

    // 底部输入区域
    JPanel bottomPanel = new JPanel(new BorderLayout(5, 5));

    tfInput = new JTextField();
    tfInput.setFont(new Font(&quot;微软雅黑&quot;, Font.PLAIN, 14));
    tfInput.setBorder(BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(new Color(200, 200, 200)),
            BorderFactory.createEmptyBorder(8, 10, 8, 10)
    ));

    btnSend = new JButton(&quot;发送&quot;);
    styleButton(btnSend, new Color(0, 122, 204));
    btnSend.setPreferredSize(new Dimension(80, 35));

    JButton btnSendFile=new JButton(&quot;发送文件&quot;);
    styleButton(btnSendFile, new Color(106, 90, 205));
    btnSendFile.setPreferredSize(new Dimension(100, 35));

    JButton btnDownload = new JButton(&quot;下载文件&quot;);
    styleButton(btnDownload, new Color(255, 140, 0)); // 橙色按钮
    btnDownload.setPreferredSize(new Dimension(100, 35));

    JPanel sendPanel=new JPanel(new FlowLayout(FlowLayout.RIGHT,5,0));
    sendPanel.add(btnSend);
    sendPanel.add(btnSendFile);
    sendPanel.add(btnDownload);

    bottomPanel.add(tfInput, BorderLayout.CENTER);
    bottomPanel.add(sendPanel, BorderLayout.EAST);
    rightPanel.add(bottomPanel, BorderLayout.SOUTH);

    // 右侧面板事件监听
    ActionListener sendAction = e -&gt; sendMessage();
    btnSend.addActionListener(sendAction);
    tfInput.addActionListener(sendAction);
    btnSendFile.addActionListener(e-&gt;sendFile());
    btnDownload.addActionListener(e -&gt; downloadFileFromServer());

    return rightPanel;
}

//逻辑方法
private void startListenThread() {
    new Thread(() -&gt; {
        try {
            String line;
            // 只要 socket 没有关闭,就一直读
            while ((line =clientSocket.readMessage()) != null) {
                if (line.startsWith(&quot;FRIEND_LIST:&quot;)) {
                    // 处理好友列表
                    String friendName = line.substring(12); // 去掉 &quot;FRIEND_LIST:&quot;
                    if (!friendListModel.contains(friendName)) {
                        friendListModel.addElement(friendName);
                        user.addFriend(friendName);
                    }
                }

                if (line.startsWith(&quot;MSG:&quot;)) {
                    // 实时消息
                    String content = line.substring(4);
                    appendMessage(content);

                } else if (line.startsWith(&quot;MSG_HISTORY:&quot;)) {
                    // 历史消息
                    String content = line.substring(12);
                    appendMessage(content);

                } else if (line.startsWith(&quot;CMD_SUCCESS:&quot;)) {
                    // 服务器反馈成功
                    String msg = line.substring(12);
                    if (msg.contains(&quot;成功添加朋友&quot;)) {
                        String friendName1 = msg.substring(6);
                        friendListModel.addElement(friendName1);
                        user.addFriend(friendName1);
                        JOptionPane.showMessageDialog(this, &quot;添加好友成功&quot;);
                    } else if (msg.contains(&quot;成功删除&quot;)) {
                        String friendName1 = msg.substring(4);
                        friendListModel.removeElement(friendName1);
                        user.removeFriend(friendName1);
                        JOptionPane.showMessageDialog(this, &quot;删除好友成功&quot;);
                    }
                } else if (line.startsWith(&quot;CMD_ERROR:&quot;)) {
                    // 服务器反馈失败
                    String errorMsg = line.substring(10);
                    JOptionPane.showMessageDialog(this, &quot;错误: &quot; + errorMsg);
                } else if (line.startsWith(&quot;FRIEND_LIST:&quot;)) {
                    // 好友列表数据
                    String friendName = line.substring(12);
                    // 简单去重
                    if (!friendListModel.contains(friendName)) {
                        friendListModel.addElement(friendName);
                        user.addFriend(friendName);
                    }
                }else if (line.startsWith(&quot;FILE_MSG:&quot;)) {
                    // 格式: FILE_MSG:林:photo.jpg
                    String[] parts = line.split(&quot;:&quot;);
                    String senderName = parts[1];
                    String fileName = parts[2];

                    // 显示提示
                    appendMessage(&quot;系统: &quot; + senderName + &quot; 给你发了一个文件 [&quot; + fileName + &quot;]&quot;);
                    appendMessage(&quot;系统: 请输入文件名并点击【下载文件】按钮保存。&quot;);
                }
            }
        } catch (Exception e) {
            // 连接断开时不打印堆栈,正常退出
        }
    }).start();
}
private void loadFriendListFromResponse() {
    if (clientSocket == null) {
        JOptionPane.showMessageDialog(this, &quot;连接已断开,请重新登录!&quot;);
        return;
    }
    String cmd = &quot;6 @####@ &quot; + user.getUsername();
    clientSocket.sendCommand(cmd);
}


private void switchChatTarget(String target) {
    this.currentChatTarget = target;
    taDisplay.setText(&quot;&quot;); // 清空显示区域
    lblCurrent.setText(&quot;当前聊天:&quot; + target);
    taDisplay.append(&quot;系统: 正在与 &quot; + target + &quot; 聊天\n&quot;);


    String command = &quot;8 @####@ &quot; + user.getUsername() + &quot; @####@ &quot; + target;
    clientSocket.sendCommand(command);
}

private void sendMessage() {
    String message = tfInput.getText().trim();
    if (message.isEmpty()) {
        JOptionPane.showMessageDialog(this, &quot;请输入要发送的消息内容!&quot;);
        return;
    }
    if (currentChatTarget == null || currentChatTarget.isEmpty()) {
        JOptionPane.showMessageDialog(this, &quot;请先从左侧列表选择聊天好友!&quot;);
        return;
    }
    String command = &quot;7 @####@ &quot; + user.getUsername() + &quot; @####@ &quot; + currentChatTarget + &quot; @####@ &quot; + message;
    clientSocket.sendCommand(command);
    appendMessage(user.getUsername()+&quot;: &quot; + message);
    tfInput.setText(&quot;&quot;);
}

private void sendFile() {
    if (currentChatTarget == null || currentChatTarget.isEmpty()) {
        JOptionPane.showMessageDialog(this, &quot;请先从左侧列表选择要发送的好友!&quot;);
        return;
    }

    JFileChooser fileChooser = new JFileChooser();
    fileChooser.setDialogTitle(&quot;选择要发送的文件&quot;);
    int result = fileChooser.showOpenDialog(this);
    if (result != JFileChooser.APPROVE_OPTION) return;

    File file = fileChooser.getSelectedFile();
    if (!file.exists() || !file.isFile()) {
        JOptionPane.showMessageDialog(this, &quot;无效的文件!&quot;);
        return;
    }
    try {
        String fileName = file.getName();
        long fileSize = file.length();
        System.out.println(currentChatTarget);
        String header = &quot;11 @####@ &quot; + user.getUsername() + &quot; @####@ &quot;
                + currentChatTarget + &quot; @####@ &quot; + fileName+ &quot; @####@ &quot; +fileSize;
        clientSocket.sendCommand(header);

        // 发送文件内容
        FileInputStream fis = new FileInputStream(file);
        OutputStream socketOut = clientSocket.getOutputStream();

        byte[] buffer = new byte[1024];
        int len;
        while ((len = fis.read(buffer)) != -1) {
            socketOut.write(buffer, 0, len);
        }
        fis.close();
        socketOut.flush();

        appendMessage(&quot;系统: 文件 [&quot; + fileName + &quot;] 发送完成&quot;);

    } catch (Exception e) {
        e.printStackTrace();
        JOptionPane.showMessageDialog(this, &quot;文件发送失败: &quot; + e.getMessage());
    }
}

private void addFriend() {
    JPanel panel = new JPanel();
    panel.setLayout(new GridLayout(2, 2, 10, 10)); // 2行2列布局,行列间距10px,排版美观
    panel.setBackground(new Color(240, 242, 245));
    JTextField newFriendField = new JTextField(); // 好友账号输入框
    JTextField  newFriendPassField = new JTextField();
    panel.add(new JLabel(&quot;请输入要添加的好友昵称名:&quot;));
    panel.add(newFriendField);
    panel.add(new JLabel(&quot;请输入要添加的好友交友码:&quot;));
    panel.add(newFriendPassField);
    int result = JOptionPane.showConfirmDialog(this, panel, &quot;添加好友&quot;, JOptionPane.OK_CANCEL_OPTION);
    String newFriendPass = null;
    if (result != JOptionPane.OK_OPTION) {
        return;
    }
    newFriend = newFriendField.getText().trim(); // 获取好友账号
    newFriendPass = newFriendPassField.getText().trim(); // 获取好友交友码(密文框正确取值)


    if (newFriend == null || newFriend.isEmpty() || newFriendPass == null || newFriendPass.isEmpty()) {
        JOptionPane.showMessageDialog(this, &quot;好友账号和交友码不能为空!&quot;);
        return;
    }
    if (newFriend.equals(user.getUsername())) {
        JOptionPane.showMessageDialog(this, &quot;不能添加自己为好友!&quot;);
        return;
    }
    if(user.getFriendList().contains(newFriend)) {
        JOptionPane.showMessageDialog(this, &quot;该好友已在列表中!&quot;);
        return;
    }
    String command=&quot;2 @####@ &quot;+user.getUsername()+&quot; @####@ &quot;+newFriend+&quot; @####@ &quot;+newFriendPass;
    clientSocket.sendCommand(command);
}

private void deleteFriend() {
    String selected = friendList.getSelectedValue();
    if (selected == null) {
        JOptionPane.showMessageDialog(this, &quot;请先选择要删除的好友!&quot;);
        return;
    }
    int confirm = JOptionPane.showConfirmDialog(this,
            &quot;确定要删除好友 &quot; + selected + &quot; 吗?&quot;,
            &quot;确认删除&quot;,
            JOptionPane.YES_NO_OPTION);

    if (confirm == JOptionPane.YES_OPTION) {
        String command=&quot;3 @####@ &quot;+user.getUsername()+&quot; @####@ &quot;+selected;
        clientSocket.sendCommand(command);
    }
}


private void logOut() {
    int confirm=JOptionPane.showConfirmDialog(
            this,
            &quot;确定要登出账号&quot;+user.getUsername()+&quot;吗?&quot;,
            &quot;确认登出&quot;,
            JOptionPane.YES_NO_OPTION

    );
    //用户取消登出
    if (confirm!=JOptionPane.YES_OPTION) {
        return;
    }
    String command=&quot;9 @####@ &quot;+user.getUsername();
    clientSocket.sendCommand(command);
    clientSocket.closeConnection();
    this.dispose();
    SwingUtilities.invokeLater(() -&gt; {
        new LoginFrame().setVisible(true);
    });
}
private void appendMessage(String msg) {
    SwingUtilities.invokeLater(() -&gt; {
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd HH:mm:ss&quot;);
        String timeStr = now.format(formatter);
        // 避免重复添加时间戳(如果服务器发来的带时间戳,这里就不加了)
        if (!msg.contains(&quot;(&quot;) &amp;&amp; !msg.startsWith(&quot;系统&quot;)) {
            taDisplay.append(msg + &quot; (&quot; + timeStr + &quot;)\n&quot;);
        } else {
            taDisplay.append(msg + &quot;\n&quot;);
        }
        taDisplay.setCaretPosition(taDisplay.getDocument().getLength());
    });
}
private void downloadFileFromServer() {
    String fileName = JOptionPane.showInputDialog(this, &quot;请输入聊天记录中显示的文件名:&quot;);
    if (fileName == null || fileName.trim().isEmpty()) return;
    try {


        // 选择保存路径
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle(&quot;选择保存位置&quot;);
        chooser.setSelectedFile(new File(fileName));
        int userSelection = chooser.showSaveDialog(this);
        if (userSelection != JFileChooser.APPROVE_OPTION) {
            return;
        }
        File fileToSave = chooser.getSelectedFile();
        System.out.println(&quot;准备保存到: &quot; + fileToSave.getAbsolutePath());

        ClientSocket clientSocket2 = new ClientSocket();
        clientSocket2.start(&quot;172.19.76.83&quot;);
        String cmd = &quot;12 @####@ &quot; +user.getUsername()+&quot; @####@ &quot;+currentChatTarget+&quot; @####@ &quot;+ fileName;
        clientSocket2.sendCommand(cmd);

        InputStream rawIn = clientSocket2.getInputStream();
            ByteArrayOutputStream lineBuffer = new ByteArrayOutputStream();
            int b = -1;
            while ((b = rawIn.read()) != -1) {
                System.out.println(&quot;b=&quot;+b);
                if (b == '\n') break; // 遇到换行符,指令结束
                lineBuffer.write(b);
            }
        System.out.println(&quot;out&quot;);
            String startLine = lineBuffer.toString(&quot;UTF-8&quot;);
            System.out.println(&quot;收到响应: &quot; + startLine);
            if (!startLine.startsWith(&quot;CMD_FILE_START:&quot;)) {
                JOptionPane.showMessageDialog(this, &quot;服务器响应错误&quot;);
                return;
            }
            String[] parts = startLine.split(&quot;:&quot;);
            long fileSize = Long.parseLong(parts[2]);
            System.out.println(&quot;准备接收文件,大小: &quot; + fileSize);

            FileOutputStream fileOut = new FileOutputStream(fileToSave);
            byte[] buffer = new byte[4096];
            long totalRead = 0;
            int len;

            while (totalRead &lt; fileSize &amp;&amp; (len = rawIn.read(buffer)) != -1) {
                // 防止多读(读到下一条指令的数据)
                if (totalRead + len &gt; fileSize) {
                    int need = (int)(fileSize - totalRead);
                    fileOut.write(buffer, 0, need);
                    totalRead += need;
                } else {
                    fileOut.write(buffer, 0, len);
                    totalRead += len;
                }
            }
            fileOut.close();

            JOptionPane.showMessageDialog(this, &quot;文件下载成功!&quot;);
            appendMessage(&quot;系统: 文件已保存到 &quot; + fileToSave.getAbsolutePath());
            clientSocket2.closeConnection();


    } catch (Exception e) {
        e.printStackTrace();
        JOptionPane.showMessageDialog(this, &quot;下载失败: &quot; + e.getMessage());
    }
}

private JButton createSmallButton(String text, Color color) {
    JButton btn = new JButton(text);
    btn.setBackground(color);
    btn.setForeground(Color.WHITE);
    btn.setFont(new Font(&quot;微软雅黑&quot;, Font.PLAIN, 12));
    btn.setFocusPainted(false);
    btn.setBorderPainted(false);
    btn.setOpaque(true);
    btn.setCursor(new Cursor(Cursor.HAND_CURSOR));
    return btn;
}

private void styleButton(JButton button, Color color) {
    button.setBackground(color);
    button.setForeground(Color.WHITE);
    button.setFont(new Font(&quot;微软雅黑&quot;, Font.BOLD, 14));
    button.setFocusPainted(false);
    button.setBorderPainted(false);
    button.setOpaque(true);
    button.setCursor(new Cursor(Cursor.HAND_CURSOR));
}

}

4.3 文件传输功能

实现基于 Socket 字节流的跨终端文件传输,核心流程如下:

  1. 客户端选择本地文件,向服务器发送文件元信息(文件名、文件大小、接收方 ID);
  2. 服务器将元信息转发至接收方,接收方确认后,发送方通过专用 Socket 字节流传输文件内容;
  3. 接收方启动独立 I/O 线程读取文件流,选择本地路径完成文件保存,并可选支持下载进度可视化。
ServerHandler.java(文件传输)
case 11:
    File dir = new File("server_files");
    if (!dir.exists()) dir.mkdirs();
    File serverFile = new File(dir,parts[3]);
    InputStream socketIn = clientSocket.getInputStream();
    FileOutputStream fileOut = new FileOutputStream(serverFile);
    byte[] buffer = new byte[1024];
    long totalRead = 0;
    int len;
    while (totalRead < Long.parseLong(parts[4])) {
        len = socketIn.read(buffer);
        if (len == -1) break;
        fileOut.write(buffer, 0, len);
        totalRead += len;
    }
    fileOut.close();
    boolean saved2 = UserInfoDaoImpl.saveMessage(parts);
    if (saved2) {
        Socket receiverSocket = ONLINE_USER_MAP.get(parts[2]);
        if (ONLINE_USER_MAP.containsKey(parts[2])) {
            PrintWriter receiverOut = new PrintWriter(receiverSocket.getOutputStream(), true);
            receiverOut.println("FILE_MSG:" + parts[1] + ":" + parts[3]);
            receiverOut.flush();
        }
        sendResponse("FILE_MSG:文件上传成功");
    }
    break;
case 12:
    boolean saved3 = UserInfoDaoImpl.Filesearch(parts[2], parts[1], parts[3]);
    if(saved3) {// 假设 receiverOut 是 PrintWriter
        if (clientSocket != null) {
            System.out.println("hello");
            try {
                File serverFile1 = new File("server_files", parts[3]);
                FileInputStream fis = new FileInputStream(serverFile1);
                long realFileSize = serverFile1.length();
            OutputStream socketOut = clientSocket.getOutputStream();
            String header = &quot;CMD_FILE_START:&quot; + parts[3] + &quot;:&quot; + realFileSize + &quot;\n&quot;;
            socketOut.write(header.getBytes(&quot;UTF-8&quot;));

            socketOut.flush();

            byte[] buffer1 = new byte[4096]; // 使用更大的缓冲区
            int len1;
            while ((len1 = fis.read(buffer1)) != -1) {
                System.out.println(len1);
                socketOut.write(buffer1, 0, len1);
            }
            fis.close();
            socketOut.flush();
            System.out.println(&quot;文件 &quot; + parts[3] + &quot; 发送完毕&quot;);
        } catch (FileNotFoundException e) {
            PrintWriter errorOut = new PrintWriter(clientSocket.getOutputStream(), true);
            errorOut.println(&quot;CMD_ERROR:文件不存在&quot;);
            errorOut.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
break;

4.4 数据库设计与 JDBC 实现

(1)数据库结构

贴合业务需求设计用户表、好友关系表、聊天记录表等核心表结构。例如:

  • 用户表usernamepasswordpasswordUser create_time 等;
  • 聊天记录表sender_namefriend_namecontentsend_time 等。
  • 朋友关系表: usernamefriend_nameadd_time
  • 在线表:usernameuser_ip

(2)JDBC 连接与配置优化

  • 单例模式:通过静态属性与静态方法实现 JDBC 连接复用(或简易连接池),避免重复加载驱动与创建连接;
  • 资源管理:全面采用 try-with-resources 语法自动关闭 ConnectionStatementResultSet 等资源,有效防止内存泄漏;
  • 异常处理:将 JDBC 的检查型异常封装为自定义运行时异常,简化上层业务逻辑的异常处理流程;
  • 配置解耦:通过 JDBC.properties 文件集中管理数据库连接参数(驱动类、URL、用户名、密码),提升系统可维护性与可移植性。
JdbcUtil.java
package chat.util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import java.io.InputStream;

public class JdbcUtil {
private static Properties props = new Properties();

// 静态代码块加载配置
static {
    try (InputStream is = JdbcUtil.class.getClassLoader().getResourceAsStream(&quot;jdbc.properties&quot;)) {
        props.load(is);
        Class.forName(props.getProperty(&quot;jdbc.driver&quot;));
    } catch (Exception e) {
        throw new RuntimeException(&quot;JDBC配置加载失败&quot;, e);
    }
}

// 获取数据库连接
public static Connection getConnection() {
    try {
        return DriverManager.getConnection(
                props.getProperty(&quot;jdbc.url&quot;),
                props.getProperty(&quot;jdbc.username&quot;),
                props.getProperty(&quot;jdbc.password&quot;)
        );
    } catch (SQLException e) {
        throw new RuntimeException(&quot;数据库连接失败&quot;, e);
    }
}
// 关闭资源
public static void close(Connection conn, PreparedStatement ps, ResultSet rs) {
    try {
        if (rs != null) rs.close();
        if (ps != null) ps.close();
        if (conn != null) conn.close();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}
// 重载:关闭连接和PreparedStatement
public static void close(Connection conn, PreparedStatement ps) {
    close(conn, ps, null);
}

}

jdbc.properties
# ?????
jdbc.driver=com.mysql.cj.jdbc.Driver
# ?????????? ???? ??????????????3306???
jdbc.url=jdbc:mysql://localhost:3306/teststu?useSSL=false&serverTimezone=UTC
jdbc.username=root
jdbc.password=Root@731731

4.5 网络通讯核心设计

  • C/S 架构:服务器端通过 ServerSocket 监听指定端口,采用线程池管理客户端连接,避免单线程阻塞;客户端主动发起 Socket 连接,维持长连接以支撑实时通信;
  • 线程池优化:服务器端使用 ThreadPoolExecutor 复用线程处理客户端请求,有效支持多用户并发在线,显著提升系统吞吐能力;
  • 长连接管理:借助 ConcurrentHashMap(键为用户 ID,值为对应 Socket 对象)安全地管理在线连接,用户登录时注册、退出或异常断开时移除,确保连接管理的线程安全性与一致性。
ChatServer .java
package chat.service;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ChatServer {
private static final int PORT = 8080;
private ExecutorService threadPool;
private volatile boolean isRunning = true;

public static void main(String[] args) {
    System.out.println(&quot;聊天服务器启动&quot;);
    ChatServer server = new ChatServer();
    server.start();
}

public void start() {
    threadPool = Executors.newCachedThreadPool();

    try (ServerSocket serverSocket = new ServerSocket(PORT)) {
        System.out.println(&quot;服务器已启动,监听端口: &quot; + PORT);
        System.out.println(&quot;等待客户端连接...&quot;);

        while (isRunning) {
            Socket clientSocket = serverSocket.accept();
            System.out.println(&quot;检测到新客户端连接: &quot; + clientSocket.getInetAddress().getHostAddress());
            threadPool.submit(new ServerHandler(clientSocket));
        }
    } catch (IOException e) {
        if (isRunning) {
            System.err.println(&quot;服务器异常: &quot; + e.getMessage());
        } else {
            System.out.println(&quot;服务器已停止。&quot;);
        }
    } finally {
        shutdown();
    }
}


 //关闭服务器
public void shutdown() {
    isRunning = false;
    if (threadPool != null) {
        threadPool.shutdown(); // 停止接受新任务
        try {
            // 等待现有任务完成(最多给5秒)
            if (!threadPool.awaitTermination(5, TimeUnit.SECONDS)) {
                threadPool.shutdownNow();
            }
        } catch (InterruptedException e) {
            threadPool.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
    System.out.println(&quot;服务器资源已释放。&quot;);
}

}

五、成果展示

登陆界面:

627b71017b2a4ed6b521973cc19a353c_1768526552052-9f5b0c60-e4c1-4a77-b1a0-40049e27261b_x-oss-process=image%2Fformat%2Cwebp

注册界面:

abbf8c6720d43dbc022a140510bd3242_1768526587107-2a7414da-2420-44cd-b4d1-814b072fdb6c_x-oss-process=image%2Fformat%2Cwebp

聊天界面:
32d232520cb3dadfd2f7e01dc808fde7_1768526624343-13323508-6ad3-4f4b-a8a2-13c50304e0e3_x-oss-process=image%2Fformat%2Cwebp

文件下载界面:
32d232520cb3dadfd2f7e01dc808fde7_1768526624343-13323508-6ad3-4f4b-a8a2-13c50304e0e3_x-oss-process=image%2Fformat%2Cwebp

六、总结及展望

6.1 总结

核心问题与解决方案

问题 解决方案
Socket 长连接管理 基于 ConcurrentHashMap 实现在线 Socket 连接的线程安全管理,登录时添加、退出时移除
文件下载阻塞 为文件流读取创建独立 I/O 线程,与文本消息处理线程隔离,彻底避免界面卡顿
消息实时更新 单客户端独立监听线程 + EDT(事件分发线程)刷新 UI,结合全局 Socket 映射实现精准推送

功能亮点

  1. 高实时通信:采用 TCP 长连接 + 多线程并发监听架构,通过独立线程阻塞式监听输入流,结合 EDT 线程安全更新 UI,实现消息“近零延迟”收发;
  2. 高效文件传输:基于 C-S-C 中继转发模式,通过独立 Socket 通道与专用 I/O 线程处理文件传输,有效避免与文本消息竞争资源,保障传输稳定性与用户体验。

6.2 展望

在现有稳定通信基础上,未来可从安全性、跨平台性、性能与交互体验四个维度进行迭代升级:

  1. 安全增强:引入 SSL/TLS 加密通信链路,实现传输层加密;支持文件传输加密与断点续传,防止数据泄露或篡改;
  2. 跨平台适配:基于 JavaFX 重构客户端界面,拓展至 Android/iOS 移动端,实现多端无缝互通;
  3. 性能优化:针对高并发消息与大文件传输场景开展压力测试,优化线程池参数,引入消息队列(如 RabbitMQ)解耦核心逻辑,进一步提升系统响应速度与稳定性;
  4. 功能拓展:增加多级权限管理、超大型群聊(>100 人)、临时群组、私密聊天(阅后即焚)等高级功能,适配更复杂的社交与协作场景。

七、课程设计感想与问题

本次 QO 聊天室课程设计是一次理论与实践深度融合的宝贵经历。我主要负责客户端界面开发、网络通信逻辑、文件传输及会话管理模块,在技术攻坚与团队协作中收获颇丰:

(1)技术认知升级

深入掌握了 Java Swing GUI 开发、Socket 网络编程、多线程并发处理及 JDBC 数据库操作的核心原理。尤其对“长连接 vs 短连接”“线程隔离 vs 资源共享”等抽象概念有了具象化理解。初期曾因采用 HTTP 短连接模式导致实时通信失效,后通过重构为 TCP 长连接 + ConcurrentHashMap 管理在线连接,最终实现稳定可靠的消息推送,深刻体会到架构设计对系统功能的决定性影响。

(2)问题解决能力提升

在文件传输模块初期,因复用聊天线程导致 I/O 竞争与界面卡顿,通过“独立 Socket 连接 + 专用 I/O 线程”的隔离策略彻底解决;消息实时更新问题则通过“单客户端独立监听线程 + EDT 刷新 UI”的方案,兼顾了并发安全性与界面流畅性。这些问题的攻克,让我学会从“线程模型、资源分配、系统架构”多维度分析并解决复杂工程问题。

(3)团队协作成长

在与队友的分工协作中,掌握了模块化开发、接口规范定义、代码集成与需求对齐的关键技巧,深刻理解了“高内聚、低耦合”在团队项目中的重要性。未来将持续优化代码的健壮性、可读性与可维护性,并持续关注即时通讯领域的前沿技术(如 WebSocket、WebRTC、端到端加密等),为构建更安全、高效、智能的通信系统积累经验。


参考文献

  1. Java Swing 官方文档:https://docs.oracle.com/javase
  2. Java Socket 编程指南:https://www.oracle.com/java/
  3. MySQL JDBC 教程:https://dev.mysql.com/doc/connector-j/8.0/en/
posted @ 2026-01-16 09:43  穗和  阅读(0)  评论(0)    收藏  举报