基于 Java Socket 实现多人在线聊天系统(附完整源码)

基于 Java Socket 实现多人在线聊天系统(附完整源码)在网络编程学习中,Socket(套接字)是实现 TCP/IP 通信的核心载体。本文将手把手教你搭建一个支持注册、登录、群发消息、在线用户查询的多人在线聊天系统,涵盖客户端 / 服务端通信、Swing 图形界面、多线程处理等核心知识点。

一、系统整体架构

本系统采用经典的 C/S(客户端 - 服务端)架构,基于 TCP 协议实现可靠的字节流通信:

  • 服务端:单端口监听(9999),为每个客户端连接创建独立线程,维护在线用户列表、处理登录 / 注册 / 消息转发逻辑。
  • 客户端:提供 Swing 图形界面,支持登录注册、群发消息、查询在线用户,通过多线程处理消息收发(避免 UI 阻塞)。
  • 通信协议:自定义指令格式(指令码:参数),例如1:账号,密码代表登录请求,4:消息内容代表群发消息。

核心技术栈

  • 网络通信:Java Socket(ServerSocket/Socket)
  • 界面开发:Swing(JFrame/JPanel/JList 等组件)
  • 多线程:Thread/Runnable(处理并发连接、异步消息收发)
  • 数据存储:内存集合(ArrayList/HashMap)维护用户 / 连接信息(简易版,实际可替换为数据库)

二、核心代码解析

1. 服务端核心类

(1)消息指令常量类(Message.java)

定义通信指令码,统一客户端和服务端的指令格式,避免硬编码:

package yjq0125.Sever; public class Message { public static final int login=1; // 登录指令 public static final int register=2; // 注册指令 public static final int chatprivate=3;// 私聊指令(本文暂未实现UI,预留) public static final int chatall=4; // 群发指令 public static final int logout=5; // 退出指令 public static final int search=6; // 查询在线用户指令 } 
(2)服务端主线程(Msever.java)

启动 ServerSocket 监听 9999 端口,为每个新客户端连接创建独立的 SeverThread 线程处理通信:

package yjq0125.Sever; import java.net.ServerSocket; import java.net.Socket; public class Msever { public static void main(String[] args) throws Exception { Msever msever=new Msever(); msever.startSever(); } public void startSever() throws Exception { ServerSocket serverSocket = new ServerSocket(9999); System.out.println("启动服务器,监听9999端口..."); int ID = 1; while (true) { Socket socket = serverSocket.accept(); // 阻塞等待客户端连接 System.out.println("客户端"+ID+++"已连接,IP:" + socket.getInetAddress()); SeverThread severThread=new SeverThread(socket); new Thread(severThread).start(); // 每个客户端独立线程 } } } 
(3)客户端连接处理线程(SeverThread.java)

核心逻辑类,处理客户端的登录、注册、群发、查询在线用户等请求:

package yjq0125.Sever; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; public class SeverThread implements Runnable { Socket socket; OutputStream os; InputStream is; public static ArrayList<User> userArrList = new ArrayList<>(); // 所有注册用户 boolean iflogin = false; String id; // 当前登录用户的昵称 public static Map<String, Socket> socketmap = new HashMap<>(); // 在线用户-连接映射 public static Map<String, Boolean> checklogin= new HashMap<>(); // 登录状态 SeverThread(Socket socket) throws IOException { this.socket = socket; os = socket.getOutputStream(); is = socket.getInputStream(); } @Override public void run() { while (true) { try { String s = readmes(); // 读取客户端指令 String[] a = s.split(":"); int choice = Integer.parseInt(a[0]); switch (choice) { case Message.login: // 登录处理 handleLogin(a[1]); break; case Message.register: // 注册处理 handleRegister(a[1]); break; case Message.chatall: // 群发消息 handleChatAll(a[1]); break; case Message.logout: // 退出登录 handleLogout(); break; case Message.search: // 查询在线用户 handleSearchOnline(); break; } } catch (Exception e) { e.printStackTrace(); break; // 连接断开则退出线程 } } } // 登录逻辑 private void handleLogin(String params) throws Exception { String[] mes = params.split(","); String account = mes[0]; String password = mes[1]; boolean ifnameright = false; User user1 = new User(); synchronized (userArrList) { // 加锁避免并发修改 for (User user : userArrList) { if (user.account.equals(account)) { ifnameright = true; user1 = user; break; } } if (!ifnameright) { sendMes(socket, "账号错误登录失败"); } else if (user1.password.equals(password)) { Boolean loginSatus = checklogin.get(account); if (Boolean.TRUE.equals(loginSatus)) { sendMes(socket, "账号已登录,登录失败"); } else { id=user1.idname; checklogin.put(id, true); socketmap.put(id,socket); sendMes(socket, "登录成功"); iflogin = true; } } else { sendMes(socket, "密码错误登录失败"); } } } // 注册逻辑 private void handleRegister(String params) throws Exception { String[] mes = params.split(","); String account = mes[0]; String password = mes[1]; String idname = mes[2]; boolean ifsuccess = true; synchronized (userArrList) { for (User user : userArrList) { if (user.account.equals(account)) { ifsuccess = false; break; } } if (ifsuccess) { userArrList.add(new User(account, password, idname)); sendMes(socket, "注册成功"); } else { sendMes(socket, "账号已存在"); } } } // 群发消息 private void handleChatAll(String msg) throws Exception { if (!iflogin) { sendMes(socket, "没有登录成功不能发送消息"); return; } String sendMsg = "["+id+"]群发:"+msg; synchronized (socketmap) { // 加锁避免遍历中修改map for (Map.Entry<String, Socket> entry : socketmap.entrySet()) { Socket socket1 = entry.getValue(); if (!socket.equals(socket1)) { // 不发给自己 sendMes(socket1, sendMsg); } } } } // 退出登录 private void handleLogout() throws Exception { synchronized (checklogin) { checklogin.put(id, false); synchronized (socketmap) { socketmap.remove(id); } sendMes(socket, "退出登录成功"); iflogin = false; socket.close(); } } // 查询在线用户 private void handleSearchOnline() throws Exception { synchronized (socketmap) { for (Map.Entry<String, Socket> entry : socketmap.entrySet()) { sendMes(socket, entry.getKey()); // 逐个发送在线用户名 } sendMes(socket, "end"); // 结束标识 } } // 读取客户端消息 public String readmes() throws Exception { byte[] b = new byte[1024]; int actualLen = is.read(b); if (actualLen == -1) { throw new IOException("服务器连接已断开"); } String mes = new String(b, 0, actualLen, StandardCharsets.UTF_8); return mes.replace("\r\n", "").trim(); } // 发送消息给客户端 public void sendMes(Socket socket, String mes) throws Exception { OutputStream outputStream = socket.getOutputStream(); String sendStr = mes + "\r\n"; // 加换行符作为结束标识 outputStream.write(sendStr.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); } } 

2. 客户端核心类

(1)客户端 Socket 通信类(MClient.java)

封装 Socket 连接、消息读写、退出等基础通信能力:

package yjq0125.Client; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; public class MClient { public OutputStream os; public InputStream is; public boolean iflogin = false; Socket client; Map<String,String> map=new HashMap<>(); // 存储账号-昵称映射 // 启动客户端连接 public void startClient() throws Exception { client = new Socket("127.0.0.1", 9999); // 连接本地服务端 os = client.getOutputStream(); is = client.getInputStream(); } // 读取服务端消息 public String readmes() throws Exception { byte[] b = new byte[1024]; int actualLen = is.read(b); if (actualLen == -1) { throw new IOException("服务器连接已断开"); } String mes = new String(b, 0, actualLen, StandardCharsets.UTF_8); return mes.replace("\r\n", "").trim(); } // 发送消息到服务端 public void writemes(String s) throws Exception { String str = s + "\r\n"; // 加换行符,服务端识别结束 os.write(str.getBytes()); os.flush(); } // 退出登录并关闭连接 public void logout()throws Exception { if (iflogin) { writemes("5:"); // 发送退出指令 iflogin = false; } if(os!=null)os.close(); if(is!=null)is.close(); if(client!=null)client.close(); } } 
(2)登录注册界面(GameUI.java)

Swing 实现图形化登录注册界面,通过子线程处理网络请求(避免 UI 阻塞):

package yjq0125.Client; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class GameUI { JTextField account; JTextField password; MClient mClient; JFrame jFrame; GameUI() throws Exception { mClient = new MClient(); mClient.startClient(); // 初始化连接 } public static void main(String[] args) throws Exception { GameUI gameUI = new GameUI(); gameUI.initUI(); } public void initUI() { jFrame = new JFrame(); jFrame.setTitle("登录界面"); jFrame.setSize(500, 600); jFrame.setLocationRelativeTo(null); jFrame.setDefaultCloseOperation(3); jFrame.setLayout(new FlowLayout()); // 背景图片(自行替换路径) ImageIcon imageIcon = new ImageIcon("photo/01.jpg"); Image target = imageIcon.getImage().getScaledInstance(500, 400, Image.SCALE_SMOOTH); JLabel jLabel = new JLabel(new ImageIcon(target)); jFrame.add(jLabel); // 账号输入框 JLabel usernameLabel = new JLabel("账号:"); jFrame.add(usernameLabel); account = new JTextField(); account.setPreferredSize(new Dimension(420, 30)); jFrame.add(account); // 密码输入框 JLabel pwdLabel = new JLabel("密码:"); jFrame.add(pwdLabel); password = new JTextField(); password.setPreferredSize(new Dimension(420, 30)); jFrame.add(password); // 昵称输入框(仅注册用) JLabel idnameLabel = new JLabel("用户名:(登录时不用填)"); jFrame.add(idnameLabel); JTextField idname = new JTextField(); idname.setPreferredSize(new Dimension(280, 30)); jFrame.add(idname); // 登录/注册按钮 JButton login = new JButton("登录"); JButton register = new JButton("注册"); jFrame.add(login); jFrame.add(register); // 登录按钮事件 login.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String username = account.getText().trim(); String userpassword = password.getText().trim(); if (username.isEmpty() || userpassword.isEmpty()) { JOptionPane.showMessageDialog(jFrame, "账号和密码不能为空", "提示", JOptionPane.ERROR_MESSAGE); return; } // 子线程处理网络请求 new Thread(() -> { try { mClient.writemes("1:" + username + "," + userpassword); // 发送登录指令 String mes = mClient.readmes(); // 读取响应 // 回到UI线程更新界面 SwingUtilities.invokeAndWait(() -> { account.setText(""); password.setText(""); if (mes.contains("失败")) { JOptionPane.showMessageDialog(jFrame, mes, "登录失败", JOptionPane.ERROR_MESSAGE); } else if (mes.equals("登录成功")) { JOptionPane.showMessageDialog(jFrame, mes, "登录成功", JOptionPane.INFORMATION_MESSAGE); mClient.iflogin = true; new chatUI(username,mClient).initui(); // 打开聊天界面 } }); } catch (Exception ex) { try { SwingUtilities.invokeAndWait(() -> { JOptionPane.showMessageDialog(jFrame, "登录失败:" + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); }); } catch (Exception innerEx) { innerEx.printStackTrace(); } } }).start(); } }); // 注册按钮事件 register.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String username = account.getText().trim(); String userpassword = password.getText().trim(); String idnameText = idname.getText().trim(); if (username.isEmpty() || userpassword.isEmpty() || idnameText.isEmpty()) { JOptionPane.showMessageDialog(jFrame, "账号、密码、用户名不能为空", "提示", JOptionPane.ERROR_MESSAGE); return; } new Thread(() -> { try { mClient.writemes("2:" + username + "," + userpassword + "," + idnameText); String mes = mClient.readmes(); SwingUtilities.invokeAndWait(() -> { account.setText(""); password.setText(""); idname.setText(""); if (mes.equals("注册成功")) { JOptionPane.showMessageDialog(jFrame, mes, "注册成功", JOptionPane.INFORMATION_MESSAGE); mClient.map.put(username,idnameText); } else { JOptionPane.showMessageDialog(jFrame, mes, "注册失败", JOptionPane.ERROR_MESSAGE); } }); } catch (Exception ex) { try { SwingUtilities.invokeAndWait(() -> { JOptionPane.showMessageDialog(jFrame, "注册失败:" + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); }); } catch (Exception innerEx) { innerEx.printStackTrace(); } } }).start(); } }); jFrame.setVisible(true); } } 
(3)聊天界面(chatUI.java)

支持群发消息、查询在线用户列表,通过多线程实时接收服务端消息:

package yjq0125.Client; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; public class chatUI { public String idname; public MClient mClient; JTextArea jTextArea; private DefaultListModel<String> onlineUserModel; // 在线用户列表模型 private JList<String> onlineUserList; boolean ifsearch=false; chatUI(String idname,MClient mClient) { this.idname = idname; this.mClient=mClient; // 初始化在线用户列表 onlineUserModel = new DefaultListModel<>(); onlineUserList = new JList<>(onlineUserModel); onlineUserList.setFont(new Font("楷体", Font.PLAIN, 18)); onlineUserList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); } public void initui() { JFrame jf = new JFrame(idname); jf.setSize(new Dimension(500, 600)); jf.setDefaultCloseOperation(3); jf.setLocationRelativeTo(null); // 中心面板(消息显示+发送) JPanel centerjPanel = new JPanel(); jf.add(centerjPanel, BorderLayout.CENTER); // 左侧面板(在线用户列表) JPanel westjPanel = new JPanel(); westjPanel.setLayout(new BoxLayout(westjPanel, BoxLayout.Y_AXIS)); westjPanel.setBackground(Color.green); westjPanel.setPreferredSize(new Dimension(150, 600)); jf.add(westjPanel, BorderLayout.WEST); // 查找在线用户按钮 JButton search=new JButton("查找在线列表"); westjPanel.add(search); // 在线用户列表(滚动面板) JScrollPane listScroll = new JScrollPane(onlineUserList); listScroll.setPreferredSize(new Dimension(90, 500)); westjPanel.add(listScroll); // 消息显示区域 jTextArea = new JTextArea(); jTextArea.setFont(new Font("楷体", Font.BOLD, 24)); jTextArea.setPreferredSize(new Dimension(400, 450)); jTextArea.setEditable(false); centerjPanel.add(jTextArea); // 消息输入+发送按钮 JLabel jLabel=new JLabel("消息栏:"); centerjPanel.add(jLabel); JTextField jTextField = new JTextField(); jTextField.setPreferredSize(new Dimension(200, 100)); centerjPanel.add(jTextField); JButton button = new JButton("发送"); centerjPanel.add(button); // 实时接收服务端消息 new Thread(() -> { while (!ifsearch) { try { String mes = mClient.readmes(); SwingUtilities.invokeLater(() -> { jTextArea.append("收到:" + mes + "\n"); jTextArea.setCaretPosition(jTextArea.getText().length()); // 自动滚动 }); } catch (Exception e) { SwingUtilities.invokeLater(() -> { jTextArea.append("连接异常:" + e.getMessage() + "\n"); }); break; } } }).start(); // 查询在线用户按钮事件 search.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { ifsearch = true; onlineUserModel.clear(); new Thread(() -> { try { mClient.writemes("6:"); // 发送查询指令 while (true) { String mes = mClient.readmes(); if (mes.equals("end")) break; // 结束标识 if (!mes.equals(idname)) { // 排除自己 SwingUtilities.invokeLater(() -> { onlineUserModel.addElement(mes); }); } } ifsearch = false; } catch (Exception ex) { SwingUtilities.invokeLater(() -> { JOptionPane.showMessageDialog(jf, "查询在线列表失败:" + ex.getMessage()); }); } }).start(); } }); // 发送群发消息按钮事件 button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String mes=jTextField.getText().trim(); if (mes.isEmpty()) return; try { mClient.writemes("4:"+mes); // 发送群发指令 SwingUtilities.invokeLater(() -> { jTextArea.append(idname+"群发:" + mes + "\n"); jTextArea.setCaretPosition(jTextArea.getText().length()); jTextField.setText(""); }); } catch (Exception ex) { throw new RuntimeException(ex); } } }); jf.setVisible(true); } } 

3. 实体类(User.java)

存储用户账号、密码、昵称信息:

package yjq0125.Sever; public class User { String account; String password; String idname; // 昵称 public User() {} public User(String account,String password,String idname) { this.account=account; this.password = password; this.idname=idname; } } 

三、运行步骤

  1. 启动服务端:运行Msever.java的 main 方法,控制台输出 “启动服务器,监听 9999 端口...”。
  2. 启动客户端:运行GameUI.java的 main 方法,弹出登录界面。
  3. 注册账号:输入账号、密码、用户名,点击 “注册”,提示 “注册成功” 即可。
  4. 登录系统:输入注册的账号、密码,点击 “登录”,成功后打开聊天界面。
  5. 功能测试
    • 多开客户端,用不同账号登录;
    • 点击 “查找在线列表”,可看到其他在线用户;
    • 在消息栏输入内容,点击 “发送”,其他客户端可收到群发消息。

四、核心知识点总结

  1. Socket 通信:ServerSocket 监听端口,Socket 建立客户端连接,通过 InputStream/OutputStream 读写字节流。
  2. 多线程:服务端为每个客户端创建独立线程,避免单连接阻塞;客户端用子线程处理网络请求,避免 Swing UI 卡顿。
  3. 线程安全:使用synchronized关键字保护共享集合(userArrList、socketmap),避免并发修改异常。
  4. Swing UI 规范:所有 UI 组件更新必须在 EDT(事件调度线程)中执行(SwingUtilities.invokeLater/invokeAndWait)。
  5. 自定义通信协议:通过 “指令码 + 参数” 的格式,让客户端和服务端明确通信意图,解耦逻辑。

五、扩展优化方向

  1. 功能扩展:实现私聊功能(基于chatprivate=3指令)、消息记录持久化(写入文件 / 数据库)、用户头像等。
  2. 性能优化:使用线程池替代手动创建线程,避免大量客户端连接导致线程爆炸;设置 Socket 超时时间,避免无限阻塞。
  3. 异常处理:完善断连重连机制、输入合法性校验(如密码加密)。
  4. 界面美化:使用 JavaFX 替代 Swing,实现更现代化的 UI。

本系统完整覆盖了 Java Socket 网络编程、多线程、Swing 界面开发的核心知识点,适合入门级学习者理解 C/S 架构的通信原理。

Read more

openclaw多Agent和多飞书机器人配置

增加Agent多个飞书机器人 一个Agent尽量只用一个飞书机器人配置 一:先增加新的agent # 创建新的Agent,命名为new-agnet openclaw agents add new-agnet # 查看创建结果 openclaw agents list 二:新的agent与新的飞书链接 配置agnet下的channels: 在命令行输入 # 配置new-agnet机器人(替换为实际App ID和App Secret) openclaw config set agents.new-agnet.channels.feishu.appId "你的new-agnet 飞书 App ID" openclaw config set agents.new-agnet.channels.feishu.appSecret "你的new-agnet 飞书 App Secret"

By Ne0inhk
OpenClaw 完整部署指南:安装 + 三大 Coding Plan 配置 + CC Switch + 飞书机器人

OpenClaw 完整部署指南:安装 + 三大 Coding Plan 配置 + CC Switch + 飞书机器人

OpenClaw 完整部署指南:安装 + 三大 Coding Plan 配置 + CC Switch + 飞书机器人 * 📋 文章目录结构 * 1.3 一键安装 OpenClaw(推荐) * 1.4 通过 npm 手动安装 * 1.5 运行 Onboard 向导 * 1.6 验证安装 * 步骤二:配置 Coding Plan 模型 * 🅰️ 选项 A:阿里百炼 Coding Plan * A.1 订阅与获取凭证 * A.2 在 OpenClaw 中配置 * A.3 可用模型列表

By Ne0inhk
开启AI绘画 “工作流时代” 的神奇应用----Comfy UI | 使用CNB平台搭建ComfyUI

开启AI绘画 “工作流时代” 的神奇应用----Comfy UI | 使用CNB平台搭建ComfyUI

文章目录 * 概要 * 操作流程 概要 ComfyUI 是一款基于节点流程的可视化 AI 生成工具,核心围绕 Stable Diffusion 等主流生成式 AI 算法构建,通过图形化节点拆解生成全流程,实现从文本 / 图像输入到图像 / 视频输出的 “精准可控创作”。 腾讯云 CNB(Cloud Native Build,官网:cnb.cool)是基于 Docker 生态的云原生开发协作平台,核心定位是通过容器化技术与资源池化能力,为开发者提供 “一键就绪” 的远程开发环境,尤其聚焦开源项目协作与 AI 工具落地,无需本地配置复杂硬件与环境即可开展开发、测试与创作。链接:cnb 操作流程 接下来展示使用腾讯云cnb搭建comfyui的流程: (1)到CNB网站 fork 项目 链接:cnb 可以直接使用已经搭建好的comfyui

By Ne0inhk

OpenClaw对接飞书机器人高频踩坑实战指南:从插件安装到回调配对全解析

前言 当前企业办公场景中,将轻量级AI框架OpenClaw与飞书机器人结合,能够快速实现智能交互、流程自动化等功能。然而,在实际对接过程中,开发者常常因权限配置、环境依赖、回调设置等细节问题陷入反复试错。本文以“问题解决”为核心,梳理了10个典型踩坑点,每个问题均配套原因分析、排查步骤和实操案例。同时,补充高效调试技巧与功能扩展建议,帮助开发者系统性地定位并解决对接障碍,提升落地效率。所有案例基于Windows 11环境、OpenClaw最新稳定版及飞书开放平台最新界面验证,解决方案可直接复用。 一、前置准备(快速自查) 为避免基础环境问题浪费时间,建议在开始前确认以下三点: * OpenClaw已正确安装,终端执行 openclaw -v 可查看版本(建议使用最新版,旧版本可能存在插件兼容风险)。 * Node.js版本不低于v14,npm版本不低于v6,通过 node -v 和 npm -v 验证,防止因依赖版本过低导致插件安装失败。 * 飞书账号需具备企业开发者权限(企业账号需管理员授权,个人账号默认具备)

By Ne0inhk