编辑
2024-07-22
实用工具
00
请注意,本文编写于 254 天前,最后修改于 184 天前,其中某些信息可能已经过时。

目录

介绍
优缺点
使用WebSocketConfigurer接口
使用@ServerEndpoint注解和ServerEndpointExporter
实现
引入spring-websocket依赖
1.ServerEndpoint注解和ServerEndpointExporter
2.使用WebSocketConfigurer接口
注意

介绍

WebSocketServer的目的是为了实时推送数据到客户端,才使用webSocket的双全工的方式

spring简单实现有两种方式

  1. WebSocketConfigurer接口
  2. 通过@ServerEndpoint注解配合ServerEndpointExporter bean来配置WebSocket端点

优缺点

在Spring框架中配置WebSocket服务器时,您可以选择使用WebSocketConfigurer接口或者通过@ServerEndpoint注解配合ServerEndpointExporter bean来配置WebSocket端点。这两种方式各有优缺点,适用于不同的场景。

使用WebSocketConfigurer接口

优点:

  • 集成度更高: 与Spring框架的集成更加紧密,可以充分利用Spring的依赖注入、AOP等特性。
  • 配置灵活: 可以通过WebSocketHandlerRegistry注册多个处理器,并且可以配置拦截器、异常处理器等。
  • 支持Spring Security: 可以更容易地与Spring Security集成,实现WebSocket层的安全控制。

缺点:

学习曲线: 需要熟悉Spring WebSocket的配置和使用方式。

使用@ServerEndpoint注解和ServerEndpointExporter

优点:

  • 简单直接: 配置相对简单,适合快速开发和小型项目。
  • 标准支持: @ServerEndpoint是Java EE WebSocket API的一部分,因此具有更好的跨平台兼容性(尽管在Spring环境中使用时,通常还是会依赖Spring的一些特性)。

缺点:

  • 集成度较低: 与Spring框架的集成不如WebSocketConfigurer紧密,依赖注入等特性需要额外配置或使用其他手段实现。
  • 配置不够灵活: 相比WebSocketConfigurer,配置选项较少,例如不能方便地添加拦截器或异常处理器。
  • 安全性: 需要手动配置WebSocket层的安全控制,与Spring Security的集成不如WebSocketConfigurer方便。
  • 结论
  • 对于大多数Spring项目来说,使用WebSocketConfigurer接口是更好的选择。它提供了更高的集成度和更灵活的配置选项,可以充分利用Spring框架的优势。同时,随着Spring框架的不断发展和完善,WebSocketConfigurer接口也可能会得到更多的功能和优化。

然而,如果您的项目比较简单,或者您更熟悉Java EE WebSocket API的标准实现方式,并且不需要与Spring框架进行深度集成,那么使用@ServerEndpoint注解和ServerEndpointExporter也是一种可行的选择。

实现

引入spring-websocket依赖

springBoot项目需要引入spring的webSocket依赖

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>

1.ServerEndpoint注解和ServerEndpointExporter

配置类

java
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); } }

webSocket服务

java
import cn.hutool.core.collection.CollUtil; import com.alibaba.fastjson.JSON; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.concurrent.CopyOnWriteArrayList; /** * webSocket服务器 */ @ServerEndpoint("/websocket/rob") @Component @Slf4j public class WsRobServer { /** * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 */ private static int onlineCount = 0; /** * concurrent包的线程安全集合,也可以map改成set,用来存放每个客户端对应的MyWebSocket对象。 */ private static CopyOnWriteArrayList<WsRobServer> webSocketList = new CopyOnWriteArrayList<>(); /** * 与某个客户端的连接会话,需要通过它来给客户端发送数据 */ private Session session; /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(Session session) { this.session = session; if (!webSocketList.contains(session)) { //加入集合中 webSocketList.add(this); //在线数加1 WsRobServer.onlineCount++; } log.info("当前连接人数为:" + onlineCount); try { //socket客户端链接到服务器就立即发送一次机器人信息 sendMessage(JSON.toJSONString(WsRobClient.wsRobInfos)); } catch (IOException e) { log.error("网络异常!!!!!!"); } } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { if (webSocketList.contains(this)) { webSocketList.remove(this); //从集合中删除 WsRobServer.onlineCount--; } } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息 */ @OnMessage public void onMessage(String message, Session session) throws IOException { log.info("收到客户端发来的消息:" + message); //通过消息类型返回对应的消息 } /** * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.error("链接关闭,原因:" + error.getMessage()); error.printStackTrace(); } /** * 实现服务器主动推送 */ public void sendMessage(String message) throws IOException { this.session.getBasicRemote().sendText(message); } /** * 群发消息 */ public static void sendsInfo(String message) { log.info("开始群发消息!"); if (CollUtil.isEmpty(webSocketList)) { log.error("没有用户在线,不需要发送!"); } else { for (WsRobServer server : webSocketList) { if (server != null) { try { server.sendMessage(message); // 调用 WebSocket 的 sendMessage 方法发送消息 } catch (IOException e) { log.error("消息发送失败!"); e.printStackTrace(); } } } } } }

2.使用WebSocketConfigurer接口

端点配置WebSocketConfig

java
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) { // 注册一个 /notification 端点 // registry.addHandler(new MyWebSocketHandler(), "/notification") // .setAllowedOrigins("*"); // 注册另一个 /chat 端点 registry.addHandler(new MyWebSocketHandler(), "/chat") .setAllowedOrigins("*"); // 注册第三个 /updates 端点 registry.addHandler(new MyWebSocketHandler(), "/updates") .setAllowedOrigins("*"); } }

MyWebSocketHandler编写

java
import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; public class MyWebSocketHandler extends TextWebSocketHandler { private static final Logger logger = LoggerFactory.getLogger(StairLiftSocketHandler.class); private static CopyOnWriteArraySet<WebSocketSession> sessions = new CopyOnWriteArraySet<>(); /** * 在WebSocket连接建立后调用 * 该方法用于处理连接建立后的操作,主要是将当前会话添加到会话列表中,并记录日志 * * @param session 当前的WebSocket会话 * @throws Exception 如果处理过程中发生异常,则抛出异常 */ @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { sessions.add(session); logger.info("新的用户加入,session id: {} ,当前用户数:{}", session.getId(),sessions.size()); } /** * 发送消息给所有活动的WebSocket会话 * * @param msg 要发送的消息内容 */ public static void sendMsg(String msg) { // 如果sessions集合不为空 if (!sessions.isEmpty()) { // 筛选并遍历所有打开状态的会话 sessions.stream() .filter(WebSocketSession::isOpen) .forEach(session -> { try { // 尝试发送消息 session.sendMessage(new TextMessage(msg)); } catch (IOException e) { // 捕获并记录发送消息时可能发生的IO异常 logger.error("发送消息失败,session Id: {}, 异常信息: {}", session.getId(), e.getMessage(), e); } }); } } /** * 处理文本消息 * 当通过WebSocket接收到文本消息时,此方法将被调用 * * @param session WebSocket会话对象,表示与客户端的连接 * @param message 文本消息对象,包含从客户端接收到的消息内容 * @throws Exception 如果处理过程中出现异常,将被抛出 */ @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String payload = message.getPayload(); logger.info("记录接收到的消息: {} ,session Id: {}", payload, session.getId()); } /** * 在WebSocket连接关闭后调用此方法 * 主要用于清理会话以及记录关闭状态 * * @param session 关闭的WebSocket会话 * @param status 会话关闭的状态 * @throws Exception 抛出异常,但具体类型未指定 */ @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { // 从会话集合中移除关闭的会话 sessions.remove(session); // 记录会话移除的信息,包括会话ID和关闭状态 logger.info("会话移除, session Id: {}, 关闭状态: {}", session.getId(), status); } /** * 处理传输错误 * 当与客户端的WebSocket连接出现传输错误时,此方法将被调用 * 它记录错误信息,并关闭WebSocket会话 * * @param session 发生错误的WebSocket会话 * @param exception 引发的异常 * @throws Exception 如果在关闭会话过程中发生错误,可能会抛出异常 */ @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { // 记录传输错误的日志,包括会话ID和异常信息 logger.error("Transport error for session: {}, exception: {}", session.getId(), exception.getMessage(), exception); // 关闭发生错误的WebSocket会话,指定关闭状态为服务器错误 session.close(CloseStatus.SERVER_ERROR); } }

注意

不同的端口需要编写不同的MyWebSocketHandler,不然发送消息时只要是通过这三个端点接入的session的都会收到消息的推送。

本文作者:Weee

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!