Java 7老后台加WebSocket群聊:JSR-356与ExtJS实践

发布时间:2026/10/23 17:20:17

Java 7老后台加WebSocket群聊:JSR-356与ExtJS实践 简介基于Tomcat8、Java7与ExtJS构建的WebSocket聊天室完整工程源码面向具备一定Java Web基础、希望打通实时通信链路的开发者与课程设计学习者。工程以JSR 356标准的javax.websocket端点承载服务端连接管理配合ExtJS组件搭建聊天窗口、输入框与消息列表可直接部署在Tomcat8容器中运行用于理解握手、消息广播与在线状态维护等环节。压缩包共1753个文件、约7.01MB含3个java源文件、6个class与11个jar依赖另有大体量gif与png图像、scss与css样式、50个js脚本构成界面皮肤与前端交互层xml、jsp及Eclipse工程配置文件保证目录可直接导入。已有263人学习。读者可据此复现一个可运行的实时聊天原型对照服务端生命周期回调与客户端封装代码梳理消息推送流程并扩展登录校验、历史记录与并发连接处理。1. 内网客服台要加群聊老后台只能上 WebSocket内网客服台要加一个群聊窗口前端是十几年前搭起来的 ExtJS 后台后端只能是 Java 7容器锁定 Tomcat 8。我拿到这份源码时先把它当成一个可拆的工具而不是完整产品服务端用 JSR-356 的ServerEndpoint接连接前端用 ExtJS 包一层 WebSocket消息走 JSON 文本。它适合维护老后台、要给内部系统加即时通知、又不想引入新中间件的开发者。下面按能跑起来、能改动的顺序拆。2. 服务端握手与会话管理ServerEndpoint背后的连接生命周期Tomcat 8 自带 WebSocket 实现Java 7 也能跑但工程边界要先定死一个 Maven WARmaven.compiler.source/target都是1.7javax.websocket-api只放在编译期作用域provided。这样 WAR 里不会把容器的 WebSocket 实现打进去部署时少一类类加载冲突。目录我一般这样摆chatroom/ pom.xml src/main/java/demo/chat/ endpoint/ChatEndpoint.java endpoint/OriginConfigurator.java model/ChatMessage.java util/JsonUtil.java src/main/webapp/ WEB-INF/web.xml index.html app.js app/view/ChatPanel.js app/ux/Socket.jsendpoint只放连接入口model放协议对象util放 Gson 单例前端按 ExtJS 的view、ux分开。不要把广播、在线表、JSON 解析全塞进一个类后面加房间和私聊会很难受。2.1 依赖与编译级别Java 7 下别写 lambdapom.xml里最关键的是两个provided和一个 Java 7 兼容的 JSON 库。WebSocket API 由 Tomcat 8 提供Servlet API 同理Gson 用你本地仓库里能跑在 Java 7 上的版本不要随手拿需要 Java 8 的新版。project xmlnshttp://maven.apache.org/POM/4.0.0 modelVersion4.0.0/modelVersion groupIddemo.chat/groupId artifactIdchatroom/artifactId packagingwar/packaging properties maven.compiler.source1.7/maven.compiler.source maven.compiler.target1.7/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding websocket.api.version1.1/websocket.api.version gson.version${gson.java7.version}/gson.version /properties dependencies dependency groupIdjavax.websocket/groupId artifactIdjavax.websocket-api/artifactId version${websocket.api.version}/version scopeprovided/scope /dependency dependency groupIdcom.google.code.gson/groupId artifactIdgson/artifactId version${gson.version}/version /dependency /dependencies build finalNamechatroom/finalName plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-war-plugin/artifactId configuration failOnMissingWebXmlfalse/failOnMissingWebXml /configuration /plugin /plugins /build /projectwebsocket.api.version对应 JSR-356 APITomcat 8 容器里有实现所以scope必须是providedgson.version换成你仓库里 Java 7 能加载的版本别把新语法带进来。finalName决定 WAR 名后面上下文路径就是/chatroom。Java 7 没有 lambdaonMessage的回调、SendHandler都老老实实写匿名类别在 IDE 里自动补成() - {}否则编译级别一卡就是一片红。2.2onOpen里做三件事拿用户、登记、发系统消息端点路径写成/chat/{user}用户从 URL 路径里取前端用encodeURIComponent处理昵称。ServerEndpoint的configurator指向自定义握手拦截器后面 2.4 再补。ServerEndpoint(value /chat/{user}, configurator OriginConfigurator.class) public class ChatEndpoint { private static final ConcurrentHashMapString, Session ONLINE new ConcurrentHashMapString, Session(); OnOpen public void onOpen(Session session, PathParam(user) String user) throws IOException { String name user null || user.trim().length() 0 ? guest : user.trim(); session.getUserProperties().put(user, name); session.getUserProperties().put(lastActive, System.currentTimeMillis()); session.setMaxTextMessageBufferSize(8 * 1024); Session old ONLINE.put(name, session); if (old ! null old.isOpen()) { old.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, duplicate login)); } ChatMessage msg ChatMessage.system(name 进入聊天室, ONLINE.size()); broadcast(msg); } OnClose public void onClose(Session session) { String name (String) session.getUserProperties().get(user); if (name null) { return; } ONLINE.remove(name, session); broadcast(ChatMessage.system(name 离开聊天室, ONLINE.size())); } // onMessage、broadcast 在下一章补全 }ServerEndpoint的value是相对上下文路径的端点地址部署成chatroom.war后完整地址是ws://host:8080/chatroom/chat/张三。PathParam把路径里的{user}注入方法参数getUserProperties()是每个连接自己的小仓库用来存昵称和最后活跃时间不要用静态变量存“当前用户”。setMaxTextMessageBufferSize限制单条文本大小聊天室给 8KB 足够给太大容易被一条长消息拖住。ONLINE.put会返回同名的旧连接重复登录时把旧连接关掉避免同一个昵称有两个窗口都在线。2.3 在线表为什么用ConcurrentHashMap在线表就是MapString, Session但别用HashMap也别用Collections.synchronizedMap然后在广播时手动synchronized整张表。WebSocket 的onOpen、onClose、onMessage可能来自不同线程广播时一边遍历一边有人下线普通 Map 会直接抛ConcurrentModificationException。ConcurrentHashMap的迭代是弱一致的广播时看到的是某一刻的快照不会因为别人下线就崩。另一个血泪经验是下线删除要写成remove(key, value)不要只写remove(key)。假设用户 A 断线重连新连接已经放进去了旧连接的onClose才姗姗来迟如果直接按昵称删会把新连接一起删掉表现就是“人明明在线消息发不过去”。ONLINE.remove(name, session)只有在值还是旧Session时才删能避开这个回马枪。广播时如果想拿稳定快照可以new ArrayListSession(ONLINE.values())再遍历但单房间聊天室直接用弱一致迭代也够。2.4 握手拦截别让任意页面都能连OriginConfigurator只做一件事检查Origin头。内网也会有人把页面挂到别的域下或者用工具直接连能拦一层是一层。public class OriginConfigurator extends ServerEndpointConfig.Configurator { private static final String ALLOWED http://localhost:8080; Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { ListString origins request.getHeaders().get(Origin); if (origins null || origins.isEmpty() || !ALLOWED.equals(origins.get(0))) { throw new RuntimeException(Origin not allowed); } sec.getUserProperties().put(origin, origins.get(0)); } }modifyHandshake在握手阶段执行拿不到Session只能改ServerEndpointConfig的userProperties。Origin头可能没有比如某些非浏览器客户端内网如果确实有这种调用方就加白名单而不是直接放行。反向代理后面Origin可能变成代理地址部署到那一步时把ALLOWED改成实际访问域名别写死localhost之后又奇怪为什么生产连不上。3. 消息协议与广播链路一条聊天消息从 ExtJS 到所有在线窗口服务端连接通了只是第一步真正决定后面好不好改的是消息协议。聊天室不需要一上来就上 Protobuf文本 JSON 足够type区分加入、离开、聊天、心跳from是发送者content是正文time给界面显示online给在线人数。字段少排查时一眼能看懂。3.1ChatMessage与 Gson 单例public class ChatMessage { private String type; // JOIN / LEAVE / SYSTEM / CHAT / PING / PONG private String from; private String content; private String time; private int online; public static ChatMessage system(String content, int online) { ChatMessage m new ChatMessage(); m.setType(SYSTEM); m.setFrom(system); m.setContent(content); m.setTime(new SimpleDateFormat(HH:mm:ss).format(new Date())); m.setOnline(online); return m; } public String getType() { return type; } public void setType(String type) { this.type type; } public String getFrom() { return from; } public void setFrom(String from) { this.from from; } public String getContent() { return content; } public void setContent(String content) { this.content content; } public String getTime() { return time; } public void setTime(String time) { this.time time; } public int getOnline() { return online; } public void setOnline(int online) { this.online online; } }type用字符串而不是枚举前端加新类型时不用同步改枚举序列化代价是服务端要自己兜底未知类型。time用HH:mm:ss就够了聊天室不靠时间排序靠服务端到达顺序。SimpleDateFormat不是线程安全的别写成静态共享实例每次new一个量级不大省心。Gson 单例放在JsonUtil里public final class JsonUtil { public static final Gson GSON new Gson(); private JsonUtil() {} }Gson本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/23 17:20:17

Web打印控件Lodop:Java后端对接与打印模板设计避坑指南

简介:针对Java环境下的Web打印需求,这份Lodop打印模板与安装包提供了可直接落地的完整资源,尤其适合需要在网页端或桌面应用中实现多页打印、自定义模板、条形码/二维码生成以及图文混排等复杂打印功能的Java开发人员。压缩包共含58个文件&am…

2026/10/23 17:20:17

SIP REGISTER注册失败排查:Via头、重传机制与eXosip状态机解析

简介:本资源是一份面向VoIP开发初学者与嵌入式通信工程师的SIP注册实战代码包,聚焦eXosip2-3.6.0库在MD5加密验证场景下的REGISTER流程实现。资源完整呈现客户端(SipClient)与服务器(SipServer)双端C工程&a…

2026/10/23 19:06:05

零基础入门《太吾绘卷》Mod开发:从改数值到写事件

1. 从玩家到作者:为什么值得动手写一个Mod很多人第一次接触《太吾绘卷》的Mod开发,都是被某个具体需求逼出来的。比如觉得某个门派的功法成长曲线太慢,或者想让某个NPC的互动逻辑更符合自己的想象,又或者单纯想让界面显示的信息更…

2026/10/23 19:06:05

MATLAB车牌识别实现:从HSV颜色空间定位到模板匹配识别

简介:基于 MATLAB 的车牌识别系统源码包,面向高校图像处理与模式识别课程设计、毕业设计场景,实现从车牌图像到车牌号码的自动识别。资源覆盖预处理、模板匹配、字符分割、数字与汉字识别等完整流程,提供多个可直接运行的 .m 文件…

2026/10/23 19:06:05

Linux用户与组群管理:从命令行基础到可审计治理体系

1. 项目概述:为什么“用户管理器”不是可有可无的摆设,而是系统稳定运行的基石“安装用户管理器管理用户和组群”——这行看似平淡的指令,背后藏着操作系统最基础、也最容易被轻视的一道安全防线。我接触过太多案例:某公司测试服务…

2026/10/23 19:06:05

热力学核心概念梳理:从熵、自由能到配分函数的杂乱笔记

老实说,每次提到热力学,我脑子里先蹦出来的都不是某一条定律,而是自己那本用黑色签字笔写得密密麻麻、后来又被各色荧光笔标记得乱七八糟的笔记本。那本笔记,我管它叫“热力学杂乱笔记”。它跟任何一本教材都不一样,教…

2026/10/23 19:06:05

Excel截图jar实战:xlsx转PNG、参数调优与批量导出避坑

简介:Excel截图jar.zip 面向需要在 Java 项目中实现 Excel 工作表截图、图表导出与去水印效果的开发者,适合办公自动化、报表生成及数据可视化等场景,技能层级偏向具备一定 Java 与 Maven 基础的读者。压缩包共 5 个文件,包含 2 个…

2026/10/23 19:01:01

信息系统项目管理师32小时通关:核心概念与备考指南

简介:本资源为信息系统项目管理师(软考高级)32小时通关备考PDF,面向计划参加全国计算机技术与软件专业技术资格(水平)考试的考生,尤其适合需要短期系统梳理考点、快速建立知识框架的应试者。内容…

2026/10/22 11:29:28

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/23 0:02:26

Verilog实现FPGA密码锁:矩阵键盘与状态机设计实战

说实话,这几年接触了不少想入门FPGA的朋友,如果你问哪个项目能一口气把Verilog的核心基本功串起来,我通常会推荐电子密码锁。这个项目看着不起眼,却把矩阵键盘扫描、按键消抖、状态机设计、动态显示刷新、外设控制这些数字逻辑里的…

2026/10/23 0:02:26

数据服务监控运维实战:从指标体系到告警治理与故障排查

周末凌晨两点,手机震了。第一反应是“数据服务挂了”。打开电脑看了下,果然,某张核心报表的数据产出时间已经超过SLA两小时,下游三个业务方连着发来问询。这种场景,做过大数据运维的人都不会陌生。数据服务的监控与运维…

2026/10/23 0:02:26

FreeRTOS任务通知实战详解:内核原理、API与避坑指南

裸机编程切到RTOS之后,任务之间的通信和同步永远是躲不开的坎。前面聊过队列、信号量、互斥锁、事件组,这次把FreeRTOS任务通知(Task Notification)单独拎出来讲透。原因很简单:任务通知是FreeRTOS里最容易用错、也最容…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑