资讯动态

Java基于WebSocket的聊天系统:从课程设计到答辩完整拆解

发布时间:2026/10/29 15:41:31 来源:尧图企业网站定制
简介面向网络编程技术课程设计场景一套Java基于WebSocket的聊天系统项目包含完整源代码与课程设计报告。系统实现用户名密码登录、多人同时在线、在线用户同步显示、文字消息收发、群聊、管理员禁言/解除禁言、一对一会话及历史记录缓存读取数据库负责保存用户信息与聊天记录功能覆盖常见即时通讯模块。资源包共74个文件、约7.3MB其中14个java源码文件为核心后端逻辑3个html与3个js文件支撑前端交互配以css样式、properties与xml配置、sql建表脚本以及png/jpg截图和docx课程设计报告便于对照代码查看界面效果与设计思路。目前已有238人浏览学习适合网络编程课程设计、Java Web开发实践或毕业设计参考。获取后可得到可直接导入IDE的Maven项目结构通过在线用户列表与私聊群聊模块理解WebSocket长连接机制结合报告与源码梳理鉴权、消息推送、禁言等功能的实现路径。1. Java 基于 WebSocket 的聊天系统课程设计从能跑到能答辩的完整拆解网络编程课程设计一遇到聊天系统四个字很多人的第一反应是直接去搜一个现成的 Spring Boot 项目改改交差。但真拿到这份 Java WebSocket 的源代码和课程设计报告时你会发现它和你平时复制粘贴的 CRUD 项目完全不一样推送消息、在线用户维护、群聊禁言、私聊、历史记录缓存每一个模块都在和网络连接的状态打交道调不通的时候连日志都看不到纯属黑匣子。我拆完这套websocket-master源码后最大的感受是它的技术选型和学习路径都挺典型的——后端是 Java 生态里最常见的 Maven 工程前端用原生 WebSocket API 建长连接数据库负责持久化用户信息和聊天记录。适合正在做网络编程课程设计、Java 毕设或者想弄懂 WebSocket 实际怎么落地的人。这篇笔记我会把代码结构和关键实现拆开讲哪些是能直接抄的哪些地方得按你自己的环境调都会说清楚。2. WebSocket 与 HTTP 的选型为什么课程设计大多选它2.1 长连接和短轮询的差别直接决定项目工作量聊天系统最核心的需求是服务端主动推消息给客户端。如果用纯 HTTP常见做法是前端每隔几秒轮询一次问有没有新消息。这种方式对小型课程设计其实也能实现但有两个明显问题一是实时性差你永远有延迟二是每次轮询都要带上完整的 HTTP 头在线用户一多服务端压力全花在没意义的请求上。而 WebSocket 建立的是 TCP 长连接一次握手成功后服务端可以随时把消息推给指定的客户端不需要客户端反复发起请求。这份源码里的聊天模块核心处理逻辑全都挂在 WebSocket 端点Endpoint上只要客户端和服务端的握手成功后续就是一个持续的数据通道。这也是为什么大多数网络编程课程设计只要涉及实时聊天老师都会建议用 WebSocket。我在实际拆这份代码时发现整个工程从pom.xml里引的依赖就能看出来它走的是 Java EE / Spring WebSocket 的标准实现路线没有引入额外的重量级框架。对于答辩来说你能说清楚为什么用 WebSocket 而不是轮询比背几个接口名重要得多。2.2 前后端各自要管好什么一份责任划分清单聊天系统最怕的是逻辑全挤在后端或者前端只管显示导致整个项目没有层次。拿到这份源码建议你先把它拆成三个关注点这样后面看代码不会迷路。关注点对应模块具体职责连接生命周期握手、建立、关闭拿到用户身份加入在线列表断开时移除消息路由群聊、私聊、系统消息判断消息类型决定是广播给所有人还是发给指定用户数据落地用户表、聊天记录表登录校验、消息入库、历史记录读取前端部分你会看到它用 WebSocket 对象创建连接监听onmessage事件来接收消息。后端的在线用户列表是整个系统的核心枢纽——谁上线了、谁下线了、谁被禁言了都要在这个列表上有状态标记。源码里维护在线用户的方式是典型的 Java 集合结构用ConcurrentHashMap保存会话以用户名为 key以 WebSocketSession对象为 value。为什么不用普通的HashMap因为 WebSocket 支持多线程并发访问当多个用户同时发送消息时Session会被不同线程操作ConcurrentHashMap能在保证线程安全的同时不锁整个集合这是课程设计里很重要的一个选型细节答辩时老师问到的概率极高。3. 搭建消息中枢后端 WebSocket 端点与在线用户管理3.1 从 pom.xml 看技术栈你需要准备什么环境打开源码包里的pom.xml你会发现它的依赖数量并不多这是课程设计项目的典型特征不是越复杂越好。核心依赖主要有两个方向一个是 Java 对 WebSocket 的支持在 Spring Boot 里通常体现为spring-boot-starter-websocket另一个是数据库访问层对应到本项目就是处理用户信息和聊天记录持久化。由于项目正文里有mvnw.cmd和 Maven Wrapper说明它自带 Maven 包装器你不需要单独装特定版本的 Maven直接执行mvnw命令就能构建这对环境不一致的课程设计场景非常友好。我建议你按下面的步骤把工程先跑起来再开始看业务代码# 在项目根目录执行Maven 会自动读取 pom.xml 并下载依赖 ./mvnw clean package # 打包成功后生成 target 目录下的 jar 文件 java -jar target/websocket-chat-1.0.jarclean package会先清理之前编译的产物再重新打包。这一步能提前把依赖缺失、JDK 版本不匹配这类环境问题暴露出来。如果构建时卡在下载依赖多半是网络原因把 Maven 仓库地址换成国内镜像即可。打完包直接跑起来控制台出现 Spring Boot 启动日志说明环境没问题接下来才值得去啃代码。3.2 后端核心类Session 管理与消息广播的实现逻辑打开src/main/java下的代码你会看到一个专门负责 WebSocket 端点处理的类通常标记了ServerEndpoint注解。这个类就是整个聊天系统的入口它里面的核心方法对应 WebSocket 的生命周期onOpen、onMessage、onClose、onError。源码里最常见的写法是这样ServerEndpoint(/chat/{username}) public class ChatEndpoint { // 用 ConcurrentHashMap 保存所有在线用户的 Session键是用户名值是 WebSocket Session private static MapString, Session onlineUsers new ConcurrentHashMap(); OnOpen public void onOpen(Session session, PathParam(username) String username) { onlineUsers.put(username, session); // 广播一条系统消息告诉所有人谁上线了 sendToAll([系统] username 加入了聊天室); } OnMessage public void onMessage(String message, Session session) { // 解析消息如果是以 用户名 开头就走私聊逻辑 if (message.startsWith()) { String targetUser message.substring(1, message.indexOf(:)); sendToUser(targetUser, message); } else { sendToAll(message); } } OnClose public void onClose(Session session, PathParam(username) String username) { onlineUsers.remove(username); sendToAll([系统] username 离开了聊天室); } }这里有几个参数值得你注意。ServerEndpoint(/chat/{username})中的{username}是路径参数前端在创建 WebSocket 连接时会把当前登录的用户名拼进 URL后端用PathParam取出来。onlineUsers声明为static是因为 WebSocket 端点的实例在每次连接时都会重新创建如果不用静态变量不同用户的消息就无法共享同一个在线列表。sendToAll是广播方法底层逻辑是遍历onlineUsers里的每个 Session调用getBasicRemote().sendText()发送数据。我在改这份代码时习惯先把onOpen里的业务逻辑简化只做用户加入列表和广播系统消息把数据库操作放到后面再做。因为聊天系统最核心的链路是一个用户发消息其他人马上能看到这条链路通了再往上加登录校验和禁言功能会清晰很多。3.3 登录校验与在线用户列表数据库的第一次介入这个项目的登录设计很朴素用户名和密码都从数据库读取没有做注册功能所以在课程设计报告里你需要重点写清楚数据库表结构。我拆源码时发现它一般会有两张表一张是用户表存用户名和密码另一张是聊天记录表存发送者、接收者、消息内容和发送时间。登录校验的逻辑可以抽象成这样一个流程public User login(String username, String password) { // 使用 MyBatis 或 JPA 查询数据库 User user userMapper.findByUsernameAndPassword(username, password); if (user ! null) { // 登录成功把用户放入 Session 或 Redis标记在线状态 return user; } return null; }很多初学者会直接把密码明文存数据库如果课程设计只是演示功能老师一般不会深究。但如果你想让答辩更有亮点可以在报告里加一句密码使用 MD5 加盐存储避免明文入库。这属于加分项不需要改太多代码只需要在插入用户数据时多做一步加密处理。在线用户列表的维护是整个系统最容易出错的地方原因在于 WebSocket 的onClose方法并不总是可靠触发——客户端断网、浏览器直接关闭时服务端要等 TCP 超时才知道连接断了。如果不在onError和onClose里都做清理在线列表里就会残留一堆死 Session后面广播消息时就会报IllegalStateException。4. 前端长连接与消息收发从握手到广播的完整链路4.1 前端 WebSocket 对象参数和后端是怎么对齐的前端页面打开后第一步是创建 WebSocket 连接。JavaScript 里的 WebSocket API 已经很成熟源码里常见的写法是// username 是当前登录的用户名从页面隐藏域或者 localStorage 中获取 let username document.getElementById(username).value; let ws new WebSocket(ws://localhost:8080/chat/ username); ws.onopen function() { console.log(连接成功); // 连接成功后可以发送一条上线通知 }; ws.onmessage function(event) { let message event.data; // 在这里处理消息并渲染到页面的消息列表区域 appendMessage(message); }; ws.onclose function() { console.log(连接关闭); };这段代码里最需要注意的是 URL 的协议头是ws://而不是http://并且路径里的username必须和后端ServerEndpoint(/chat/{username})的占位符对应上。如果前端没有传username或者路径不一致后端会直接命中 404 或握手失败。我在调试时经常遇到的翻车场景是前端写成了ws://localhost:8080/chat?usernamexxx用了查询参数而后端是从路径变量里取值这种不匹配改起来很快但排查起来会让人怀疑人生。4.2 消息格式约定群聊、私聊和系统消息怎么区分源码里对所有消息的处理方式简单说就是用固定格式区分类型然后前端根据类型渲染不同的样式。比如群聊消息就是一段纯文本私聊消息前面加用户名:系统消息前缀是[系统]。后端的onMessage方法拿到消息后先做字符串判断再决定广播还是单发。这种设计的好处是代码量小课程设计完全够用坏处是如果消息内容本身以开头会被误判为私聊这是协议设计层面的问题。如果你觉得纯字符串判断不够优雅可以改成 JSON 消息格式{ type: chat, target: all, content: 你好世界, sender: 张三, timestamp: 1710000000000 }后端解析时用 Java 内置的JsonParser或者 Jackson判断target字段是all还是具体用户名。我一般会建议课程设计用 JSON因为这样前端的onmessage处理逻辑可以更统一不需要对字符串做各种indexOf切割后期加已读回执、消息类型这些扩展功能也更方便。源码里如果用的还是字符串方案你可以照着重构成 JSON 格式这个改进在答辩时很能体现你的理解和改动能力。4.3 在线用户列表的实时刷新前端渲染与后端广播的配合在线用户列表看起来是前端的功能但实际上它依赖后端每次onOpen和onClose时广播新列表。常见做法是后端定义一个专门的系统消息每次有用户上线或下线就广播一次完整的在线用户集合给所有人。前端收到后把列表重新渲染一遍。ws.onmessage function(event) { let data JSON.parse(event.data); if (data.type system) { // 更新在线用户列表 renderUserList(data.onlineUsers); } else if (data.type chat) { appendMessage(data.sender 说 data.content); } };这里有一个要注意的地方如果在线用户数量不多全量推送列表没有问题但人数一旦上百每次上线都推送全量列表就会浪费带宽。更优的做法是后端只通知谁上线了、谁下线了由前端自己维护列表。不过课程设计阶段全量推送是最稳的代码逻辑简单不容易出并发问题。我拆这份源码时发现它的在线列表数据是从后端实时读取的没有做缓存这意味着每次广播都要遍历ConcurrentHashMap复杂度是 O(n)但对于小型聊天室完全够用。5. 避坑/常见问题必踩的五个坑与排查方法5.1 Maven 构建失败依赖下载不了卡在package阶段现象执行mvnw clean package时控制台一直停留在下载依赖的进度条最后报Could not transfer artifact错误。原因默认的 Maven 中央仓库在海外网络访问不稳定尤其在学校机房的网络环境下很多 jar 包下载超时。解决打开pom.xml所在的.mvn目录或者在~/.m2/settings.xml里配置阿里云镜像。如果工程已经自带了settings.xml直接在mirrors节点下加一个镜像地址即可。配置好之后重新构建依赖下载速度会明显提升。这个坑几乎每个用 Maven 的课程设计都会遇到不是代码问题是环境问题。5.2 前端连接后端一直 404浏览器控制台报 WebSocket 握手失败现象前端new WebSocket(ws://localhost:8080/chat/zhangsan)之后控制台显示WebSocket connection failed后端日志也没看到连接请求。原因最常见的两种情况一种是路径不对后端端点路径和前端路径不一致另一种是上下文路径问题如果你的 Spring Boot 项目设置了server.servlet.context-path/api那 WebSocket 的路径也会变成ws://localhost:8080/api/chat/zhangsan少写/api就握手不到。解决先看后端启动日志里 WebSocket 端点注册的完整路径再用浏览器的开发者工具查看请求 URL。一个简单的做法是在前端代码里把 URL 打成字符串打印出来然后手动复制到浏览器直接访问能看到响应就说明路径对了。这一点是血泪经验多少人花半天时间调握手最后只是路径少个字母。5.3 在线用户列表出现幽灵用户明明下线了还显示在线现象用户关闭浏览器页面但聊天室里的在线用户列表还保留着他的名字过一会儿再广播消息后端报The WebSocket session [xxx] has been closed。原因浏览器关闭时WebSocket 关闭帧不一定能及时发到服务端服务端onClose方法没有被触发。部分前端的beforeunload事件里没有主动关闭连接或者断网时 TCP 连接要进行超时重试导致服务端迟迟不知道客户端已经跑了。解决在服务端onOpen方法里给 Session 注册一个监听器定期检查 Session 是否仍然有效。如果是 Spring WebSocket可以配置心跳检测比如每 30 秒发送一次 ping 帧如果连续几次没有收到 pong就主动关闭这个 Session。前端也在页面卸载时调用ws.close()这样大部分情况下能正常触发onClose。从那以后我每次写 WebSocket 项目都会强制检查关闭连接这条路径是否覆盖了所有异常场景。5.4 广播消息时部分用户收到部分用户收不到现象群聊发送一条消息A 用户能看到B 用户看不到在后端日志里广播代码确实执行了没有报错。原因问题通常出在ConcurrentHashMap里的 Session 对象已经失效或者前端创建连接时有多个 WebSocket 实例共存。比如用户打开多个浏览器标签页每个标签页都会建立一个独立连接后端onlineUsers以用户名为 key后连接的那个 Session 覆盖了先前的导致之前标签页的 Session 被顶掉发消息时那条连接自然收不到。解决在广播方法里发送消息之前检查session.isOpen()如果为 false 就移除并跳过。另外建议前端在尝试建立新连接时先把旧的ws实例关闭避免覆盖。代码里处理方式是这样public void sendToAll(String message) { onlineUsers.forEach((username, session) - { try { if (session.isOpen()) { session.getBasicRemote().sendText(message); } } catch (IOException e) { onlineUsers.remove(username); } }); }isOpen()判断是防止你对一个已经关闭的 Session 调sendText那会直接抛异常影响整个广播操作。5.5 数据库保存聊天记录同时写库和推送怎么保证顺序不乱现象聊天记录存到数据库后前端的消息顺序和数据库里的顺序不一致刷新页面后历史记录顺序是乱的。原因推送消息和写数据库是两个独立操作如果先写库再推送可能出现推送成功了写库失败或者反过来如果没有用时间戳排序历史记录读取出来就是按插入顺序或者主键顺序不一定跟发送顺序一致。解决在消息类里显式加上发送时间字段前端渲染时用这个字段排序数据库查询时也按这个字段排序不要依赖数据库的自增 ID。具体做法是把时间戳放在消息对象里作为排序依据。历史记录查询的逻辑可以这样写SELECT sender, receiver, content, send_time FROM chat_record WHERE (sender ? AND receiver ?) OR (sender ? AND receiver ?) ORDER BY send_time ASC这个查询方式主要解决私聊记录的有序取回群聊记录需要在chat_record表里加一个group_id字段然后按group_id和send_time联合排序。我在拆这份源码时发现它的历史记录缓存用了本地内存存储也就是用一个List把最近的消息暂存在内存里这个方案可以当作演示但服务端一重启缓存就丢了如果时间允许把内存缓存替换成像 Redis 这样的外部缓存答辩加分会很明显。6. 让答辩更稳的进阶技巧心跳机制、历史记录缓存与私聊的边界处理WebSocket 课程设计做到能聊天只是及格答辩时要想让老师眼前一亮必须在三个点上做强化。第一个是心跳机制。很多同学不知道 WebSocket 连接看似还在实际已经断了我一般会建议在后端加一个定时器每 30 秒向所有 Session 发一次 Ping收到 Pong 的才保留两轮没回应的直接判离线。这个机制能完美解决前面提到的幽灵用户问题而且代码量很小只需要一个ScheduledExecutorService就能实现。第二个是把历史记录缓存从本地内存改成 Redis课程设计里不需要整套缓存策略最简单的做法是发送消息时同时写入 MySQL 和 Redis 的 List 结构读取历史记录时先查 Redis查不到再查 MySQL。这样前端刷新页面加载历史记录的速度会快很多而且能证明你对缓存一致性有基本理解。第三个是私聊的权限边界源码里私聊的入口是消息以用户名开头但如果被私聊的用户不在线这条消息会丢失你得在后端加一层判断如果目标用户不在线就返回一条用户不在线给发送方这样体验才完整。我拆完这份websocket-master后最深的感触是网络编程课程设计的难点不在于写多少代码而在于怎么把连接管理这件事想清楚。任何聊天系统的所有问题最后都归结为连接还在不在、消息该发给谁、发不出去怎么办这三个问题。如果你也正在被 WebSocket 的调试折磨别急着改代码先把你项目里的在线用户列表打印出来看每一次连接事件是不是符合预期再看消息的路由判断逻辑是怎么走的思路就顺了。这份源码和报告我建议先按原文把整个流程跑通再动手改数据和样式希望能帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑