Java实现WebSocket服务端教程
大家好,我们又见面了啊~本文《Java搭建WebSocket服务端教程》的内容中将会涉及到等等。如果你正在学习文章相关知识,欢迎关注我,以后会给大家带来更多文章相关文章,希望我们能一起进步!下面就开始本文的正式内容~
要使用Java构建WebSocket服务端实现实时推送,可通过Spring Boot快速搭建,其核心步骤包括添加依赖、配置WebSocket和实现处理器。1. 在pom.xml中添加spring-boot-starter-websocket依赖;2. 创建配置类WebSocketConfig并启用WebSocket支持,注册处理器并设置允许的来源;3. 实现MyTextWebSocketHandler处理器,继承TextWebSocketHandler并重写连接建立、消息处理、连接关闭等方法,使用CopyOnWriteArraySet管理会话实现广播,或使用ConcurrentHashMap实现定向推送;4. WebSocket相较于HTTP轮询和长轮询具有全双工通信、低开销、高实时性等优势;5. 高并发场景下需解决连接管理、I/O瓶颈、跨实例会话管理、认证授权等问题,可采用异步处理、消息队列、Redis共享会话、JWT令牌等方案。
Java构建WebSocket服务端来实现实时推送,说白了,就是建立一个持久化的双向通信通道,让服务器能够主动、即时地把数据推送到客户端,而不是客户端反复去问“有新消息吗?”。这对于聊天应用、股票行情、实时通知或者任何需要即时数据更新的场景来说,几乎是唯一的选择。它避免了传统HTTP轮询的低效和长轮询的复杂性,提供了一种更优雅、更高效的解决方案。

解决方案
在Java生态中,搭建WebSocket服务端,Spring Boot无疑是当下最便捷、功能最完善的选择之一。我们通常会利用spring-boot-starter-websocket
这个模块来快速构建。
首先,在你的pom.xml
中加入依赖:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
接着,你需要配置WebSocket。创建一个配置类,启用WebSocket支持:
import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new MyTextWebSocketHandler(), "/ws") .setAllowedOrigins("*"); // 允许所有来源,生产环境应限制特定域名 } }
然后,就是实现你的WebSocket处理器。你可以继承TextWebSocketHandler
来处理文本消息,或者BinaryWebSocketHandler
来处理二进制消息。这里以文本消息为例:

import org.springframework.stereotype.Component; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.io.IOException; import java.util.concurrent.CopyOnWriteArraySet; @Component public class MyTextWebSocketHandler extends TextWebSocketHandler { // 用来存放所有在线的WebSocketSession private static final CopyOnWriteArraySet<WebSocketSession> sessions = new CopyOnWriteArraySet<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); System.out.println("新连接建立: " + session.getId() + ", 当前在线人数: " + sessions.size()); session.sendMessage(new TextMessage("欢迎连接到WebSocket服务!")); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { System.out.println("收到来自 " + session.getId() + " 的消息: " + message.getPayload()); // 可以将消息广播给所有客户端 broadcastMessage(message.getPayload()); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { sessions.remove(session); System.out.println("连接关闭: " + session.getId() + ", 状态: " + status.getCode() + ", 原因: " + status.getReason() + ", 当前在线人数: " + sessions.size()); } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { System.err.println("传输错误: " + session.getId() + ", 错误信息: " + exception.getMessage()); sessions.remove(session); } // 广播消息给所有在线客户端 public static void broadcastMessage(String message) { TextMessage textMessage = new TextMessage(message); for (WebSocketSession session : sessions) { try { if (session.isOpen()) { session.sendMessage(textMessage); } } catch (IOException e) { System.err.println("发送消息到 " + session.getId() + " 失败: " + e.getMessage()); // 考虑移除发送失败的session,或者在afterConnectionClosed中统一处理 } } } }
现在,你的Spring Boot应用启动后,客户端就可以通过ws://localhost:8080/ws
(如果你的端口是8080)连接到WebSocket服务了。客户端可以使用JavaScript的WebSocket
API来连接和发送接收消息。
WebSocket与传统HTTP轮询/长连接有何优势?
谈到实时推送,很多人会先想到HTTP轮询或长轮询,但WebSocket出来后,它们就显得有点“老旧”了。WebSocket的优势在于它的全双工通信能力和更低的协议开销。
传统HTTP轮询,说白了就是客户端每隔一段时间(比如1秒)就去问服务器“有新数据吗?”。这种方式简单粗暴,但效率极低,服务器大部分时间都在处理“没有新数据”的请求,浪费带宽和服务器资源。而且,消息的实时性取决于你设置的轮询间隔,间隔短了,资源消耗大;间隔长了,消息就不够“实时”。
长轮询稍微好一点,客户端发起请求后,服务器会“hold住”这个请求,直到有新数据或者超时才响应。客户端收到响应后会立即发起新的请求。这减少了空轮询的次数,但本质上还是基于HTTP请求-响应模型,每次通信都需要完整的HTTP头,开销不小。而且,服务器端维护这些“挂起”的请求也需要一定的资源。
WebSocket则完全不同。它在HTTP握手之后,会升级为一个持久化的TCP连接。一旦连接建立,客户端和服务器可以随时互相发送数据,是真正的双向通信。这意味着服务器可以主动“推送”消息给客户端,无需客户端发起请求。这种模式带来的好处显而易见的:
- 实时性极高:数据几乎是即时到达。
- 效率高:一旦连接建立,后续通信只需要很小的帧头,相比每次都带完整HTTP头,开销大大降低。
- 资源占用少:连接是持久的,避免了频繁的连接建立和关闭,减少了服务器和客户端的资源消耗。
- 编程模型更简洁:对于开发者来说,处理WebSocket消息流通常比管理HTTP请求-响应周期更直观。
当然,WebSocket也并非万能药,它更适合需要高实时性、高频率数据交互的场景。对于那些数据更新不频繁,或者只需要单向获取信息的场景,传统的HTTP请求可能依然是更简单的选择。
Java WebSocket服务端如何实现消息的广播和定向推送?
在构建实时推送系统时,仅仅能够接收消息是不够的,核心在于如何将消息准确地送达给一个或多个客户端。这通常涉及到两种模式:广播(给所有连接的客户端)和定向推送(给特定的客户端)。
实现这两种推送模式,关键在于如何管理和访问这些活跃的WebSocketSession
对象。在上面给出的示例代码中,我们用了一个CopyOnWriteArraySet
来存储所有在线的session。
1. 消息广播:
广播是最直接的方式。当有需要通知所有客户端的事件发生时(比如系统公告、全局更新),你只需要遍历所有当前活跃的WebSocketSession
,然后逐一发送消息。
在MyTextWebSocketHandler
中,broadcastMessage
方法就是实现了这一点:
public static void broadcastMessage(String message) { TextMessage textMessage = new TextMessage(message); for (WebSocketSession session : sessions) { // 遍历所有session try { if (session.isOpen()) { // 确保session仍然是打开状态 session.sendMessage(textMessage); } } catch (IOException e) { // 发送失败通常意味着客户端已断开,但连接状态更新滞后, // 可以在这里记录日志,或者更健壮地处理(比如尝试移除这个session) System.err.println("发送消息到 " + session.getId() + " 失败: " + e.getMessage()); } } }
这里需要注意并发问题。CopyOnWriteArraySet
在读多写少的场景下表现良好,因为它在写入时会复制底层数组,保证了迭代时的线程安全。但如果连接频繁建立和断开,或者在线人数巨大,它的性能可能会受到影响。
2. 定向推送:
定向推送就复杂一些,因为它需要识别“特定客户端”。这通常意味着你需要维护一个“用户ID”到“WebSocketSession”的映射关系。
例如,当用户登录成功并通过WebSocket连接时,你可以将用户的ID与对应的WebSocketSession
关联起来:
import java.util.concurrent.ConcurrentHashMap; import java.util.Map; // ... 其他import @Component public class MyTextWebSocketHandler extends TextWebSocketHandler { // 存储所有在线session,key可以是用户ID,value是WebSocketSession private static final Map<String, WebSocketSession> userSessions = new ConcurrentHashMap<>(); @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { // 假设你可以从session中获取用户ID,例如通过认证信息或者URL参数 String userId = extractUserIdFromSession(session); // 这是一个你需要实现的方法 if (userId != null) { userSessions.put(userId, session); System.out.println("用户 " + userId + " 连接建立: " + session.getId()); } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { // 在连接关闭时移除对应的session userSessions.entrySet().removeIf(entry -> entry.getValue().equals(session)); System.out.println("连接关闭: " + session.getId() + ", 当前在线用户数: " + userSessions.size()); } // 定向发送消息给某个用户 public static void sendMessageToUser(String userId, String message) { WebSocketSession session = userSessions.get(userId); if (session != null && session.isOpen()) { try { session.sendMessage(new TextMessage(message)); System.out.println("向用户 " + userId + " 发送消息: " + message); } catch (IOException e) { System.err.println("发送消息到用户 " + userId + " 失败: " + e.getMessage()); } } else { System.out.println("用户 " + userId + " 不在线或会话已关闭。"); } } // 示例:如何从session中获取用户ID(实际情况可能通过认证令牌等) private String extractUserIdFromSession(WebSocketSession session) { // 实际应用中,这部分会涉及安全认证,例如从Spring Security的Principal中获取 // 或者从WebSocket握手时的HTTP头、URL参数中解析 // 简单示例:假设客户端连接时URL是 /ws?userId=xxx String query = session.getUri() != null ? session.getUri().getQuery() : null; if (query != null && query.contains("userId=")) { return query.split("userId=")[1].split("&")[0]; } return null; } }
在实际应用中,extractUserIdFromSession
这部分会非常关键,它通常涉及到与你的用户认证系统集成。你可能需要从WebSocket握手请求的HTTP头中获取JWT令牌,然后解析出用户ID。
定向推送的挑战在于,如果一个用户在不同设备或浏览器上建立了多个WebSocket连接,你可能需要一个Map
来存储一个用户ID对应的所有session,以便将消息推送到该用户的所有在线客户端。
WebSocket服务端在高并发场景下可能遇到哪些挑战及解决方案?
当你的WebSocket服务需要支撑成千上万甚至百万级的并发连接时,一些潜在的挑战就会浮出水面,这需要我们在系统设计时就有所考量。
1. 连接管理与内存消耗:
每个WebSocket连接都会占用服务器一定的内存资源,包括会话对象、缓冲区等。在高并发下,这些累积的内存开销可能非常巨大。
- 挑战:内存溢出(OOM)、GC暂停时间过长导致服务响应变慢甚至卡顿。
- 解决方案:
- 优化Session存储:避免在Session对象中存储过多的业务数据。
- 使用高效的并发集合:如
ConcurrentHashMap
,而不是同步锁或CopyOnWriteArraySet
(在高写入场景下效率不高)。 - JVM参数调优:合理配置堆内存大小、GC策略。
- 连接池/资源回收:确保连接断开时资源能被及时释放。
2. 消息处理与I/O瓶颈:
大量的消息发送和接收会产生大量的I/O操作。如果处理不当,可能导致线程阻塞,影响整体吞吐量。
- 挑战:线程池耗尽、消息积压、发送延迟。
- 解决方案:
- 异步消息处理:将消息的业务逻辑处理从WebSocket I/O线程中剥离,放到独立的线程池或消息队列中处理。例如,接收到消息后,将其放入一个内部队列,由消费者线程异步处理,避免阻塞WebSocket连接的读写。
- NIO/AIO框架:Spring WebSocket底层通常会使用Netty或Tomcat的NIO实现,它们本身就是非阻塞的。但业务逻辑层仍需注意避免阻塞。
- 限流与熔断:防止恶意或过载的客户端导致服务器崩溃。
3. 跨服务器实例的会话管理(集群部署):
当你的WebSocket服务需要进行水平扩展,部署多个实例时,如何确保消息能够推送到正确的客户端,无论它连接到哪个服务器实例,是一个核心问题。
- 挑战:单个服务器实例无法感知其他实例上的连接,导致定向推送失败或广播不完整。
- 解决方案:
- 共享会话存储:使用外部的、可共享的存储来管理所有活跃的WebSocket会话信息,例如Redis。当一个实例需要向某个用户发送消息时,它首先查询Redis,找到该用户连接所在的实例IP和端口,然后通过内部RPC或消息队列将消息转发给目标实例。
- 消息中间件:引入消息队列(如Kafka、RabbitMQ)作为消息总线。所有业务服务将需要推送的消息发送到消息队列的特定Topic。所有WebSocket服务器实例都订阅这个Topic,当它们收到消息时,判断消息是否需要发送给当前实例上的连接,如果是,则进行推送。这是一种非常常见的、可伸缩的实时推送系统架构。
- Sticky Session (粘性会话):通过负载均衡器(如Nginx、HAProxy)的配置,确保同一个客户端的WebSocket连接总是路由到同一个服务器实例。这虽然简化了服务器端会话管理,但降低了负载均衡的灵活性,并且如果某个实例宕机,其上的所有连接都会断开,需要客户端重连。通常不推荐作为高可用方案。
4. 认证与授权:
在高并发下,每次连接的认证和后续消息的授权都需要高效执行。
- 挑战:认证过程耗时、授权逻辑复杂。
- 解决方案:
- JWT令牌:在WebSocket握手阶段,通过HTTP头传递JWT令牌进行认证。服务器解析令牌,获取用户身份信息,并将其关联到
WebSocketSession
。后续消息处理时,直接使用会话中已认证的身份,避免重复认证。 - 与现有安全框架集成:例如Spring Security可以与Spring WebSocket很好地集成,利用其认证授权机制。
- JWT令牌:在WebSocket握手阶段,通过HTTP头传递JWT令牌进行认证。服务器解析令牌,获取用户身份信息,并将其关联到
总的来说,构建高并发的Java WebSocket服务,不仅仅是写几行代码那么简单,它更像是一个系统工程,需要你对网络通信、并发编程、分布式系统有深入的理解,并结合实际业务场景进行权衡和设计。
终于介绍完啦!小伙伴们,这篇关于《Java实现WebSocket服务端教程》的介绍应该让你收获多多了吧!欢迎大家收藏或分享给更多需要学习的朋友吧~golang学习网公众号也会发布文章相关知识,快来关注吧!

- 上一篇
- Linux系统安全加固方法详解

- 下一篇
- CSS空状态处理::empty实用技巧分享
-
- 文章 · java教程 | 3小时前 |
- MapStruct嵌套列表映射技巧解析
- 214浏览 收藏
-
- 文章 · java教程 | 3小时前 |
- SpringCloud微服务实战,Java架构设计详解
- 307浏览 收藏
-
- 文章 · java教程 | 4小时前 |
- Gson高级用法:单对象与数组动态映射技巧
- 311浏览 收藏
-
- 文章 · java教程 | 4小时前 |
- SpringBoot整合ActiveMQ配置详解
- 235浏览 收藏
-
- 文章 · java教程 | 4小时前 | 使用场景 并发 线程同步 lock synchronized
- Lock接口与synchronized区别详解
- 391浏览 收藏
-
- 文章 · java教程 | 4小时前 |
- SpringBoot部署Tomcat详细步骤教程
- 188浏览 收藏
-
- 文章 · java教程 | 4小时前 |
- MyBatis拦截器原理与插件开发详解
- 192浏览 收藏
-
- 文章 · java教程 | 4小时前 |
- 泛型类内部类参数覆盖问题解决方法
- 153浏览 收藏
-
- 文章 · java教程 | 5小时前 |
- Java类与对象区别与联系详解
- 333浏览 收藏
-
- 文章 · java教程 | 5小时前 |
- Java灰度发布实现与版本控制方法
- 406浏览 收藏
-
- 文章 · java教程 | 5小时前 |
- Nginx负载均衡配置与优化教程
- 449浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 511次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 498次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 484次学习
-
- TextIn智能文字识别平台
- TextIn智能文字识别平台,提供OCR、文档解析及NLP技术,实现文档采集、分类、信息抽取及智能审核全流程自动化。降低90%人工审核成本,提升企业效率。
- 8次使用
-
- 简篇AI排版
- SEO 简篇 AI 排版,一款强大的 AI 图文排版工具,3 秒生成专业文章。智能排版、AI 对话优化,支持工作汇报、家校通知等数百场景。会员畅享海量素材、专属客服,多格式导出,一键分享。
- 8次使用
-
- 小墨鹰AI快排
- SEO 小墨鹰 AI 快排,新媒体运营必备!30 秒自动完成公众号图文排版,更有 AI 写作助手、图片去水印等功能。海量素材模板,一键秒刷,提升运营效率!
- 9次使用
-
- Aifooler
- AI Fooler是一款免费在线AI音频处理工具,无需注册安装,即可快速实现人声分离、伴奏提取。适用于音乐编辑、视频制作、练唱素材等场景,提升音频创作效率。
- 9次使用
-
- 易我人声分离
- 告别传统音频处理的繁琐!易我人声分离,基于深度学习的AI工具,轻松分离人声和背景音乐,支持在线使用,无需安装,简单三步,高效便捷。
- 9次使用
-
- 提升Java功能开发效率的有力工具:微服务架构
- 2023-10-06 501浏览
-
- 掌握Java海康SDK二次开发的必备技巧
- 2023-10-01 501浏览
-
- 如何使用java实现桶排序算法
- 2023-10-03 501浏览
-
- Java开发实战经验:如何优化开发逻辑
- 2023-10-31 501浏览
-
- 如何使用Java中的Math.max()方法比较两个数的大小?
- 2023-11-18 501浏览