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默认情况下不会转发Connection
和Upgrade
首部(Header),导致WebSocket无法正常工作,nginx官方文档里已经说明了如何通过配置来解决。同时肯定会遇到连接60s后自动断开的问题,因为nginx默认60秒发现没有数据传送,就会关闭连接,可以通过proxy_read_timeout指令把这一时间延长。
参考链接:https://fookwood.com/spring-boot-tutorial-15-websocket#respond