ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

JavaWeb聊天室实战:Servlet+WebSocket+MySQL全链路解析

JavaWeb聊天室实战:Servlet+WebSocket+MySQL全链路解析 简介这是一份面向Java Web初学者与全栈开发学习者的实战型网络聊天室项目资源聚焦Web实时通信场景帮助开发者系统掌握前后端协同开发全流程。资源包含284个文件以42个jar包含Spring MVC、数据库驱动等核心依赖、14个js文件含jQuery交互逻辑、10个css样式文件、5个xml配置文件及2个jsp页面为主辅以大量图片资源94个gif、93个png和编译后的class文件整体压缩包大小为23.31MB。已有218人下载学习适合用于课程设计、毕业设计或技术栈整合练习。项目完整实现了用户注册登录、好友管理、消息收发、会话状态保持等功能代码结构清晰涵盖Servlet处理、WebSocket通信、MySQL数据持久化、AJAX异步交互及基础安全防护如用户名校验、SQL注入防范可直接部署运行并作为二次开发模板。1. 为什么一个“基于Javaweb的网络聊天室.zip”能卡住90%刚学完Servlet的新手不是它太难而是它太“全”——表面看只是个带登录注册的网页聊天框背后却横跨了HTTP短连接的天然缺陷、Session状态管理的边界、数据库事务一致性、前端长连接的兼容性、以及Tomcat线程模型对并发的真实压力。我见过太多人在UserLoginServlet里写完request.getSession().setAttribute(user, user)就以为登录成功了结果一开两个浏览器窗口消息发着发着就串号也见过有人把WebSocketTest当成普通Servlet配在web.xml里启动报ClassNotFoundException还翻遍百度找不到原因。这个压缩包不是玩具项目它是用最朴素的JSPServletMySQL堆出来的Javaweb能力压力测试仪你能跑通说明你真懂请求生命周期你调通消息实时推送才算摸到Web通信的底层脉搏你让10个用户同时上线不丢消息、不卡顿、不报500才敢说“会做Javaweb项目”。适合刚啃完《Head First Servlet JSP》、正卡在“学了但不会串起来”的人也适合想用最小成本验证自己工程能力的求职者——别急着抄源码先搞清每个.java文件在Tomcat里到底活了几秒、被谁调用、状态存在哪。2. 从解压到可运行三步搭起能登录、注册、发消息的最小闭环这个.zip包不是IDEA直接打开就能跑的“智能项目”它本质是传统Maven结构缺失、依赖全靠手动拷jar包、配置散落在多个XML里的老派Web应用。新手常在这里翻车解压后双击index.jsp看到空白页或者在IDEA里右键Run As → Run on Server却提示“No servlet container configured”。别慌我们按Tomcat原生部署逻辑走绕过所有IDE自动配置陷阱。2.1 解压后目录结构必须满足的硬性约定打开压缩包你会看到类似这样的结构chat-room/ ├── src/ # Java源码注意不是标准maven src/main/java │ ├── servlet/ # 所有Servlet类 │ │ ├── UserLoginServlet.java │ │ ├── UserRegisterServlet.java │ │ └── WebSocketTest.java ← 关键这是WebSocket端点不是普通Servlet │ └── bean/ # 用户实体类 ├── WebContent/ # 等价于标准WebApp根目录不是WebRoot也不是src/main/webapp │ ├── WEB-INF/ │ │ ├── web.xml # 核心配置必须含servlet-mapping和websocket配置 │ │ └── lib/ # 所有jar包servlet-api.jar、mysql-connector-java.jar、javax.websocket-api.jar │ ├── index.jsp │ ├── login.jsp │ ├── chat.jsp │ └── ... └── db/ # SQL建表脚本通常叫chat_db.sql提示如果解压后看到src/main/java或pom.xml说明你拿到的是别人二次改造过的Maven版和原始标题不符。请退回下载源认准纯src/WebContent/结构。2.2 数据库初始化MySQL 8.0必须改的三个坑原始SQL脚本如db/chat_db.sql大概率是为MySQL 5.x写的直接source进8.0会报错。重点改这三处-- 原始脚本常见写法在MySQL 8.0会失败 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL ); -- ✅ 必须改成指定存储引擎显式字符集 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci; -- 原始密码字段可能用MD5但MySQL 8.0默认禁用old_passwords -- ✅ 创建用户时强制用caching_sha2_password插件推荐或切回mysql_native_password ALTER USER your_userlocalhost IDENTIFIED WITH caching_sha2_password BY your_pass;执行完建表插入一条测试数据INSERT INTO users (username, password) VALUES (test, e10adc3949ba59abbe56e057f20f883e); -- MD5(123456)2.3 Tomcat 9部署绕过IDEA自动配置的纯净启动法不要用IDEA的“Add Framework Support”或“Create Artifact”直接物理部署将整个chat-room/WebContent/目录重命名为chat-room去掉WebContent层然后复制到$TOMCAT_HOME/webapps/下确保$TOMCAT_HOME/lib/里有javax.websocket-api.jarTomcat 9自带Tomcat 8需手动放启动Tomcat$TOMCAT_HOME/bin/startup.shLinux/Mac或startup.batWindows访问http://localhost:8080/chat-room/index.jsp参数说明chat-room是Context Path由文件夹名决定index.jsp是欢迎文件在web.xml中声明welcome-file-listwelcome-fileindex.jsp/welcome-file/welcome-file-list。若访问404请检查webapps/chat-room/目录下是否存在WEB-INF/web.xml且路径无拼写错误。3. 登录注册流程拆解为什么UserLoginServlet总跳转失败UserLoginServlet和UserRegisterServlet看着简单但它们暴露了Javaweb最经典的请求-响应-重定向-转发认知盲区。很多人写完response.sendRedirect(chat.jsp)发现跳转后session丢失、用户名为空其实是没吃透sendRedirect和RequestDispatcher.forward的本质区别。3.1 UserLoginServlet一次登录背后的三次状态流转核心逻辑链// UserLoginServlet.java 伪代码 protected void doPost(HttpServletRequest request, HttpServletResponse response) { String username request.getParameter(username); String password request.getParameter(password); // 1️⃣ 查询数据库这里省略DAO但必须用PreparedStatement防SQL注入 User user userDao.findByUsername(username); // 2️⃣ 验证密码原始项目多用明文或MD5生产环境必须BCrypt if (user ! null user.getPassword().equals(md5(password))) { // 3️⃣ 关键将用户存入Session不是request不是application HttpSession session request.getSession(true); // true表示不存在则创建 session.setAttribute(currentUser, user); // 键名必须和JSP里EL表达式一致 // 4️⃣ 重定向到聊天页注意redirect会发起新请求原request对象销毁 response.sendRedirect(request.getContextPath() /chat.jsp); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } }逻辑说明sendRedirect触发浏览器发起全新HTTP GET请求所以原request里的setAttribute失效但session是服务端全局状态只要Session ID通常存cookie没丢chat.jsp里就能通过session.getAttribute(currentUser)取到用户。而forward是在服务端内部跳转request对象复用所以能传error信息给login.jsp。3.2 UserRegisterServlet事务与重复注册的双重校验原始代码常只查一次数据库就插入高并发下必然出现重复用户名。必须加事务和唯一索引// UserRegisterServlet.java 关键片段 protected void doPost(HttpServletRequest request, HttpServletResponse response) { String username request.getParameter(username); String password request.getParameter(password); Connection conn null; PreparedStatement ps null; try { conn dataSource.getConnection(); // 假设已配好JNDI或Druid数据源 conn.setAutoCommit(false); // 开启事务 // 先查重SELECT FOR UPDATE锁住该行防止并发插入 ps conn.prepareStatement(SELECT id FROM users WHERE username ? FOR UPDATE); ps.setString(1, username); ResultSet rs ps.executeQuery(); if (rs.next()) { request.setAttribute(error, 用户名已存在); request.getRequestDispatcher(/register.jsp).forward(request, response); return; } // 再插入MySQL唯一索引会兜底 ps conn.prepareStatement(INSERT INTO users (username, password) VALUES (?, ?)); ps.setString(1, username); ps.setString(2, md5(password)); ps.executeUpdate(); conn.commit(); // 提交事务 response.sendRedirect(request.getContextPath() /login.jsp?registeredtrue); } catch (SQLException e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ignored) {} e.printStackTrace(); request.setAttribute(error, 注册失败请重试); request.getRequestDispatcher(/register.jsp).forward(request, response); } }参数说明FOR UPDATE是InnoDB行锁比单纯SELECTINSERT更可靠dataSource.getConnection()要求web.xml中已配置JNDI资源见下一章md5(password)仅作教学演示实际必须用BCrypt.hashpw(password, BCrypt.gensalt())。4. WebSocketTest为什么你的聊天消息总延迟3秒以上WebSocketTest.java是整个项目的灵魂也是最容易被当普通Servlet误配的模块。它不是HTTP协议不走web.xml里的servlet-mapping而是通过ServerEndpoint注解由Tomcat的WebSocket容器自动扫描加载。如果你在web.xml里给它配了servlet标签启动时就会报java.lang.IllegalArgumentException: Not a WebSocket endpoint。4.1 WebSocket端点正确注册方式Tomcat 8.5WebSocketTest.java必须满足三个条件类上加ServerEndpoint(/ws/chat)注解路径即WebSocket连接URL实现OnOpen、OnMessage、OnClose、OnError四个生命周期方法用静态ConcurrentHashMap存在线用户Session不能用HttpSession// WebSocketTest.java 完整关键片段 ServerEndpoint(/ws/chat) public class WebSocketTest { // ✅ 用ConcurrentHashMap存所有WebSocket Session非HttpSession private static final MapString, Session onlineUsers new ConcurrentHashMap(); OnOpen public void onOpen(Session session, EndpointConfig config) { // 从HTTP Session获取登录用户关键WebSocket本身无Session HttpSession httpSession (HttpSession) session.getUserProperties().get(httpSession); if (httpSession null || httpSession.getAttribute(currentUser) null) { session.close(); // 未登录拒绝连接 return; } User user (User) httpSession.getAttribute(currentUser); onlineUsers.put(user.getUsername(), session); } OnMessage public void onMessage(String message, Session session) { // 解析JSON消息原始项目多用简单字符串拼接此处升级为标准JSON try { JSONObject json new JSONObject(message); String from json.getString(from); String to json.getString(to); // 私聊目标 String content json.getString(content); // 广播给所有人或精准推送给to for (Session s : onlineUsers.values()) { if (s.isOpen()) { s.getBasicRemote().sendText( new JSONObject() .put(from, from) .put(content, content) .put(time, System.currentTimeMillis()) .toString() ); } } } catch (Exception e) { e.printStackTrace(); } } OnClose public void onClose(Session session) { // 从Map中移除断开连接的用户 onlineUsers.values().removeIf(s - s.getId().equals(session.getId())); } }逻辑说明session.getUserProperties().get(httpSession)是Tomcat特有机制需在HTTP请求阶段手动注入——这要求login.jsp提交登录后UserLoginServlet在重定向前把HttpSession对象塞进WebSocket握手请求的属性里见下节。ConcurrentHashMap保证线程安全避免HashMap在多线程put时死循环。4.2 前端JavaScript连接WebSocket的必填参数chat.jsp里不能直接写new WebSocket(ws://localhost:8080/ws/chat)因为路径必须带Context Path/chat-room/ws/chat必须携带Cookie含JSESSIONID才能关联HTTP Session需监听onopen后才允许发消息否则send()报错!-- chat.jsp 中的WebSocket初始化 -- script let ws; const contextPath % request.getContextPath() %; // 动态获取上下文路径 function connectWebSocket() { const wsUrl ws:// window.location.host contextPath /ws/chat; ws new WebSocket(wsUrl); ws.onopen function(event) { console.log(WebSocket连接成功); // ✅ 连接成功后前端才可发送消息 document.getElementById(sendBtn).disabled false; }; ws.onmessage function(event) { const data JSON.parse(event.data); const msgDiv document.createElement(div); msgDiv.innerHTML strong[${data.from}]/strong: ${data.content}; document.getElementById(chatBox).appendChild(msgDiv); document.getElementById(chatBox).scrollTop document.getElementById(chatBox).scrollHeight; }; ws.onerror function(event) { console.error(WebSocket错误:, event); alert(连接异常请刷新页面重试); }; } // 页面加载完成时连接 window.onload function() { connectWebSocket(); }; /script参数说明window.location.host确保跨域时URL正确request.getContextPath()动态适配部署路径避免硬编码/chat-roomonopen回调是唯一安全的发消息时机过早调用ws.send()会触发InvalidStateError。5. 避坑指南五个让新手调试到凌晨三点的真实问题这些不是理论假设而是我在带训时收集的血泪经验——每个都导致至少3人连续2小时卡死在同一个地方。5.1 现象登录后chat.jsp显示nullsession.getAttribute(currentUser)始终为空原因UserLoginServlet用了response.sendRedirect()但chat.jsp试图从request取用户request.getAttribute(currentUser)而重定向后request已销毁。解决chat.jsp必须用session.getAttribute(currentUser)且确认UserLoginServlet中session.setAttribute()的键名与JSP中完全一致大小写敏感。5.2 现象WebSocket连接报404浏览器控制台显示WebSocket connection to ws://... failed原因web.xml里错误配置了servlet映射或WebSocketTest.java未放在src/下被编译Tomcat扫描不到class文件。解决删除web.xml中所有关于WebSocketTest的servlet配置确认WebSocketTest.class存在于webapps/chat-room/WEB-INF/classes/下路径要和包名一致如com/chat/WebSocketTest.class。5.3 现象MySQL插入中文变???日志显示Incorrect string value: \xE4\xBD\xA0\xE5\xA5\xBD原因MySQL服务器、数据库、表、连接URL四层字符集不统一常见于只改了表字符集但没改连接URL。解决在web.xml的JDBC URL末尾强制指定jdbc:mysql://localhost:3306/chat_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai5.4 现象UserRegisterServlet插入重复用户名不报错数据库却出现两条相同记录原因没在MySQL表上建UNIQUE索引仅靠Java层判断高并发时两次查询都返回null然后双双插入。解决执行SQL建唯一索引ALTER TABLE users ADD UNIQUE INDEX idx_username (username);并捕获SQLIntegrityConstraintViolationException做友好提示。5.5 现象Tomcat启动后控制台疯狂刷SEVERE: Error starting static Resources项目无法访问原因WebContent/下存在隐藏文件如.DS_Store、Thumbs.db或WEB-INF/lib/里混入了tomcat-*开头的jar包与Tomcat内置冲突。解决彻底删除WebContent/下所有以.开头的文件检查WEB-INF/lib/只保留mysql-connector-java-x.x.x.jar、javax.websocket-api.jar、servlet-api.jarTomcat 9无需servlet-api.jar删掉。6. 进阶验证用curl和jconsole亲手测出你的聊天室真实并发能力跑通不等于健壮。真正的验收不是“能发消息”而是在100个并发连接下消息不丢失、不乱序、平均延迟200ms。下面这两个命令能让你甩开所有“Hello World”教程进入真实工程验证层。6.1 用curl模拟10个用户批量登录生成有效Session ID别再手动点登录按钮。用curl脚本批量获取Session为压测铺路#!/bin/bash # login_test.sh for i in {1..10}; do usernameuser$i password123456 # 第一步GET登录页获取初始JSESSIONID cookie$(curl -s -I http://localhost:8080/chat-room/login.jsp | grep Set-Cookie | head -n1 | sed s/Set-Cookie: \(.*\);.*/\1/) # 第二步POST登录携带cookie并提取新session response$(curl -s -X POST \ -H Cookie: $cookie \ -d username$username \ -d password$password \ http://localhost:8080/chat-room/UserLoginServlet \ -w \n%{http_code} -o /dev/null) if [ $response 302 ]; then echo ✅ 用户$usernam登录成功 else echo ❌ 用户$usernam登录失败HTTP状态码: $response fi done技巧说明-w \n%{http_code}让curl输出HTTP状态码302表示重定向成功即登录成功grep Set-Cookie提取cookie是关键因为Tomcat每次重定向会生成新JSESSIONID必须捕获它才能用于后续WebSocket连接。6.2 用jconsole实时监控Tomcat线程与内存定位卡顿根源当多人聊天开始卡顿时打开$JAVA_HOME/bin/jconsole连接本地Tomcat进程监控项正常阈值危险信号应对动作Threadspeak 200peak 300 且持续不降检查WebSocketTest.onMessage是否有阻塞IO如同步DB写Heap Memoryused 70%used 90% 且Full GC频繁增加JVM参数-Xms512m -Xmx1024mLoaded Classessteady ~1500持续上涨类泄漏检查onlineUsersMap是否及时清理断开连接实操细节在WebSocketTest.onClose()里加日志System.out.println(用户断开: session.getId());对照jconsole的Thread列表看是否有大量WebSocket线程处于WAITING状态——那说明onClose没被触发onlineUsers没清理最终OOM。6.3 终极验证WebSocket消息时序一致性测试表用以下JSON格式发10条消息验证后端是否严格按接收顺序广播[ {from:user1,to:all,content:msg1,seq:1}, {from:user2,to:all,content:msg2,seq:2}, {from:user1,to:all,content:msg3,seq:3} ]在WebSocketTest.onMessage()里打印接收时间戳System.out.println(收到[ json.getInt(seq) ] System.currentTimeMillis());合格标准10条消息的seq字段在服务端打印顺序必须严格递增且任意两条间隔50ms排除网络抖动。如果出现seq:5先于seq:4打印说明你的ConcurrentHashMap遍历或sendText()调用存在非线程安全操作——立刻检查onlineUsers.values()迭代过程是否加锁。我带过的学员里能跑通登录注册的占70%能调通WebSocket的剩40%但最后用jconsole把线程数压到200以下、消息延迟稳定在150ms内的不到15%。这项目的价值不在代码量而在于它逼你直面Tomcat线程池、JVM内存模型、MySQL锁机制、浏览器Event Loop这四座大山。每次你修复一个NullPointerException其实是在和Java的类加载器握手每次你解决WebSocket断连都是在和TCP的TIME_WAIT状态谈判。希望帮到你。本文还有配套的精品资源点击获取
返回列表