SpringBoot 集成websocket

springboot 版本为 2.7,利用websocket向前端推送数据,配置如下;

1、添加依赖:

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

项目中其他依赖自动安装了 org.apache.tomcat.embed:tomcat-embed-websocket:9.0.26 依赖包,无需手动添加。如果你的项目中没有这个依赖,则需要手动添加到 pom 中。

2、配置类中添加一个Bean

  @Bean
    WebSocketConfigurer createWebSocketConfigurer(@Autowired MessageHandler messageHandler, @Autowired MessageHandshakeInterceptor messageHandshakeInterceptor) {
        return new WebSocketConfigurer() {
            @Override
            public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
                registry.addHandler(messageHandler, "/websocket").addInterceptors(messageHandshakeInterceptor).setAllowedOrigins("*");
            }
        };
    }

其中自动注入了两个对象: messageHandler 和  messageHandshakeInterceptor,其中 messageHandler  中用于消息的初始化、监听、和连接的销毁,messageHandshakeInterceptor 用于会话拦截,从httpSession中获取用户信息,包括登陆、权限相关。

3、messageHandler 对象

@Component
public class MessageHandler extends TextWebSocketHandler {
    //   保存所有会话实例
    private Map<String, WebSocketSession> clients = new ConcurrentHashMap<>();

    @Autowired
    Logger logger;

    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws IOException {
        //新会话放入Map
        clients.put(webSocketSession.getId(), webSocketSession);
        String httpSessionId = (String) webSocketSession.getAttributes().get("httpSessionId");
        logger.info(httpSessionId);
        logger.info("websocket: {} 连接成功", webSocketSession.getId());
        webSocketSession.sendMessage(new TextMessage("welcome"));
    }

    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus status) {
        clients.remove(webSocketSession.getId());
        logger.info("websocket: {} 断开连接", webSocketSession.getId());
    }

    public void handleTextMessage(WebSocketSession webSocketSession, TextMessage message) {
        String s = message.getPayload();
        System.out.println(s);
    }
}

messageHandler 从 TextWebSocketHandler 继承,因为发送的是文本消息。如果是二进制消息,可以从 BinaryWebSocketHandler 继承。覆写的 afterConnectionEstablished 方法 和  afterConnectionClosed 方法,顾名思义,用于websocket连接建立和销毁的回调。handleTextMessage 方法用于消息监听。

如果是主动发送消息,可以从 clients 中保存的websocketSession 会话集合中,取出对应的 websocketSession,调用其 sendMessage方法,如果是群发消息,则直接广播数据即可。注意消息需要 TextMessage 包装。

for (String id : clients.keySet()) {
    WebSocketSession session = clients.get(id);
    session.sendMessage(new TextMessage("welcome"));
}

问题来了,如果是给特定的客户端发送消息,该如何区分客户端?一般在登陆验证后,将验证信息保存到 httpSession中,websocketSession没有用户登陆信息,如果这时有一条消息过来,该发送给哪个 websocketSession?所以需要在websocketSession中拿到httpSession。这就是 messageHandshakeInterceptor 对象需要干的事情。

4、messageHandshakeInterceptor 对象

@Component
public class MessageHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
    public MessageHandshakeInterceptor () {
        super();
    }
}

添加了这个拦截器之后,就可以访问httpSession中保存的信息。测试一下:

在httpSession中放入httpSessionId:

httpSession.setAttribute("httpSessionId", httpSession.getId());

在 websocket中拿到对应的 httpSessionId:

String httpSessionId = (String) webSocketSession.getAttributes().get("httpSessionId");
logger.info(httpSessionId);

 观察 clients 中保存的key 是  websocketSession.getId() ,与 httpSeesion.getId() 是不同的。如果使用httpSeesion中的id作为key,还可以直接从登陆会话中直接拿到对应参数,进而从clients 直接取出 websocketSession 会话,发送消息。

5、setAllowedOrigins 

如果直接使用上述方案配置好之后,大概率会连接不上websocket,原因在于浏览器对于 websocket的跨域。所以设置 setAllowedOrigins 非常重要。

registry.addHandler(messageHandler, "/websocket").addInterceptors(messageHandshakeInterceptor).setAllowedOrigins("*");

6、注意

这里的websocket 与 http 服务公用了一个端口 80 或443。实际项目中使用nginx作为反向代理,将请求发送到实际的内网服务器上。但是nginx默认情况下不会转发ConnectionUpgrade首部(Header),导致WebSocket无法正常工作,nginx官方文档里已经说明了如何通过配置来解决。同时肯定会遇到连接60s后自动断开的问题,因为nginx默认60秒发现没有数据传送,就会关闭连接,可以通过proxy_read_timeout指令把这一时间延长。

 

参考链接:https://fookwood.com/spring-boot-tutorial-15-websocket#respond

 

posted @ 2022-08-15 10:15  恩恩先生  阅读(546)  评论(0编辑  收藏  举报