2026/3/1 6:23:46
网站建设
项目流程
建立什么样的网站赚钱,网站 制作 工具,网站建站模式,wordpress 显示文章摘要Java Socket 聊天室 - 零基础手把手教程
GitHub地址#xff1a;https://github.com/RONGX563647/NewChatRoom 本教程面向零基础学习者#xff0c;从最基础的概念开始#xff0c;一步步带你完成一个完整的聊天室项目。 目录
准备工作基础知识第一阶段#xff1a;创建项目第…Java Socket 聊天室 - 零基础手把手教程GitHub地址https://github.com/RONGX563647/NewChatRoom本教程面向零基础学习者从最基础的概念开始一步步带你完成一个完整的聊天室项目。目录准备工作基础知识第一阶段创建项目第二阶段公共类第三阶段服务端第四阶段客户端第五阶段功能完善运行测试常见问题一、准备工作1.1 你需要准备什么JDKJava开发工具包下载地址https://www.oracle.com/java/technologies/downloads/建议版本JDK 8 或更高安装后配置环境变量IDE集成开发环境推荐IntelliJ IDEA Community Edition免费下载地址https://www.jetbrains.com/idea/download/备选Eclipse、VS Code基础知识Java基础语法类、方法、变量面向对象概念基本的网络概念IP、端口1.2 检查Java环境打开命令行Windows: CMD/PowerShellMac: Terminal输入java -version如果显示版本信息说明安装成功java version 17.0.1 2021-10-19 LTS Java(TM) SE Runtime Environment (build 17.0.112-39)二、基础知识2.1 什么是Socket简单理解Socket套接字就像电话机服务端Socket 总机等待来电客户端Socket 普通电话主动拨打IP地址 电话号码端口号 分机号工作流程服务端创建ServerSocket → 等待连接 → 接受连接 → 收发数据 客户端创建Socket → 连接服务端 → 收发数据2.2 什么是C/S架构C/S Client客户端/ Server服务端就像微信服务端腾讯的服务器处理消息转发、存储数据客户端你手机上的微信APP显示界面、发送消息2.3 什么是多线程比喻单线程 一个人同时只能做一件事多线程 多个人同时做不同的事为什么需要多线程服务端要同时处理多个客户端的连接客户端要同时发消息 收消息 更新界面三、第一阶段创建项目3.1 在IntelliJ IDEA中创建项目步骤1新建项目打开IntelliJ IDEA点击 New Project选择 Java项目名输入NewChatRoom点击 Create步骤2创建目录结构在src文件夹上右键 → New → Package依次创建src/ ├── common/ 存放客户端和服务端共用的类 ├── server/ 存放服务端代码 └── client/ 存放客户端代码为什么要这样分common两边都要用的类如消息格式server只在服务端运行的代码client只在客户端运行的代码四、第二阶段公共类4.1 什么是Message类作用定义消息的格式让客户端和服务端能说同一种语言想象一下两个人打电话需要约定怎么打招呼消息类型谁打来的发送者打给谁接收者说什么内容消息内容4.2 创建Message类步骤在common包上右键 → New → Java Class类名输入Message复制以下代码package common; import java.io.Serializable; /** * 消息类 - 用于客户端和服务端之间传递数据 * * 实现Serializable接口表示这个类的对象可以被网络传输 * 就像把信件装进信封可以邮寄出去 */ public class Message implements Serializable { // serialVersionUID 是版本号用于验证序列化兼容性 // 如果不加编译器会自动生成但建议手动指定 private static final long serialVersionUID 1L; /** * 消息类型枚举 * 枚举就像单选题只能选其中一个 */ public enum Type { LOGIN, // 登录 PRIVATE_CHAT, // 私聊 GROUP_CHAT, // 群聊 ONLINE_NOTIFY, // 上线通知 OFFLINE_NOTIFY, // 下线通知 FILE_PRIVATE, // 私聊文件 FILE_GROUP, // 群聊文件 SHAKE, // 窗口抖动 CREATE_GROUP, // 创建群组 JOIN_GROUP, // 加入群组 GROUP_LIST, // 群组列表 REGISTER, // 注册 REGISTER_RESPONSE // 注册响应 } // 成员变量 private Type type; // 消息类型必填 private String sender; // 发送者谁发的 private String receiver; // 接收者发给谁 private String content; // 消息内容文字内容 // 文件传输相关 private String fileName; // 文件名 private long fileSize; // 文件大小 private byte[] fileData; // 文件内容字节数组 // 群聊相关 private String groupId; // 群组ID private String groupName; // 群组名称 // 构造方法 /** * 构造方法1用于普通文字消息 * * param type 消息类型 * param sender 发送者 * param receiver 接收者 * param content 消息内容 */ public Message(Type type, String sender, String receiver, String content) { this.type type; this.sender sender; this.receiver receiver; this.content content; } /** * 构造方法2用于简单的通知类消息 * * param type 消息类型 * param sender 发送者 */ public Message(Type type, String sender) { this.type type; this.sender sender; this.content ; } /** * 构造方法3用于文件传输 * * param type 消息类型 * param sender 发送者 * param receiver 接收者 * param fileName 文件名 * param fileSize 文件大小 * param fileData 文件数据 */ public Message(Type type, String sender, String receiver, String fileName, long fileSize, byte[] fileData) { this.type type; this.sender sender; this.receiver receiver; this.fileName fileName; this.fileSize fileSize; this.fileData fileData; } // Getter和Setter方法 // 用于获取和设置成员变量的值 public Type getType() { return type; } public void setType(Type type) { this.type type; } public String getSender() { return sender; } public void setSender(String sender) { this.sender sender; } public String getReceiver() { return receiver; } public void setReceiver(String receiver) { this.receiver receiver; } public String getContent() { return content; } public void setContent(String content) { this.content content; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName fileName; } public long getFileSize() { return fileSize; } public void setFileSize(long fileSize) { this.fileSize fileSize; } public byte[] getFileData() { return fileData; } public void setFileData(byte[] fileData) { this.fileData fileData; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId groupId; } public String getGroupName() { return groupName; } public void setGroupName(String groupName) { this.groupName groupName; } }代码解释implements Serializable表示这个类的对象可以被序列化转换成字节流只有序列化的对象才能通过网络传输就像把物品打包才能快递enum Type枚举类型定义所有可能的消息类型使用枚举而不是字符串可以避免拼写错误编译器会检查类型是否正确构造方法构造方法是创建对象时自动调用的方法提供多个构造方法适应不同场景这就是方法重载的概念Getter/Setter获取和设置私有变量的标准方法遵循封装原则保护数据安全IDEA可以自动生成右键 → Generate → Getter and Setter4.3 创建Group类步骤在common包上右键 → New → Java Class类名输入Group复制以下代码package common; import java.io.Serializable; import java.util.ArrayList; import java.util.List; /** * 群组类 - 表示一个聊天群组 */ public class Group implements Serializable { private static final long serialVersionUID 1L; private String groupId; // 群组唯一ID private String groupName; // 群组名称 private ListString members; // 成员列表存用户名 /** * 构造方法 * param groupId 群组ID * param groupName 群组名称 */ public Group(String groupId, String groupName) { this.groupId groupId; this.groupName groupName; this.members new ArrayList(); // 初始化空列表 } /** * 添加成员 * param username 用户名 */ public void addMember(String username) { // 检查是否已存在避免重复添加 if (!members.contains(username)) { members.add(username); } } /** * 移除成员 * param username 用户名 */ public void removeMember(String username) { members.remove(username); } // Getter和Setter public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId groupId; } public String getGroupName() { return groupName; } public void setGroupName(String groupName) { this.groupName groupName; } public ListString getMembers() { return members; } public void setMembers(ListString members) { this.members members; } /** * 获取成员数量 */ public int getMemberCount() { return members.size(); } }五、第三阶段服务端5.1 服务端的作用比喻服务端就像邮局接收所有信件客户端连接根据地址转发信件消息转发管理用户信息在线列表5.2 最简服务端步骤在server包上右键 → New → Java Class类名输入ChatServer复制以下代码package server; import common.Message; import java.io.*; import java.net.*; /** * 聊天室服务端 - 最简版本 */ public class ChatServer { // 服务端监听的端口号 // 端口号范围0-65535建议使用1024以上的 private static final int PORT 8888; public static void main(String[] args) { System.out.println( 聊天室服务端启动 ); try { // 1. 创建ServerSocket总机 // 参数是端口号客户端需要通过这个端口连接 ServerSocket serverSocket new ServerSocket(PORT); System.out.println(服务端已启动监听端口 PORT); // 2. 无限循环等待客户端连接 while (true) { System.out.println(等待客户端连接...); // accept() 是阻塞方法会一直等待直到有客户端连接 // 返回的Socket代表与这个客户端的连接 Socket clientSocket serverSocket.accept(); System.out.println(客户端已连接 clientSocket.getInetAddress()); // 3. 为每个客户端创建处理线程 // 为什么要用线程因为accept()会阻塞不用线程就只能处理一个客户端 ClientHandler handler new ClientHandler(clientSocket); handler.start(); // 启动线程 } } catch (IOException e) { System.err.println(服务端错误 e.getMessage()); e.printStackTrace(); } } /** * 客户端处理线程类 * 继承Thread类重写run()方法 */ static class ClientHandler extends Thread { private Socket socket; // 与客户端的连接 private ObjectInputStream ois; // 输入流接收消息 private ObjectOutputStream oos; // 输出流发送消息 private String username; // 用户名 public ClientHandler(Socket socket) { this.socket socket; } /** * 线程的入口方法 * 当调用start()时会自动执行run() */ Override public void run() { try { // 1. 初始化流 // 注意顺序必须先创建输出流再创建输入流 // 否则会阻塞 oos new ObjectOutputStream(socket.getOutputStream()); ois new ObjectInputStream(socket.getInputStream()); System.out.println(与客户端建立通信通道); // 2. 循环接收消息 Message message; while ((message (Message) ois.readObject()) ! null) { System.out.println(收到消息 message.getType() 来自 message.getSender()); // 处理消息 handleMessage(message); } } catch (IOException | ClassNotFoundException e) { System.out.println(客户端断开连接 username); } finally { // 3. 清理资源 closeConnection(); } } /** * 处理收到的消息 */ private void handleMessage(Message message) throws IOException { switch (message.getType()) { case LOGIN: handleLogin(message); break; case PRIVATE_CHAT: handlePrivateChat(message); break; default: System.out.println(未知消息类型 message.getType()); } } /** * 处理登录 */ private void handleLogin(Message message) throws IOException { this.username message.getSender(); System.out.println(用户登录 username); // 发送登录成功响应 Message response new Message( Message.Type.LOGIN, 服务器, username, 登录成功欢迎 ); oos.writeObject(response); oos.flush(); // 立即发送不要缓存 } /** * 处理私聊 */ private void handlePrivateChat(Message message) { System.out.println(私聊消息 message.getSender() → message.getReceiver() 内容 message.getContent()); // 暂时只打印后面会实现转发 } /** * 关闭连接 */ private void closeConnection() { try { if (ois ! null) ois.close(); if (oos ! null) oos.close(); if (socket ! null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }5.3 代码详解1. ServerSocket 的创建ServerSocket serverSocket new ServerSocket(PORT);绑定到指定端口开始监听客户端连接请求一个端口只能被一个程序占用2. accept() 方法Socket clientSocket serverSocket.accept();阻塞方法直到有客户端连接才返回返回的Socket代表与客户端的连接每个客户端有独立的Socket3. 为什么要用多线程不用线程的情况 客户端A连接 → 处理A → A断开 → 客户端B连接 → 处理B 同一时间只能处理一个客户端 用线程的情况 客户端A连接 → 启动线程处理A 客户端B连接 → 启动线程处理B 同时处理多个客户端4. 流的顺序很重要// 正确顺序 oos new ObjectOutputStream(socket.getOutputStream()); ois new ObjectInputStream(socket.getInputStream()); // 错误顺序会阻塞 ois new ObjectInputStream(socket.getInputStream()); oos new ObjectOutputStream(socket.getOutputStream());原因ObjectOutputStream构造时会发送头信息ObjectInputStream构造时会等待头信息。如果两边顺序不一致就会互相等待导致死锁。六、第四阶段客户端6.1 客户端的作用比喻客户端就像你的手机微信APP显示界面聊天窗口发送消息输入文字点击发送接收消息显示新消息提醒6.2 最简客户端步骤在client包上右键 → New → Java Class类名输入ChatClient复制以下代码package client; import common.Message; import javax.swing.*; // GUI组件 import java.awt.*; // 布局和事件 import java.awt.event.*; import java.io.*; import java.net.*; /** * 聊天室客户端 - 最简版本命令行界面 */ public class ChatClient { private static final String SERVER_IP 127.0.0.1; // 服务器IP private static final int SERVER_PORT 8888; // 服务器端口 private Socket socket; // 与服务器的连接 private ObjectOutputStream oos; // 输出流发消息 private ObjectInputStream ois; // 输入流收消息 private String username; // 用户名 public static void main(String[] args) { // 从命令行读取用户名 String username JOptionPane.showInputDialog(请输入用户名); if (username null || username.trim().isEmpty()) { System.out.println(用户名不能为空); return; } ChatClient client new ChatClient(); client.start(username); } /** * 启动客户端 */ public void start(String username) { this.username username; try { // 1. 连接服务器 System.out.println(正在连接服务器...); socket new Socket(SERVER_IP, SERVER_PORT); System.out.println(连接成功); // 2. 初始化流 oos new ObjectOutputStream(socket.getOutputStream()); ois new ObjectInputStream(socket.getInputStream()); // 3. 发送登录消息 Message loginMsg new Message( Message.Type.LOGIN, username, , ); oos.writeObject(loginMsg); oos.flush(); // 4. 接收登录响应 Message response (Message) ois.readObject(); System.out.println(服务器回复 response.getContent()); // 5. 启动消息接收线程 new Thread(new ReceiveThread()).start(); // 6. 开始发送消息命令行输入 BufferedReader reader new BufferedReader( new InputStreamReader(System.in) ); System.out.println(\n可以开始聊天了输入 exit 退出); System.out.println(格式接收者:消息内容\n); String input; while ((input reader.readLine()) ! null) { if (exit.equalsIgnoreCase(input)) { break; } // 解析输入接收者:内容 String[] parts input.split(:, 2); if (parts.length 2) { String receiver parts[0].trim(); String content parts[1].trim(); Message msg new Message( Message.Type.PRIVATE_CHAT, username, receiver, content ); oos.writeObject(msg); oos.flush(); System.out.println(我 → receiver : content); } else { System.out.println(格式错误请使用接收者:消息内容); } } } catch (Exception e) { System.err.println(客户端错误 e.getMessage()); e.printStackTrace(); } finally { closeConnection(); } } /** * 消息接收线程 */ class ReceiveThread implements Runnable { Override public void run() { try { Message message; while ((message (Message) ois.readObject()) ! null) { // 显示收到的消息 System.out.println(\n【 message.getSender() 】 message.getContent()); System.out.print( ); // 提示符 } } catch (IOException | ClassNotFoundException e) { System.out.println(\n与服务器的连接已断开); } } } /** * 关闭连接 */ private void closeConnection() { try { if (ois ! null) ois.close(); if (oos ! null) oos.close(); if (socket ! null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } }6.3 测试运行步骤编译代码在IntelliJ IDEA中点击菜单 Build → Build Project启动服务端找到ChatServer.java右键 → Run ChatServer.main()应该看到输出服务端已启动监听端口8888启动第一个客户端找到ChatClient.java右键 → Run ChatClient.main()输入用户名如张三应该看到服务器回复登录成功欢迎启动第二个客户端再次右键 → Run ChatClient.main()输入另一个用户名如李四现在有两个客户端在线了发送消息测试在张三的客户端输入李四:你好啊查看李四的客户端是否收到消息6.4 代码详解1. Socket 连接socket new Socket(SERVER_IP, SERVER_PORT);参数1服务器IP地址参数2服务器端口号127.0.0.1表示本机用于测试2. 为什么需要接收线程主线程发送消息等待用户输入 接收线程接收消息等待服务器推送 如果只有一个线程 - 等待输入时无法接收消息 - 等待消息时无法输入3. 输入格式解析String[] parts input.split(:, 2);split(:, 2)按冒号分割最多分成2份李四:你好啊→[李四, 你好啊]这样消息内容中可以有冒号七、第五阶段功能完善7.1 添加在线用户列表目标服务端维护在线用户列表登录时广播给所有客户端修改 ChatServer在ChatServer类中添加// 存储在线用户用户名 - 输出流 private static MapString, ObjectOutputStream onlineUsers new ConcurrentHashMap(); /** * 广播在线用户列表 */ private static void broadcastOnlineUsers() { // 获取所有用户名 ListString userList new ArrayList(onlineUsers.keySet()); // 创建消息 Message message new Message(Message.Type.ONLINE_USERS, 服务器); message.setOnlineUsers(userList); // 发送给所有在线用户 for (ObjectOutputStream oos : onlineUsers.values()) { try { oos.writeObject(message); oos.flush(); } catch (IOException e) { e.printStackTrace(); } } }在handleLogin方法中添加private void handleLogin(Message message) throws IOException { this.username message.getSender(); // 添加到在线列表 onlineUsers.put(username, oos); System.out.println(用户登录 username 当前在线 onlineUsers.size()); // 发送登录成功响应 Message response new Message( Message.Type.LOGIN, 服务器, username, 登录成功欢迎 ); oos.writeObject(response); oos.flush(); // 广播在线用户列表 broadcastOnlineUsers(); }在closeConnection之前添加// 从在线列表移除 if (username ! null) { onlineUsers.remove(username); broadcastOnlineUsers(); System.out.println(用户离线 username 当前在线 onlineUsers.size()); }注意需要在文件顶部添加导入import java.util.*; import java.util.concurrent.ConcurrentHashMap;7.2 实现私聊转发修改handlePrivateChat方法private void handlePrivateChat(Message message) throws IOException { String receiver message.getReceiver(); ObjectOutputStream targetOos onlineUsers.get(receiver); if (targetOos ! null) { // 转发消息 targetOos.writeObject(message); targetOos.flush(); System.out.println(转发消息 message.getSender() → receiver); } else { // 用户不在线发送提示 Message tip new Message( Message.Type.PRIVATE_CHAT, 服务器, message.getSender(), 用户 receiver 不在线 ); oos.writeObject(tip); oos.flush(); } }7.3 添加图形界面现在我们为客户端添加GUI界面让聊天室更好看。创建 ChatClientGUI 类package client; import common.Message; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.util.List; /** * 聊天室客户端 - 图形界面版本 */ public class ChatClientGUI extends JFrame { private static final String SERVER_IP 127.0.0.1; private static final int SERVER_PORT 8888; // 网络相关 private Socket socket; private ObjectOutputStream oos; private ObjectInputStream ois; private String username; // 界面组件 private JTextArea chatArea; // 聊天显示区 private JTextField inputField; // 输入框 private JListString userList; // 用户列表 private DefaultListModelString userListModel; // 用户列表数据模型 private JComboBoxString targetBox; // 发送目标选择 public static void main(String[] args) { // 设置外观 try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { e.printStackTrace(); } // 获取用户名 String username JOptionPane.showInputDialog(请输入用户名); if (username null || username.trim().isEmpty()) { return; } // 启动客户端 SwingUtilities.invokeLater(() - { new ChatClientGUI(username).setVisible(true); }); } public ChatClientGUI(String username) { this.username username; // 初始化界面 initUI(); // 连接服务器 connectToServer(); } /** * 初始化界面 */ private void initUI() { // 窗口设置 setTitle(聊天室 - username); setSize(800, 600); setLocationRelativeTo(null); // 居中 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 主面板 JPanel mainPanel new JPanel(new BorderLayout(10, 10)); mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // 左侧用户列表 JPanel leftPanel new JPanel(new BorderLayout()); leftPanel.setPreferredSize(new Dimension(150, 0)); leftPanel.setBorder(BorderFactory.createTitledBorder(在线用户)); userListModel new DefaultListModel(); userList new JList(userListModel); userList.setFont(new Font(微软雅黑, Font.PLAIN, 14)); leftPanel.add(new JScrollPane(userList), BorderLayout.CENTER); // 中间聊天区域 JPanel centerPanel new JPanel(new BorderLayout(5, 5)); // 聊天记录显示区 chatArea new JTextArea(); chatArea.setFont(new Font(微软雅黑, Font.PLAIN, 14)); chatArea.setEditable(false); // 只读 chatArea.setLineWrap(true); // 自动换行 centerPanel.add(new JScrollPane(chatArea), BorderLayout.CENTER); // 底部输入区域 JPanel inputPanel new JPanel(new BorderLayout(5, 0)); // 目标选择 targetBox new JComboBox(); targetBox.setFont(new Font(微软雅黑, Font.PLAIN, 14)); targetBox.setPreferredSize(new Dimension(120, 30)); inputPanel.add(targetBox, BorderLayout.WEST); // 输入框 inputField new JTextField(); inputField.setFont(new Font(微软雅黑, Font.PLAIN, 14)); inputPanel.add(inputField, BorderLayout.CENTER); // 发送按钮 JButton sendBtn new JButton(发送); sendBtn.setFont(new Font(微软雅黑, Font.PLAIN, 14)); sendBtn.addActionListener(e - sendMessage()); inputPanel.add(sendBtn, BorderLayout.EAST); // 回车发送 inputField.addActionListener(e - sendMessage()); centerPanel.add(inputPanel, BorderLayout.SOUTH); // 组装界面 mainPanel.add(leftPanel, BorderLayout.WEST); mainPanel.add(centerPanel, BorderLayout.CENTER); add(mainPanel); } /** * 连接服务器 */ private void connectToServer() { try { socket new Socket(SERVER_IP, SERVER_PORT); oos new ObjectOutputStream(socket.getOutputStream()); ois new ObjectInputStream(socket.getInputStream()); // 发送登录消息 Message loginMsg new Message(Message.Type.LOGIN, username, , ); oos.writeObject(loginMsg); oos.flush(); // 启动接收线程 new Thread(new ReceiveThread()).start(); appendMessage(系统, 已连接到服务器); } catch (IOException e) { JOptionPane.showMessageDialog(this, 连接服务器失败 e.getMessage()); System.exit(1); } } /** * 发送消息 */ private void sendMessage() { String content inputField.getText().trim(); if (content.isEmpty()) { return; } String target (String) targetBox.getSelectedItem(); if (target null) { JOptionPane.showMessageDialog(this, 请选择发送对象); return; } try { Message message new Message( Message.Type.PRIVATE_CHAT, username, target, content ); oos.writeObject(message); oos.flush(); appendMessage(我 → target, content); inputField.setText(); // 清空输入框 } catch (IOException e) { appendMessage(系统, 发送失败 e.getMessage()); } } /** * 添加消息到显示区 */ private void appendMessage(String sender, String content) { SwingUtilities.invokeLater(() - { chatArea.append(【 sender 】\n content \n\n); // 自动滚动到底部 chatArea.setCaretPosition(chatArea.getDocument().getLength()); }); } /** * 更新在线用户列表 */ private void updateUserList(ListString users) { SwingUtilities.invokeLater(() - { userListModel.clear(); targetBox.removeAllItems(); for (String user : users) { if (!user.equals(username)) { // 不显示自己 userListModel.addElement(user); targetBox.addItem(user); } } }); } /** * 消息接收线程 */ class ReceiveThread implements Runnable { Override public void run() { try { Message message; while ((message (Message) ois.readObject()) ! null) { switch (message.getType()) { case PRIVATE_CHAT: appendMessage(message.getSender(), message.getContent()); break; case ONLINE_USERS: updateUserList(message.getOnlineUsers()); break; default: appendMessage(message.getSender(), message.getContent()); } } } catch (Exception e) { SwingUtilities.invokeLater(() - { appendMessage(系统, 与服务器的连接已断开); }); } } } }注意需要在Message类中添加onlineUsers字段和对应的getter/setterprivate ListString onlineUsers; public ListString getOnlineUsers() { return onlineUsers; } public void setOnlineUsers(ListString onlineUsers) { this.onlineUsers onlineUsers; }八、运行测试8.1 完整测试流程启动服务端运行ChatServer看到服务端已启动监听端口8888启动客户端1运行ChatClientGUI输入用户名张三看到界面和已连接到服务器启动客户端2运行ChatClientGUI输入用户名李四张三的界面应该更新在线用户列表发送消息测试张三选择李四输入消息点击发送李四应该收到消息李四回复张三应该收到8.2 常见问题排查问题1连接被拒绝原因服务端没启动或端口被占用 解决 1. 确认服务端已启动 2. 检查端口8888是否被其他程序占用 3. 检查防火墙设置问题2序列化异常原因Message类没有实现Serializable或类版本不一致 解决 1. 确认实现了Serializable 2. 确认serialVersionUID一致 3. 重新编译所有代码问题3消息发不出去原因流没有flush() 解决每次writeObject后都要调用flush()问题4界面卡死原因在事件调度线程执行耗时操作 解决网络操作放在后台线程九、进阶功能9.1 添加群聊功能思路服务端维护群组列表创建群组时生成唯一ID群聊消息转发给所有群成员9.2 添加文件传输思路读取文件为字节数组通过Message的fileData字段传输接收方保存到本地9.3 添加用户认证思路服务端存储用户名和密码登录时验证密码注册时添加新用户十、总结10.1 学到的知识点Socket网络编程ServerSocket和Socket的使用输入输出流的操作对象序列化传输多线程编程Thread类的使用线程安全问题Swing的线程规则Swing GUI编程常用组件JFrame、JPanel、JButton等布局管理器BorderLayout等事件处理机制面向对象设计类的封装枚举的使用代码分层common/server/client10.2 后续学习方向数据库- 使用MySQL存储用户和消息加密传输- 使用SSL/TLS保护通信Android开发- 制作手机客户端Web开发- 使用WebSocket开发网页版附录完整代码下载所有代码已包含在项目中的src目录下common/Message.java- 消息类common/Group.java- 群组类server/ChatServer.java- 服务端client/ChatClient.java- 命令行客户端client/ChatClientGUI.java- 图形界面客户端祝你学习愉快