Java WebSocket 使用示例汇总

Java WebSocket 使用示例汇总 Java WebSocket 使用示例汇总本文档提供两个完整的 WebSocket 示例纯 Java 环境使用 Java EE WebSocket API 和 Tyrus 独立服务器Spring Boot 环境使用 STOMP 协议基于消息代理并针对 Spring 配置中的enableSimpleBroker进行深入解释。第一部分纯 Java WebSocket 示例Java EE Tyrus此示例不依赖任何 Web 容器使用 Tyrus 作为独立服务器演示服务端广播和客户端收发消息。环境准备JDK 8Maven 或 GradleMaven 依赖xmldependencies !-- WebSocket API -- dependency groupIdjavax.websocket/groupId artifactIdjavax.websocket-api/artifactId version1.1/version scopeprovided/scope /dependency !-- 客户端 API -- dependency groupIdjavax.websocket/groupId artifactIdjavax.websocket-client-api/artifactId version1.1/version /dependency !-- Tyrus 独立服务器Grizzly 容器 -- dependency groupIdorg.glassfish.tyrus/groupId artifactIdtyrus-container-grizzly-server/artifactId version1.17/version /dependency !-- Tyrus 客户端 -- dependency groupIdorg.glassfish.tyrus/groupId artifactIdtyrus-client/artifactId version1.17/version /dependency /dependencies服务端端点创建一个广播式服务端收到消息后发送给所有连接的客户端。javaimport 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 ChatServer { private static final SetSession sessions Collections.synchronizedSet(new HashSet()); OnOpen public void onOpen(Session session) { sessions.add(session); System.out.println(新连接加入 session.getId() 当前在线 sessions.size()); } OnMessage public void onMessage(String message, Session session) { System.out.println(收到来自 session.getId() 的消息 message); broadcast(用户 session.getId() : message); } OnClose public void onClose(Session session) { sessions.remove(session); System.out.println(连接关闭 session.getId() 当前在线 sessions.size()); } OnError public void onError(Session session, Throwable error) { System.err.println(连接 session.getId() 发生错误 error.getMessage()); } private void broadcast(String message) { for (Session s : sessions) { if (s.isOpen()) { try { s.getBasicRemote().sendText(message); } catch (IOException e) { System.err.println(广播失败 e.getMessage()); } } } } }客户端端点实现ClientEndpoint处理接收到的消息。javaimport javax.websocket.*; ClientEndpoint public class ChatClientEndpoint { OnOpen public void onOpen(Session session) { System.out.println(客户端连接成功Session ID session.getId()); } OnMessage public void onMessage(String message) { System.out.println(客户端收到消息 message); } OnClose public void onClose(Session session, CloseReason reason) { System.out.println(客户端连接关闭原因 reason.getReasonPhrase()); } OnError public void onError(Throwable error) { System.err.println(客户端错误 error.getMessage()); } }启动服务器和客户端创建主类启动服务器并模拟两个客户端通信。javaimport org.glassfish.tyrus.server.Server; import javax.websocket.ContainerProvider; import javax.websocket.Session; import javax.websocket.WebSocketContainer; import java.net.URI; public class WebSocketDemo { public static void main(String[] args) throws Exception { // 1. 启动 Tyrus 服务器 Server server new Server(localhost, 8080, /ws, ChatServer.class); server.start(); System.out.println(WebSocket 服务器启动ws://localhost:8080/ws/chat); // 2. 创建两个客户端并通信 WebSocketContainer container ContainerProvider.getWebSocketContainer(); Session session1 container.connectToServer(ChatClientEndpoint.class, URI.create(ws://localhost:8080/ws/chat)); Session session2 container.connectToServer(ChatClientEndpoint.class, URI.create(ws://localhost:8080/ws/chat)); Thread.sleep(1000); // 等待连接稳定 session1.getBasicRemote().sendText(大家好我是客户端1); Thread.sleep(1000); session2.getBasicRemote().sendText(你好我是客户端2); Thread.sleep(1000); session1.close(); session2.close(); server.stop(); System.out.println(演示结束服务器已关闭。); } }运行说明将代码放入相应包如com.example.websocket。使用 Maven 编译并运行WebSocketDemo.main()。控制台会输出连接、消息广播等日志两个客户端互相收到对方的消息。输出示例textWebSocket 服务器启动ws://localhost:8080/ws/chat 客户端连接成功Session ID0 客户端连接成功Session ID1 新连接加入0当前在线1 新连接加入1当前在线2 收到来自 0 的消息大家好我是客户端1 客户端收到消息用户 0: 大家好我是客户端1 客户端收到消息用户 0: 大家好我是客户端1 收到来自 1 的消息你好我是客户端2 客户端收到消息用户 1: 你好我是客户端2 客户端收到消息用户 1: 你好我是客户端2 ...第二部分Spring Boot 中使用 WebSocketSTOMPSpring 推荐使用 STOMP 协议构建 WebSocket 应用支持高级消息路由和订阅机制。以下示例实现一个简单的聊天室。环境准备JDK 8Spring Boot 2.xMaven 或 Gradle创建项目并添加依赖Maven (pom.xml)xmlparent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.0/version /parent dependencies !-- WebSocket 支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-websocket/artifactId /dependency !-- 用于前端页面测试 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency /dependencies配置 WebSocket 消息代理创建配置类启用 STOMP 消息代理。javaimport org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void configureMessageBroker(MessageBrokerRegistry config) { // 服务端发送给客户端的消息前缀客户端订阅 config.enableSimpleBroker(/topic, /queue); // 客户端发送给服务端的消息前缀 config.setApplicationDestinationPrefixes(/app); } Override public void registerStompEndpoints(StompEndpointRegistry registry) { // 注册 STOMP 端点客户端通过此路径连接 registry.addEndpoint(/chat-websocket) .setAllowedOrigins(*) // 生产环境请谨慎 .withSockJS(); // 启用 SockJS 回退 } }创建消息实体javapublic class ChatMessage { private String from; private String content; public ChatMessage() {} public ChatMessage(String from, String content) { this.from from; this.content content; } // getters and setters 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; } }创建消息处理控制器使用MessageMapping处理客户端消息并广播到指定主题。javaimport org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; Controller public class ChatController { MessageMapping(/chat.sendMessage) // 对应 /app/chat.sendMessage SendTo(/topic/public) // 广播给所有订阅 /topic/public 的客户端 public ChatMessage sendMessage(Payload ChatMessage chatMessage) { // 可添加业务逻辑 return chatMessage; } }创建前端页面测试在src/main/resources/templates/chat.html中编写 HTML 和 JavaScript使用 SockJS 和 STOMP.js 连接。html!DOCTYPE html html head titleWebSocket Chat/title script srchttps://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.1/sockjs.min.js/script script srchttps://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js/script script var stompClient null; function connect() { var socket new SockJS(/chat-websocket); stompClient Stomp.over(socket); stompClient.connect({}, function (frame) { console.log(Connected: frame); stompClient.subscribe(/topic/public, function (message) { var chat JSON.parse(message.body); showMessage(chat.from : chat.content); }); }); } function sendMessage() { var from document.getElementById(from).value; var content document.getElementById(content).value; stompClient.send(/app/chat.sendMessage, {}, JSON.stringify({ from: from, content: content })); } function showMessage(msg) { var messages document.getElementById(messages); var p document.createElement(p); p.appendChild(document.createTextNode(msg)); messages.appendChild(p); } /script /head body div label用户名:/label input idfrom typetext / /div div label消息:/label input idcontent typetext / button onclicksendMessage()发送/button /div button onclickconnect()连接/button div idmessages/div /body /html添加页面控制器返回测试页面。javaimport org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; Controller public class PageController { GetMapping(/) public String chat() { return chat; } }启动应用在application.properties中可选设置server.port8080。运行 Spring Boot 主类带有SpringBootApplication。访问http://localhost:8080点击“连接”然后发送消息所有已连接的客户端都会收到广播。关键点解释EnableWebSocketMessageBroker开启基于消息代理的 WebSocket 支持。configureMessageBroker配置消息代理。enableSimpleBroker(/topic, /queue)启动内存代理处理订阅和广播。setApplicationDestinationPrefixes(/app)客户端发送消息到服务器的前缀。registerStompEndpoints注册 STOMP 端点客户端通过此路径连接。MessageMapping映射客户端发送到特定目的地的消息。SendTo指定方法返回值应广播到的目的地。扩展原生 WebSocket 方式不使用 STOMP如果只需简单双向通信也可使用ServerEndpoint但需要注册ServerEndpointExporter。不过这种方式无法利用 Spring 的依赖注入通常不如 STOMP 方便。javaComponent ServerEndpoint(/ws/chat) public class MyWebSocket { OnOpen public void onOpen(Session session) { ... } OnMessage public void onMessage(String message) { ... } } Configuration public class WebSocketConfig { Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }第三部分关于enableSimpleBroker配置的说明问题在 Spring STOMP 配置中如果没有配置enableSimpleBroker(/topic, /queue)也没有配置enableStompBrokerRelay连接外部代理会发生什么消息还能转发给客户端吗原因enableSimpleBroker的作用是启动一个基于内存的简单消息代理负责维护客户端的订阅关系并将服务端发往匹配前缀的消息分发给所有订阅者。如果不配置任何代理Spring WebSocket 的 STOMP 支持就没有消息代理来处理“发布-订阅”模式。没有配置的后果客户端无法订阅stompClient.subscribe(/topic/public, callback)会失败服务器返回错误因为没有组件处理订阅。服务端的SendTo和SimpMessagingTemplate失效即使MessageMapping方法正常执行并返回数据由于没有代理返回值无法路由给任何客户端。手动注入SimpMessagingTemplate并调用convertAndSend(/topic/public, data)也无法送达消息。但MessageMapping仍可处理请求-响应客户端发送到/app/someEndpoint的消息可以被服务器接收处理但无法通过返回值将响应发回客户端因为没有代理处理目的地。最终客户端收不到响应。替代方案外部消息代理如果不想使用内存代理可以配置enableStompBrokerRelay连接支持 STOMP 的外部代理如 RabbitMQ、ActiveMQ它们同样可以处理订阅和广播。总结没有消息代理配置就无法实现“服务器推送”和“订阅/发布”功能。WebSocket 退化为一个双向通信的管道但无法利用 STOMP 的主题广播机制。若只需点对点通信如向特定用户发送消息可结合SendToUser和用户队列但用户队列底层仍依赖代理/user前缀由代理特殊处理。因此简单内存代理是最轻量的实现方式在大多数基于 STOMP 的应用中必不可少。参考资料Java WebSocket API (JSR 356)Spring WebSocket 文档Tyrus 项目