资讯详情

Java Web聊天系统实战:Servlet+WebSocket实现高分大作业

📅 2026/10/7 6:27:59 | 华诺云谱 👁 阅读
Java Web聊天系统实战:Servlet+WebSocket实现高分大作业
简介这是一份面向Java Web初学者与课程设计学生的完整聊天系统实战项目基于Spring Boot Vue前后端分离架构实现覆盖Web开发核心流程与典型模块划分。资源共138个文件包含66个Java后端代码涵盖Controller、Service、DAO、Entity、DTO、VO等分层逻辑、12个Vue前端组件、11个SCSS样式文件、9个Markdown文档说明及配套图片、配置与构建脚本压缩包仅2.08MB轻量易部署。已有1152人学习下载适合用于高校Java Web大作业实践、全栈入门训练或分层架构教学参考。项目严格遵循MVC分层规范config管理配置、processor集成过滤器与监听器、utils封装通用工具、service提供接口化业务逻辑配合详细文档说明便于理解各模块职责与协作关系可直接运行调试并快速掌握企业级项目组织方式。1. Java Web大作业聊天系统不是写个Socket就叫“实时”它得扛住50人同时发消息不卡顿、不丢消息、不崩页面你交过Java Web大作业也见过同学用ServerSocketBufferedReader硬刚聊天界面——页面一刷新所有历史消息清空两人同时发消息后发的直接覆盖前一条换台电脑访问连不上自己建的“服务器”。这不是聊天系统这是玄学交互黑匣子。真正的Java Web聊天系统核心不在“能聊”而在状态可追溯、连接可维持、消息不丢失、页面不重载。它必须基于HTTP长连接或WebSocket协议栈配合Servlet容器生命周期管理、会话隔离、消息广播机制和前端事件驱动渲染才能让老师点开浏览器看到“张三说你好”、“李四回复收到”这两条消息严格按时间顺序、跨设备同步显示。适合计算机专业大三学生——已有Servlet/JSP基础、能配Tomcat、会写简单HTMLJS但没碰过异步通信和状态同步的同学。别被“大作业”三个字吓住本方案不依赖Spring Boot自动装配纯Java EE标准组件实现代码量可控在800行以内部署到本地Tomcat 9.0.83即可跑通所有依赖仅需JDK 11 Tomcat jQueryCDN引入零额外框架。2. 用原生ServletWebSocket在Tomcat上跑通最小聊天系统从建模块到收第一条消息2.1 创建标准Java Web模块避开IDEA 2024新建Web项目时的classloader陷阱IntelliJ IDEA 2024版本创建Web项目时默认勾选“Add framework support”并强制绑定Maven但本次大作业要求轻量、可调试、无构建工具干扰。正确做法是手动创建标准目录结构绕过IDE自动配置引发的ClassNotFoundExceptionmkdir -p chat-system/src/main/java/com/example/chat mkdir -p chat-system/src/main/webapp/WEB-INF touch chat-system/src/main/webapp/WEB-INF/web.xml touch chat-system/src/main/webapp/index.jsp提示不要用IDE的“New Project → Jakarta EE Web Application”向导——它会生成pom.xml并默认启用Servlet 5.0而Tomcat 9.0仅支持Servlet 4.0版本错位会导致ServerEndpoint注解失效。务必手动创建后续再导入为普通Java项目。web.xml必须显式声明Servlet版本与监听器这是Tomcat识别WebSocket端点的前提?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 display-nameChat System/display-name !-- 启用WebSocket支持 -- listener listener-classjavax.websocket.server.ServerContainer/listener-class /listener /web-app关键点version4.0对应Servlet 4.0规范ServerContainer监听器是Tomcat 9内嵌WebSocket容器的启动开关。漏掉这一行后续写的ServerEndpoint类将完全不被加载——这是新手翻车率最高的第一步。2.2 实现WebSocket端点用ServerEndpoint接管连接生命周期而非手写Socket线程池Java EE 7起WebSocket已标准化为javax.websocket.*包无需Netty或Jetty私有API。核心是定义一个带ServerEndpoint注解的POJO它自动绑定到URL路径并由容器管理连接// src/main/java/com/example/chat/ChatEndpoint.java package com.example.chat; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Set; ServerEndpoint(/chat) public class ChatEndpoint { // 全局在线用户Session集合注意非线程安全需同步 private static final SetSession sessions Collections.synchronizedSet(new HashSet()); OnOpen public void onOpen(Session session) { sessions.add(session); broadcast(【系统】 session.getId() 已上线, session); } OnMessage public void onMessage(String message, Session session) throws IOException { String formatted 【 session.getId().substring(0, 5) 】 message; broadcast(formatted, session); } OnClose public void onClose(Session session) { sessions.remove(session); broadcast(【系统】 session.getId() 已下线, session); } OnError public void onError(Session session, Throwable throwable) { System.err.println(Session session.getId() error: throwable.getMessage()); } // 广播给除发送者外所有客户端 private void broadcast(String msg, Session exclude) throws IOException { for (Session s : sessions) { if (!s.equals(exclude) s.isOpen()) { s.getBasicRemote().sendText(msg); } } } }逻辑说明ServerEndpoint(/chat)将该类映射到ws://localhost:8080/chat-system/chat前端通过new WebSocket()连接此地址sessions用Collections.synchronizedSet()包装避免多线程并发修改HashSet导致ConcurrentModificationExceptiononMessage中截取session.getId().substring(0,5)作为简易用户标识——大作业无需登录鉴权用Session ID片段足够区分broadcast方法排除发送者自身实现“群聊”语义若需“私聊”在此处加if (targetId.equals(s.getId()))判断即可扩展。编译后.class文件需放在WEB-INF/classes/com/example/chat/ChatEndpoint.class路径下Tomcat启动时自动扫描ServerEndpoint并注册端点。2.3 前端页面用原生JavaScript建立WebSocket连接用DOM操作实时渲染消息index.jsp不写JSP脚本纯HTMLJS降低复杂度确保老师检查源码时一眼看清逻辑!-- src/main/webapp/index.jsp -- % page contentTypetext/html;charsetUTF-8 languagejava % html head titleJava Web聊天系统/title script srchttps://cdn.jsdelivr.net/npm/jquery3.6.0/dist/jquery.min.js/script style #chat-box { height: 400px; overflow-y: scroll; border: 1px solid #ccc; padding: 10px; } .message { margin: 5px 0; } .self { color: #007bff; } .other { color: #28a745; } .system { color: #6c757d; font-style: italic; } /style /head body h2Java Web 聊天室/h2 div idchat-box/div input typetext idmsg-input placeholder输入消息... stylewidth: 70%;/ button onclicksendMessage()发送/button script let ws; const chatBox $(#chat-box); function connect() { // 注意ws://协议端口与Tomcat一致路径匹配ServerEndpoint ws new WebSocket(ws:// window.location.host /chat-system/chat); ws.onopen function () { appendMessage(【系统】连接成功开始聊天, system); }; ws.onmessage function (event) { appendMessage(event.data, other); }; ws.onclose function () { appendMessage(【系统】连接断开请刷新页面重试, system); }; ws.onerror function (error) { console.error(WebSocket error:, error); appendMessage(【系统】网络错误请检查浏览器控制台, system); }; } function appendMessage(text, type) { const cls type self ? self : type other ? other : system; chatBox.append(div classmessage ${cls}${text}/div); chatBox.scrollTop(chatBox[0].scrollHeight); // 滚动到底部 } function sendMessage() { const input $(#msg-input); const msg input.val().trim(); if (msg ws ws.readyState WebSocket.OPEN) { ws.send(msg); appendMessage(【我】 msg, self); input.val(); } } // 回车发送 $(#msg-input).keypress(function (e) { if (e.which 13) sendMessage(); }); // 页面加载即连接 $(document).ready(connect); /script /body /html参数说明window.location.host动态获取当前域名端口如localhost:8080避免硬编码IP导致部署失败ws://协议必须与HTTP协议同域同端口否则浏览器拦截CORS不适用于WebSocketappendMessage中chatBox.scrollTop(...)是血泪经验不加这句新消息来了用户得手动滚屏体验极差input.val()清空输入框后必须调用否则连续发送时内容残留。部署到Tomcat后访问http://localhost:8080/chat-system/打开浏览器开发者工具Console能看到WebSocket connection established日志——这是第一条可验证的成功信号。3. 消息持久化与会话隔离用内存Map模拟用户状态解决刷新丢消息问题3.1 为什么页面刷新后消息全丢——WebSocket Session生命周期与HTTP Session的本质区别WebSocket连接一旦关闭如页面刷新Session对象即被Tomcat销毁sessions集合中对应条目消失所有未持久化的消息永久丢失。这不是Bug是协议设计使然WebSocket Session ≠ HTTP Session。HTTP Session靠Cookie维持可跨请求存在WebSocket Session是TCP连接抽象断连即销毁。因此大作业要拿高分必须实现“页面刷新后历史消息仍在”这需要独立于WebSocket Session的消息存储层。常见误用是把消息存进HttpSession——错HttpSession作用域是HTTP请求WebSocket回调中无法直接获取OnMessage方法参数只有Session和String。正确做法用静态ConcurrentHashMap缓存每个用户的最近20条消息键为Session ID值为ListString// 在ChatEndpoint.java顶部添加 private static final ConcurrentHashMapString, ListString userMessages new ConcurrentHashMap(); OnOpen public void onOpen(Session session) { sessions.add(session); // 初始化该用户消息列表 userMessages.putIfAbsent(session.getId(), Collections.synchronizedList(new ArrayList())); broadcast(【系统】 session.getId().substring(0,5) 已上线, session); } OnMessage public void onMessage(String message, Session session) throws IOException { String formatted 【 session.getId().substring(0,5) 】 message; // 存入用户专属消息列表线程安全 ListString list userMessages.get(session.getId()); if (list ! null) { list.add(formatted); // 限制每用户最多存20条防止内存溢出 if (list.size() 20) list.remove(0); } broadcast(formatted, session); }注意ConcurrentHashMap保证putIfAbsent原子性Collections.synchronizedList保证add/remove线程安全。二者嵌套使用是Java Web大作业中兼顾性能与安全的典型模式。3.2 前端主动拉取历史消息在WebSocket连接建立后立即请求服务端回传仅存消息不够前端需在连接成功后向服务端发起一次HTTP请求获取当前用户的历史记录。为此新增一个HistoryServlet// src/main/java/com/example/chat/HistoryServlet.java package com.example.chat; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.List; WebServlet(/history) public class HistoryServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 从HTTP Session中获取用户标识此处简化用Cookie或URL参数传Session ID String sessionId req.getParameter(sid); if (sessionId null || sessionId.trim().isEmpty()) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, Missing sid parameter); return; } ListString history ChatEndpoint.userMessages.get(sessionId); resp.setContentType(application/json;charsetUTF-8); PrintWriter out resp.getWriter(); out.print({\history\: new Gson().toJson(history null ? new ArrayList() : history) }); } }提示Gson需添加依赖com.google.code.gson:gson:2.10.1但大作业允许用JSONArray替代此处为简洁展示逻辑。实际提交时可用org.json库避免额外jar。前端在ws.onopen后追加AJAX请求ws.onopen function () { appendMessage(【系统】连接成功开始聊天, system); // 主动拉取历史消息 fetch(/chat-system/history?sid getWsSessionId()) .then(r r.json()) .then(data { data.history.forEach(msg appendMessage(msg, other)); }) .catch(e console.warn(Fetch history failed:, e)); };getWsSessionId()需从前端提取WebSocket Session ID——但浏览器JS无法直接读取故改用URL参数透传修改WebSocket连接URL为ws://.../chat?sidxxx后端在OnOpen中解析OnOpen public void onOpen(Session session, EndpointConfig config) { // 从URL参数提取sidTomcat 9.0.83支持 String uri session.getRequestURI().toString(); String sid uri.substring(uri.indexOf(sid) 4); if (!sid.isEmpty()) { session.getUserProperties().put(sid, sid); } // ...其余逻辑 }这样页面刷新时浏览器重新加载index.jspJS生成新WebSocket连接URL并携带当前session.id可通过Math.random().toString(36).substr(2,9)生成临时ID服务端将其与消息列表绑定实现“刷新不丢上下文”。4. 避坑Java Web聊天系统5个必踩的坑与现场急救指南4.1 现象WebSocket连接始终报ERR_CONNECTION_REFUSED控制台无任何日志原因Tomcat未启用WebSocket支持或web.xml中listener配置错误。Tomcat 9默认开启WebSocket但若web.xml里写了servlet标签却没配servlet-mapping容器会拒绝加载任何Servlet相关类包括WebSocket端点。解决确认web.xml只含listener且无其他Servlet配置删除servlet、servlet-mapping等无关标签重启Tomcat后检查logs/catalina.out是否有ServerContainer started字样。4.2 现象两人同时发消息其中一条消失或出现乱序原因broadcast方法未加synchronized多线程遍历sessions集合时触发ConcurrentModificationException异常被静默吞掉部分消息未发出。解决将broadcast方法改为synchronized或改用Iterator安全遍历synchronized (sessions) { IteratorSession it sessions.iterator(); while (it.hasNext()) { Session s it.next(); if (!s.equals(exclude) s.isOpen()) { s.getBasicRemote().sendText(msg); } } }4.3 现象Chrome控制台报WebSocket is closed before the connection is established原因前端WebSocket URL写成http://而非ws://或路径错误如写成/chat-system/ws/chat但后端是ServerEndpoint(/chat)。解决用window.location.origin.replace(http, ws)生成协议路径严格匹配ServerEndpoint值在Chrome Network标签页Filter中输入ws查看WebSocket连接是否出现在列表中。4.4 现象页面刷新后新窗口看不到之前发送的消息原因未实现消息持久化或HistoryServlet返回空数组但前端未处理。解决在HistoryServlet中加日志System.out.println(Get history for sessionId);确认参数传递正确前端fetch后加.catch捕获网络错误并console.log(data)验证JSON结构。4.5 现象Tomcat启动报java.lang.ClassNotFoundException: javax.websocket.server.ServerEndpoint原因JDK版本过高如JDK 17或过低JDK 8而Tomcat 9.0要求JDK 8–17。javax.websocket.*包由Tomcat提供若IDE构建路径包含旧版tomcat-websocket.jar会冲突。解决检查Project Structure → Project → SDK设为JDK 11删除lib目录下所有websocket相关jar确认Tomcat安装目录lib中存在tomcat-websocket.jar大小约200KB。5. 进阶技巧用HTTP Session绑定用户昵称实现“张三”而非“abc123”显示5.1 为什么不用Session ID当昵称——大作业验收时老师会问“用户怎么知道对方是谁”用session.getId().substring(0,5)做昵称虽能跑通但不符合人机交互常识。真实场景中用户首次进入需输入昵称且该昵称要与HTTP Session绑定以便WebSocket回调中能读取。难点在于OnOpen方法无法直接获取HttpSession因为WebSocket不属于HTTP请求生命周期。破局点利用HttpServletRequest的getSession()在HTTP请求阶段预存昵称再通过URL参数传递Session ID服务端用该ID查HttpSession修改index.jsp增加昵称输入框和登录按钮div input typetext idnickname placeholder请输入昵称 stylewidth: 200px;/ button onclicklogin()登录/button /div script function login() { const nick $(#nickname).val().trim(); if (!nick) return; // 创建HTTP Session并存昵称 fetch(/chat-system/login?nick encodeURIComponent(nick), {method: POST}) .then(() { // 登录成功跳转到聊天页带Session ID window.location.href /chat-system/?sid getJSessionId(); }); } /script新增LoginServlet将昵称存入HTTP SessionWebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String nick req.getParameter(nick); if (nick ! null !nick.trim().isEmpty()) { req.getSession().setAttribute(nickname, nick.trim()); } resp.setStatus(HttpServletResponse.SC_OK); } }index.jsp中getJSessionId()从Cookie读取JSESSIONIDfunction getJSessionId() { const cookies document.cookie.split(; ); for (let cookie of cookies) { if (cookie.startsWith(JSESSIONID)) { return cookie.substring(JSESSIONID.length); } } return ; }后端ChatEndpoint中通过session.getUserProperties().put(nickname, nick)在OnOpen时关联昵称OnOpen public void onOpen(Session session, EndpointConfig config) { String sid session.getRequestParameterMap().get(sid).get(0); // 根据sid查HttpSession需自建Session管理器此处简化为内存Map String nick httpSessions.get(sid); // httpSessions是ConcurrentHashMapString, String session.getUserProperties().put(nickname, nick ! null ? nick : 游客); // ...其余逻辑 }最终onMessage中格式化消息变为String nick (String) session.getUserProperties().get(nickname); String formatted 【 nick 】 message;5.2 表格昵称绑定方案对比与选型依据方案实现难度是否需额外存储刷新后昵称是否保留老师验收亮点Session ID截取★☆☆☆☆否否每次刷新ID变无仅能演示连接Cookie存昵称★★☆☆☆否是Cookie持久简单但易被清除HTTP Session绑定★★★★☆否Tomcat内存是Session超时前展示Java Web核心机制理解数据库存储★★★★★是需MySQL是永久超纲大作业不推荐我当年交这个作业时特意在README.md里写了一行“昵称绑定采用HTTP Session机制体现对Java Web会话管理的理解”。老师批注“思路清晰加分”。——技术细节决定分数而不是代码行数。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑