当前位置: 首页 > news >正文

Java Socket 多人聊天室(私聊+群聊)技术笔记(V4版本)

一、项目整体架构

本项目是基于 BIO 阻塞式 Socket 实现的桌面多人聊天室,支持用户登录、群聊、一对一私聊功能,搭配Swing实现可视化客户端界面。整体分为服务端、客户端、用户实体、UI界面四大核心模块,采用 服务端中转消息 的通信模式(所有客户端消息统一发送至服务端,由服务端分发转发)。

1.1 核心模块分工

  • Server(服务端):监听端口、接收客户端连接、处理用户登录、解析消息类型、转发私聊/群聊消息、维护在线用户列表
  • Client(客户端):建立服务端连接、用户账号登录、封装私聊/群聊消息、发送消息、接收服务端转发的消息
  • ClientUser(用户实体):封装在线用户信息(账号、Socket连接),服务端核心存储对象
  • ClientUI(可视化界面):基于Swing实现聊天窗口、输入框、发送按钮,绑定消息收发事件

1.2 通信核心原理

基于TCP长连接通信,客户端与服务端建立永久Socket连接,全程流式传输字节数据;通过 自定义消息协议 区分私聊、群聊消息,服务端统一调度转发,实现多客户端通信。

二、自定义通信协议(核心重点)

项目无第三方通信框架,完全自定义消息格式,通过固定字节顺序解析消息类型、发送目标、消息内容,是实现私聊/群聊区分的核心。

2.1 消息类型定义

  • 1:私聊消息(定向发送指定用户)
  • 2:群聊消息(广播给所有在线用户,除发送者自身)

2.2 完整消息协议格式

私聊消息格式:消息类型(1字节) + 好友名称长度(1字节) + 好友名称(字节数组) + 消息长度(1字节) + 消息内容(字节数组)
群聊消息格式:消息类型(1字节) + 消息长度(1字节) + 消息内容(字节数组)
登录消息格式:账号长度(1字节) + 账号名称(字节数组)

三、用户实体类(ClientUser)

服务端用于存储所有在线用户的核心实体,绑定用户账号与Socket连接,实现精准消息转发。

packageV4;importjava.net.Socket;/** * 在线用户实体类:封装单个客户端的连接信息与账号 * 服务端集合通过该对象维护所有在线用户 */publicclassClientUser{Stringaccount;// 客户端登录账号Socketsocket;// 客户端对应的Socket连接对象// 构造方法:登录成功后创建用户对象publicClientUser(Socketsocket,Stringaccount){this.account=account;this.socket=socket;}}

四、客户端核心实现(Client)

客户端核心功能:初始化连接、账号登录、封装发送消息、接收服务端返回消息。所有IO操作基于字节流传输,严格遵循自定义协议。

packageV4;importjava.io.IOException;importjava.io.InputStream;importjava.io.OutputStream;importjava.net.Socket;importjava.util.Scanner;/** * 聊天室客户端核心类 * 负责连接服务端、登录、消息发送、消息接收 */publicclassClient{Socketsocket=null;// 客户端Socket连接对象// 初始化:连接服务端 + 执行登录逻辑publicvoidinit(){try{// 连接本地服务端,端口与服务端一致:54321socket=newSocket("127.0.0.1",54321);login(socket.getOutputStream());}catch(IOExceptione){thrownewRuntimeException(e);}}// 用户登录:向服务端发送账号信息publicvoidlogin(OutputStreamos){Scannersc=newScanner(System.in);System.out.println("请输入账号:");Stringaccount=sc.nextLine();byte[]accounts=account.getBytes();try{// 先发送账号长度,再发送账号内容(服务端按长度读取,避免粘包)os.write(accounts.length);os.write(accounts);os.flush();// 强制刷新流,确保数据立即发送}catch(IOExceptione){thrownewRuntimeException(e);}}/** * 消息发送核心方法:区分私聊/群聊,封装协议数据 * 规则:消息以@开头为私聊,否则为群聊 */publicvoidsend(Stringmessage){try{OutputStreamos=socket.getOutputStream();// 判断消息类型:首字符@ 代表私聊if(message.charAt(0)=='@'){// 拆分私聊格式:@好友名 消息内容intindex=message.indexOf(' ');Stringfriend=message.substring(1,index);// 截取目标好友账号Stringmsg=message.substring(index+1);// 截取消息内容// 按照私聊协议封装数据os.write(1);// 1=私聊类型标识intfidlen=friend.getBytes().length;os.write(fidlen);// 好友名称字节长度os.write(friend.getBytes());// 好友名称字节数据intmsglen=msg.getBytes().length;os.write(msglen);// 消息内容字节长度os.write(msg.getBytes());// 消息内容字节数据}else{// 群聊消息封装os.write(2);// 2=群聊类型标识byte[]msgs=message.getBytes();os.write(msgs.length);// 消息长度os.write(msgs);// 消息内容}os.flush();// 强制刷新,完成消息发送}catch(IOExceptione){thrownewRuntimeException(e);}}// 读取服务端转发的消息publicStringreadMsg(){try{InputStreamins=socket.getInputStream();// 先读长度,再读内容,解决TCP粘包问题intlen=ins.read();byte[]msgs=newbyte[len];ins.read(msgs);returnnewString(msgs);}catch(IOExceptione){thrownewRuntimeException(e);}}staticvoidmain(){}}

4.1 客户端核心关键点备注

  • 粘包解决核心:所有消息传输遵循「先传长度、再传内容」,服务端根据长度精准读取字节,避免TCP流式传输导致的粘包、半包问题
  • 消息区分规则:通过消息首字符@判定私聊,空格分割好友名称与消息内容
  • 流刷新机制:每次write后必须执行flush(),否则数据会滞留缓冲区,无法及时发送

五、服务端核心实现(Server)

服务端是整个聊天室的中枢,负责监听连接、处理登录、解析消息、转发消息、维护在线用户列表,通过多线程支持多客户端同时在线。

packageV4;importjava.io.IOException;importjava.io.InputStream;importjava.io.OutputStream;importjava.net.ServerSocket;importjava.net.Socket;importjava.util.ArrayList;/** * 聊天室服务端核心类 * 端口监听、客户端连接管理、消息解析、消息转发 */publicclassServer{ServerSocketss=null;// 全局在线用户列表:存储所有已登录的客户端连接ArrayList<ClientUser>socketlist<>();// 初始化服务端:绑定固定端口54321publicvoidinit(){try{ss=newServerSocket(54321);}catch(IOExceptione){thrownewRuntimeException(e);}}// 监听客户端连接:阻塞方法,等待客户端接入publicSocketlisten(){try{returnss.accept();}catch(IOExceptione){thrownewRuntimeException(e);}}// 统一消息发送工具:服务端向客户端转发消息publicvoidsend(Stringmessage,OutputStreamos){try{// 遵循长度+内容协议发送消息intlen=message.getBytes().length;os.write(len);os.write(message.getBytes());os.flush();}catch(IOExceptione){thrownewRuntimeException(e);}}/** * 消息解析方法:读取客户端发送的原始字节流 * 根据消息类型(1/2)分别解析私聊、群聊数据 * 返回带标识的字符串,用于后续转发逻辑处理 */publicStringreceive(InputStreamin){try{inttype=in.read();// 读取消息类型if(type==1){// 解析私聊消息intfidLen=in.read();// 好友名称长度byte[]fid=newbyte[fidLen];in.read(fid);StringfidStr=newString(fid);// 目标好友账号intmlen=in.read();// 消息长度byte[]msgs=newbyte[mlen];in.read(msgs);Stringmsg=newString(msgs);// 消息内容// 自定义分隔符# 拼接数据,方便后续拆分处理return"1"+"#"+fidStr+"#"+msg;}else{// 解析群聊消息intlen=in.read();byte[]msgs=newbyte[len];in.read(msgs);Stringmsg=newString(msgs);return"2"+"#"+msg;}}catch(IOExceptione){thrownewRuntimeException(e);}returnnull;}/** * 处理客户端所有消息的核心方法 * 每个客户端连接单独开启一个子线程,实现多客户端并发 */publicvoidproClientMsg(Socketsocket){newThread(()->{// 1. 处理用户登录,获取登录账号Stringaccount=prologin(socket);// 2. 将在线用户存入集合ClientUserclientUser=newClientUser(socket,account);socketlist.add(clientUser);// 3. 循环监听当前客户端的所有消息while(true){try{InputStreamin=clientUser.socket.getInputStream();Stringmsg=receive(in);String[]datas=msg.split("#");// 拆分解析后的消息数据Stringtype=datas[0];// 私聊消息转发逻辑if(type.equals("1")){Stringfid=datas[1];// 目标好友账号Stringmsg1=datas[2];// 消息内容// 遍历在线用户列表,精准匹配目标用户for(ClientUserotCU:socketlist){if(otCU.account.equals(fid)){// 拼接发送者信息,转发给目标用户msg1=clientUser.account+"说:"+msg1;send(msg1,otCU.socket.getOutputStream());break;}}}else{// 群聊消息转发逻辑:广播给所有在线用户(排除自己)Stringmsg1=datas[1];msg1=clientUser.account+"说:"+msg1;for(ClientUserotCU:socketlist){Socketsocket1=otCU.socket;if(socket1!=clientUser.socket){send(msg1,socket1.getOutputStream());}}}}catch(IOExceptione){thrownewRuntimeException(e);}}}).start();}// 处理用户登录:读取客户端发送的账号信息publicStringprologin(Socketsocket){try{InputStreamin=socket.getInputStream();intlen=in.read();byte[]msgs=newbyte[len];in.read(msgs);Stringaccount=newString(msgs);System.out.println("用户名:"+account+" 上线了 ");returnaccount;}catch(IOExceptione){thrownewRuntimeException(e);}}// 服务端启动入口staticvoidmain(){Serverserver=newServer();server.init();// 循环监听客户端连接(主线程阻塞,单独开启线程)newThread(()->{while(true){System.out.println("监听连接中~");Sockets=server.listen();System.out.println(s.getInetAddress()+":"+s.getPort()+"连接成功!");server.proClientMsg(s);// 处理该客户端的所有后续逻辑}}).start();}}

5.1 服务端核心关键点备注

  • 多线程并发:每个客户端连接单独创建子线程处理消息,避免单线程阻塞,支持多用户同时在线
  • 消息转发机制:所有消息不直接点对点传输,必须经过服务端中转,实现统一管理
  • 私聊精准匹配:通过遍历在线用户集合,匹配账号名称,实现定向消息发送
  • 群聊广播过滤:转发群聊消息时排除发送者自身,避免自己收到自己的消息
  • 数据分隔处理:使用#作为自定义分隔符,拆分消息类型、目标账号、消息内容,解析简单高效

六、可视化UI界面实现(ClientUI)

基于Swing实现桌面可视化窗口,包含聊天记录展示区、消息输入框、发送按钮,同时开启子线程实时接收服务端消息,避免UI阻塞。

packageV4;importjavax.swing.*;importjava.awt.*;/** * 聊天室可视化客户端界面 * 基于Swing实现,绑定消息收发事件 */publicclassClientUIextendsJFrame{Clientclient=newClient();// 绑定客户端核心通信对象publicstaticvoidmain(){ClientUIclientUI=newClientUI();clientUI.showUI();}// 初始化展示UI窗口publicvoidshowUI(){// 启动客户端连接+登录client.init();// 窗口基础配置JFramejf=newJFrame("小小聊天室");jf.setSize(500,550);jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);jf.setLocationRelativeTo(null);// 窗口居中// 聊天记录展示区域(带滚动条)JTextAreamsgArea=newJTextArea();JScrollPanescrollPane=newJScrollPane(msgArea);scrollPane.setPreferredSize(newDimension(0,470));// 消息输入框、发送按钮JTextFieldmsgField=newJTextField();msgField.setBackground(Color.pink);JButtonsendBtn=newJButton("发送");msgField.setPreferredSize(newDimension(370,0));sendBtn.setPreferredSize(newDimension(100,0));// 组件布局添加jf.add(scrollPane,BorderLayout.NORTH);jf.add(msgField,BorderLayout.WEST);jf.add(sendBtn,BorderLayout.EAST);jf.setVisible(true);// 绑定发送按钮点击事件sendBtn.addActionListener(e->sendMsg(msgArea,msgField));// 开启子线程:实时接收服务端消息,刷新UI(防止主线程阻塞)newThread(()->{while(true){Stringmsg=client.readMsg();msgArea.append(msg+"\n");// 追加消息到聊天界面}}).start();}// 发送消息事件:点击按钮触发publicvoidsendMsg(JTextAreamsgArea,JTextFieldmsgField){Stringmsg=msgField.getText();// 本地先展示自己发送的消息msgArea.append("我说:"+msg+"\n");// 调用客户端方法发送消息到服务端client.send(msg);msgField.setText("");// 清空输入框}}

6.1 UI核心关键点备注

  • 子线程接收消息:消息接收是阻塞循环操作,必须放在子线程,否则会卡死Swing主线程(UI刷新线程)
  • 本地消息回显:自己发送的消息本地直接展示,服务端只转发他人消息,提升体验
  • 滚动文本域:使用JScrollPane包裹文本域,实现聊天记录滚动查看

七、项目启动流程总结

  1. 启动 Server服务端,监听54321端口,等待客户端连接
  2. 启动 ClientUI客户端,输入账号完成登录,服务端记录在线用户
  3. 客户端输入消息:无@为群聊,@好友+空格+内容为私聊
  4. 客户端封装协议消息发送至服务端
  5. 服务端解析消息类型,转发至对应目标用户
  6. 客户端子线程接收转发消息,刷新UI展示聊天内容
http://www.jsqmd.com/news/1284097/

相关文章:

  • 5分钟快速导出QQ空间全部历史说说的终极指南:GetQzonehistory工具完全教程
  • 河北专业的FRP玻璃钢格栅厂如何挑选才能省心 - 品牌优推
  • 亚龙湾贝壳找房门店 海南旅居置业选房全流程实用指南 - 品牌优推
  • 河南不锈钢屏风定制 可满足商用家装多元装修场景需求 - 品牌优推
  • 智习室避坑指南:技术适配与合规性才是核心,天学网实测数据揭秘
  • 选江西实木木作全屋定制订购厂家怎么避坑2026 - 品牌优推
  • 2026国产AI电商营销工具推荐 优劣对比与选购技巧
  • Netty从入门到精通9
  • kill-doc:打破文档获取壁垒的开源浏览器脚本解决方案
  • 庭院排水系统设计中容易忽略的几处关键点
  • Unity UI进阶:UI布局组件(Horizontal/Vertical Layout Group)使用
  • 2026龙华居民搬家上门公司 正规搬迁服务选择实用指南 - 品牌优推
  • 2026优选:新中式家具品牌实力厂家深度解析 - 优企名品
  • 2026贵州考生 370 分,重庆专科填报指南来了 - 2027品牌AI展
  • AI 电动竹火炬智能功率 MOSFET 完整选型方案
  • 计算机毕业设计之基于SpringBoot的党员学习交流平台的设计与实现
  • 2026年标签印刷品牌厂商哪家可靠 实用选购参考指南 - 品牌优推
  • FlashDB超轻量级嵌入式数据库:从零到精通的完整指南
  • 新中式庭院设计融合传统概念与现代需求的要点
  • 玄幻仙侠3D漫剧人设分享提示词
  • 抖音批量下载终极指南:5分钟学会无水印视频批量保存
  • 【转帖】每周质量报告
  • 小学生AI编程启蒙:用Python循环批量处理Prompt任务
  • 手写高性能在线 JSON 工具|Web Worker 工程化打包 + 语法自动修复 + 多语言代码生成实战
  • 2026年靠谱钟框品牌厂商怎么选择 实用选购全指南 - 品牌优推
  • 家装工装选大规格装饰板材 1200X2700岩板品牌厂商有哪些 - 品牌优推
  • 魔珐星云实战:让商场导购 Agent 从聊天框走向真实接待场景
  • 衡阳生鲜托盒定制厂家 按需适配本地生鲜商户实际需求 - 品牌优推
  • 超低损耗 PTFE 复合介质芯层,广氟 GFN 系列构筑高频 PCB 基材国产化核心壁垒
  • 河北户外景观工厂 可承接小区景观水景古建一体化施工项目 - 品牌优推