WebSocket聊天室实战:Java后端+JavaScript前端实时通讯源码解析
简介这是一份基于JavaScript、jQuery和Java技术实现的WebSocket聊天室项目压缩包面向正在学习Web实时通信的初中级开发者适合作为课程设计或毕业设计参考。项目通过WebSocket协议在TCP之上建立全双工通道客户端发送Upgrade请求完成握手后即可双向自由通信前端用JavaScript监听用户输入jQuery简化DOM操作与Ajax请求后端用Java配合Jetty或Tomcat维护连接、用户状态和消息定向广播覆盖多人聊天、私人对话和在线客服三类场景。压缩包共23个文件包含Java源码、HTML页面、XML配置、class编译产物以及项目描述文件等整体仅43KB结构紧凑但模块清晰囊括登录界面、聊天主页面、私聊窗口和客服面板。已有116人学习下载可对照代码学习WebSocket握手建立、实时消息推送、并发连接管理、断线重连与异常处理等关键点还能参考登录验证、在线用户列表刷新、私聊定向投递等完整逻辑对提升JavaScript、jQuery和Java综合实战能力很有价值。1. WebSocket 聊天室一份能跑通的 Java JavaScript 实时通讯源码做 Web 开发的人迟早会遇到一个尴尬场景用 HTTP 轮询做在线列表和消息刷新前端每两秒发一次请求服务器被无意义的空请求打满而消息延迟还是让人抓狂。这份 WebSocket 聊天室源码正好解决这个问题它用 Java 做后端 WebSocket 端点前端用 JavaScript 加 jQuery 写交互跑在 Tomcat 7 上实现多人聊天、私人对话和在线客服三类场景。它不是那种只讲原理的空架子而是直接从 Eclipse 工程导入就能跑的实验项目适合正在学 Java Web 或 jQuery 的开发者拿来当练手底子也适合需要快速搭一个内部通讯演示的从业者参考。我拆这份资源时最直接的感受是它的目录结构非常干净src下全是 Java 类WebContent下是页面和 JS 文件没有冗余代码。对刚接触 WebSocket 的人来说从这份源码入手比读协议文档快得多因为你能看到一次完整握手、一条消息广播、一次私聊路由的完整链路。2. 从 HTTP 到 WebSocket为什么聊天室必须换协议2.1 HTTP 轮询的瓶颈与 WebSocket 的升级机制传统聊天室用 Ajax 轮询服务器浏览器每隔几秒发一次请求问有没有新消息。这个模式的浪费在于绝大多数请求都拿不到新数据但每次都要带上完整的 HTTP 头服务器还要做一次无意义的业务处理。当在线用户超过几十人服务器大部分 CPU 都消耗在解析无用请求上。WebSocket 解决的思路是一次握手长期连接。客户端发一个 HTTP Upgrade 请求服务器返回 101 状态码确认切换协议之后这条 TCP 连接上双向自由传帧不再需要重复的请求头。这个Upgrade机制是本项目的核心源码里ServerEndpoint注解标注的类就是处理握手的入口。Tomcat 7 对 WebSocket 的支持已经比较成熟使用 JSR-356 标准 API不需要额外引入复杂依赖这也是这份源码能快速跑起来的原因之一。如果你之前用 Tomcat 8.5 以上版本跑出奇怪问题多半是版本行为差异后面避坑章会单独说。2.2 源码目录结构与服务器配置要点先看工程目录这份资源解压后是个标准 Eclipse 动态 Web 工程WebSocket聊天室/ ├── .settings/ ├── src/ │ ├── ...Java 后端类 ├── WebContent/ │ ├── ...jsp/html/js/css └── build/部署之前要确认三件事。第一Eclipse 里配置的 Server 是 Tomcat v7.0但本地装的 Tomcat 版本最好一致至少是 7.0.x 系列。第二WebContent 目录下如果有WEB-INF/web.xml检查里面是否声明了 Servlet 版本Tomcat 7 对应 Servlet 3.0。第三项目构建路径里 Java 版本要选 1.7 或以上因为 WebSocket API 依赖 Java 7 的某些特性。我一般会在导入工程后先做一次Project Clean然后右键Run As Run on Server选之前配置好的 Tomcat 7。启动后浏览器访问项目名对应的根路径即可。如果启动直接报错优先看是否端口被占用——Tomcat 默认 8080被其他进程占住时启动日志会显示大段异常堆栈。2.3 前端三件套的职责边界这个项目里 JavaScript、jQuery、Java 各管一段。JavaScript 负责 WebSocket 对象创建、事件监听和数据帧收发jQuery 负责 DOM 操作、表单提交和 Ajax 登录验证Java 负责管理 WebSocket 会话、在线列表和消息路由。理解这个边界很重要否则调 bug 时容易找错方向。举例来说页面上新增一条消息气泡这个动作属于 jQuery 的 DOM 操作而收到消息后触发回调函数这个属于 JavaScript 对onmessage事件的处理。两者顺序错了或混淆了会出现消息明明推送到浏览器但页面没反应的诡异现象。后面我会给出完整的前端收发代码。3. 跑通前端收发链路jQuery 事件绑定与 WebSocket 消息处理3.1 登录验证与建立 WebSocket 连接的完整过程登录界面是聊天室的第一道门。用户输入用户名后前端通过 jQuery 发起 Ajax 请求服务端校验用户名是否重名、是否合法校验通过后才允许建立 WebSocket 连接。这段代码是登录成功后初始化连接的标准写法$(function () { $(#loginBtn).on(click, function () { var username $(#username).val().trim(); if (!username) { alert(用户名不能为空); return; } $.ajax({ url: login, type: POST, data: { username: username }, dataType: json, success: function (res) { if (res.success) { initWebSocket(username); } else { alert(res.message); } } }); }); function initWebSocket(username) { var ws new WebSocket(ws:// window.location.host /WebSocketChat/chat?username encodeURIComponent(username)); // 后续事件绑定见下文 } });这段代码里有一个关键参数encodeURIComponent。用户名里如果包含中文或特殊字符比如 、#直接拼进 URL 会乱码或直接连接失败所以必须做一次编码。连接地址的 path 部分/WebSocketChat/chat要和后端ServerEndpoint(/chat)注解里的值完全一致大小写都不能错否则会 404。我这里额外说明登录接口本质是一个普通的 HTTP Servlet 或 Struts Action它不参与 WebSocket 通信。WebSocket 连接建立之后登录验证是否通过其实已经由服务端在onOpen里再次判断了双保险。3.2 消息发送、在线列表刷新与私聊定向连接建立后所有的聊天消息都通过ws.send()发送。项目里常见的做法是把消息封装成一个 JSON 对象包含消息类型、发送者、接收者和内容服务端再根据类型做不同处理。以下是我基于这份资源整理的关键 JS 代码function sendMessage() { var content $(#msgInput).val().trim(); if (!content) { return; } var targetUser currentChatUser; // 私聊时指向某人群里时为 ALL var msg { type: targetUser ALL ? group : private, from: currentUser, to: targetUser, content: content }; ws.send(JSON.stringify(msg)); $(#msgInput).val(); } ws.onmessage function(event) { var data JSON.parse(event.data); if (data.type system) { // 系统消息有人上线/下线刷新在线列表 refreshOnlineList(data.onlineUsers); } else if (data.type group) { appendMessage(data.from, data.content, left); } else if (data.type private) { if (data.from currentUser) { appendMessage(我 [私聊] data.from, data.content, right); } else { appendMessage(data.from [私聊], data.content, left); } } };ws.onmessage是所有实时消息的入口服务端推送什么前端就处理什么。比较值得留意的是系统消息分支在线用户列表不是前端主动刷的而是服务端有用户上线或下线时广播给所有人一份最新的在线名单。这种设计减少了一次 HTTP 请求也避免并发情况下列表不一致。如果发现收不到消息先打开浏览器开发者工具的网络面板查看 WebSocket 帧是否在发送。如果帧在飞但页面没反应大概率是JSON.parse报错了——服务端返回的不是合法 JSON比如混入了日志输出。这时候要在onmessage外层加 try-catch 并打日志排查。3.3 在线客服窗口的实现逻辑在线客服并不是一个独立的复杂系统它本质是普通用户与客服账号之间的私聊。项目里客服角色就是一个固定用户名比如 kefu普通用户发起对话时把目标设为这个用户名消息走私有路由到客服客户端。客服端看到的是所有用户发来的消息按用户维度分组展示。实现时要注意的一点客服窗口和普通聊天窗口共用同一个 WebSocket 端点但前端渲染逻辑不同。客服端需要在消息带上from字段左侧显示用户昵称同时提供快速回复入口。如果你需要把它接入真实的客服工单系统就得在后端增加一个客服消息持久化步骤源码里没有这个逻辑按需扩展即可。4. Java 后端 WebSocket 端点会话管理、消息路由与广播机制4.1 ServerEndpoint 生命周期与在线用户 Map 设计后端核心是一个标注了ServerEndpoint的 Java 类它负责整个 WebSocket 生命周期。先看标准实现的关键骨架ServerEndpoint(/chat) public class ChatEndpoint { private static ConcurrentHashMapString, Session onlineUsers new ConcurrentHashMap(); OnOpen public void onOpen(Session session) { String username session.getRequestParameterMap().get(username).get(0); onlineUsers.put(username, session); // 广播系统消息xxx 上线了 新在线列表 } OnMessage public void onMessage(String message, Session session) { // 解析 JSON判断群聊还是私聊走不同分发逻辑 } OnClose public void onClose(Session session) { String username findUsernameBySession(session); onlineUsers.remove(username); // 广播系统消息xxx 下线了 新在线列表 } OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } }ConcurrentHashMap是这里的关键选型。因为 WebSocket 服务端是多线程环境不同用户的连接由不同线程处理如果用一个普通 HashMap 做在线用户表并发增删时会出现数据不一致甚至死循环HashMap 在并发扩容时是出了名的坑。ConcurrentHashMap是线程安全的实测并发几百连接时没有出现过用户列表错乱。Session对象是每个连接的唯一标识服务端发消息就是调用session.getBasicRemote().sendText(msg)。私聊时要从onlineUsers里取出目标用户的 Session定向推送群聊时遍历所有 Session 逐个推送。这里有一个细节遍历时如果某个用户已经断开但没触发onClose比如手机掉电Session 可能还是 open 状态但实际不可写发送时会抛异常。规范做法是在发送前检查session.isOpen()代码里一般都会加这个判断没有就自己补。4.2 私聊消息路由与消息格式约定服务端收到一条消息后首先要做的是用 JSON 解析库把消息还原成对象。这个项目里消息格式约定如下{ type: private, from: zhangsan, to: lisi, content: 晚上一起吃饭吗 }服务端拿到type private后从onlineUsers中查找to对应的 Session定向推送。同时要注意私聊消息要不要回推一份给发送者如果前端发送后本地直接追加一条气泡就不需要回推如果前端希望消息状态以服务端回执为准就必须回推。资源源码里的做法是前端本地追加服务端只发给接收者这样省一半流量。实际运行时有个容易翻车的点用户 A 私聊用户 B如果 B 不在线onlineUsers里查不到 B 的 Session服务端会直接丢弃消息。合理的做法是返回一条系统消息给 A提示用户 B 不在线。源码里可能没有这层处理你在二次开发时优先补上。4.3 广播风暴控制与消息粘包问题群聊场景下服务端向所有在线用户推送这是最直接的实现。但当在线人数上到几百且有人频繁发言时这种广播会产生消息风暴——每条消息都放大 N 倍发送服务器带宽被打满。源码对这种教学级项目不会做复杂优化但你要知道边界超过 500 人在线就需要引入消息队列或按组广播。另一个常见问题是消息粘包。WebSocket 本身是消息边界清晰的协议一帧就是一条完整消息所以不存在 TCP 粘包问题。但如果你在服务端用了session.getBasicRemote().sendText()在同一线程内连续发送多条消息接收端是能正确分开的这一点比 TCP Socket 编程省心很多。5. 避坑指南部署运行中常见问题与排查记录5.1 页面能打开但 WebSocket 连接失败现象Tomcat 启动正常访问 JSP 页面正常但浏览器 Console 报WebSocket connection to ws://... failed。原因最典型的有三类。一是访问路径写错前端new WebSocket(ws://host:port/项目名/chat)里项目名和实际部署名不一致二是 Tomcat 版本不对比如用 Tomcat 9 跑为 Tomcat 7 编写的 WebSocket 代码某些 API 行为有差异或已废弃三是浏览器安全限制如果你用 HTTPS 访问页面但 WebSocket 地址写的还是ws://浏览器会直接拦截必须改成wss://。解决先确认浏览器 Console 的完整报错在服务端onOpen里加一行日志System.out.println(New connection: session.getId())如果日志没输出说明请求根本没到后端问题在网络层或路径层如果日志有输出但前端仍报失败基本就是 WebSocket 握手响应异常。我处理这类问题时通常先用/echo形式的静态页面测试浏览器对 WebSocket 的基本支持再排查具体路径。5.2 消息发送后页面显示乱码现象中文消息到达后标题和内容显示问号或乱码英文正常。原因服务端向前端推送消息时编码不是 UTF-8。Tomcat 7 里请求和响应的字符编码默认可能是 ISO-8859-1浏览器解析时就会错乱。解决在 WebSocket 端点类顶部加静态代码块System.setProperty(file.encoding, UTF-8)只是治标更有效的是在 JSP 页面顶部加% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %同时在onMessage收到消息后重新用 UTF-8 解析。源码里如果中文正常就别动如果出现乱码优先检查这两个位置。5.3 用户断开后在线列表残留现象某用户直接关掉浏览器标签页但其他用户看到的在线列表里他还在。原因浏览器关闭标签页时 WebSocket 连接未必立刻触发服务端onClose可能延迟几秒甚至几十秒。如果服务端没有心跳检测机制TCP 半开连接会一直占着在线列表。解决这是教学项目的常见缺陷好的方案是服务端定期向所有 Session 发送 Ping 控制帧客户端必须回 Pong连续几次没回就强制关闭该 Session 并移出在线列表。WebSocket 协议本身支持 Ping/Pong 控制帧Tomcat 的 Session API 里提供了session.getBasicRemote().sendPing()方法你需要自己在后端加一个定时任务来做这个检测。这个功能我在最后一章会演示具体写法。5.4 多人同时登录同一用户名导致互踢现象A 和 B 同时用zhangsan登录后登录的会把先登录的踢下线或者两个连接互相覆盖。原因登录校验只查了名字是否已存在但没查该名字对应的连接是否还活着。当 A 的连接还在线B 用同名登录时onlineUsers.put()覆盖了 A 的记录A 的 Session 虽然开着但已经不在 Map 里无法再收到消息。解决一种做法是登录时若发现用户名已存在直接拒绝新登录提示该用户已在线另一种是允许顶号但要主动关闭旧 Session 并向前端推送你的账号在其他地方登录。第二种做法在真实系统中更常见但实现起来需要额外维护一份 Session 与用户名的双向映射。5.5 Tomcat 7 端口被占用或启动失败现象Eclipse 中启动 Tomcat 时控制台报Port 8080 required by Tomcat v7.0 Server at localhost is already in use。原因之前启动的 Tomcat 实例没完全释放或宿主机上其他程序占用了 8080 端口。解决cmd里执行netstat -ano | findstr :8080找到 PID 之后taskkill /PID PID /F杀掉。或者直接修改 Tomcat 的server.xml把端口改成 8081同步修改前端代码里 WebSocket 地址的端口号。6. 给聊天室加上心跳与断线重连验证可靠性的两个必修模块教学版的聊天室能跑但离可用还差一步连接稳定性。真实使用场景里手机切网、路由器重启、电脑休眠再唤醒都会导致 WebSocket 静默断开而且双方往往还不知道。我拿这份源码做二次开发时最先加的就是心跳和断线重连。心跳的核心逻辑是客户端每隔 30 秒发一个 Ping 帧或普通消息帧约定为type: ping服务端收到后回一个 Pong 帧。如果客户端连续 3 次没收到 Pong判定连接已死主动close()并进入重连流程。服务端这边我加了一个定时任务每 60 秒遍历所有 Session发送 Ping记录未回 Pong 的次数超过阈值就session.close()。var heartbeatTimer setInterval(function () { if (ws.readyState WebSocket.OPEN) { ws.send(JSON.stringify({ type: ping, from: currentUser, time: Date.now() })); } }, 30000); var reconnectCount 0; ws.onclose function () { clearInterval(heartbeatTimer); if (reconnectCount 5) { reconnectCount; setTimeout(initWebSocket, 2000 * reconnectCount); } else { alert(连接已断开请刷新页面); } };重连计时我用的是递增等待策略第一次重连等 2 秒第二次等 4 秒最多等 10 秒。这是为了防止服务端还在重启客户端却高频重连把服务器打挂。同时我把reconnectCount设计成只在onclose里递增连接成功后重置为 0——不然用户断一次网后每次刷新页面还得再等 5 次重连才提示失败。验证整个聊天室是否可靠我建议按这个顺序做一遍测试开两个浏览器窗口分别登录两个不同用户互相私聊一条中文消息看是否正常送达然后开着页面拔掉网线或关掉 Wi-Fi 30 秒再重新联网观察是否触发断线重连并恢复收发消息最后用手机浏览器打开页面锁屏 30 秒后解锁看是否会出现消息收到了但页面没刷新如果出现确认服务端 Ping 逻辑是否正常。从那以后我每次拿到一份聊天室类的教学源码都会强制走一遍心跳、重连和并发登录这三项测试——正是因为这份项目让我意识到教学代码能跑通和能在真实场景扛住之间差着一个心跳机制的跨度。希望这份拆解能帮你把这套源码跑起来别被那些看似玄学的 WebSocket 断连和乱码问题劝退。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →