![](/img/trans.png)
[英]Spring+WebSocket+STOMP. Message to specific session (NOT user)
[英]Sending message to specific user on Spring Websocket
如何仅从服务器向特定用户发送 websocket 消息?
我的网络应用程序具有 spring 安全设置并使用 websocket。我在尝试仅从服务器向特定用户发送消息时遇到棘手的问题。
我阅读手册的理解是我们可以做的服务器
simpMessagingTemplate.convertAndSend("/user/{username}/reply", reply);
在客户端:
stompClient.subscribe('/user/reply', handler);
但我永远无法调用订阅回调。 我尝试了许多不同的路径但没有运气。
如果我将它发送到/topic/reply它有效,但所有其他连接的用户也会收到它。
为了说明这个问题,我在 github 上创建了这个小项目: https://github.com/gerrytan/wsproblem
重现步骤:
1) 克隆并构建项目(确保您使用的是 jdk 1.7 和 maven 3.1)
$ git clone https://github.com/gerrytan/wsproblem.git
$ cd wsproblem
$ mvn jetty:run
2) 导航到http://localhost:8080
,使用 bob/test 或 jim/test 登录
3) 单击“请求用户特定消息”。 预期:仅针对此用户在“仅收到给我的消息”旁边显示一条消息“hello {username}”,实际:未收到任何消息
哦, client side no need to known about current user
,服务器会为您做到这一点。
在服务器端,使用以下方式向用户发送消息:
simpMessagingTemplate.convertAndSendToUser(username, "/queue/reply", message);
注意:使用queue
而不是topic
,Spring总是使用sendToUser
queue
在客户端
stompClient.subscribe("/user/queue/reply", handler);
说明
当任何websocket连接打开时,Spring将为其分配一个session id
(不是HttpSession
,为每个连接分配)。 当您的客户端订阅以/user/
开头的频道时,例如: /user/queue/reply
,您的服务器实例将订阅名为queue/reply-user[session id]
当使用向用户发送消息时,例如:username是admin
您将编写simpMessagingTemplate.convertAndSendToUser("admin", "/queue/reply", message);
Spring将确定哪个session id
映射到用户admin
。 例如:它找到了两个会话wsxedc123
和thnujm456
,Spring会将其转换为2个目标queue/reply-userwsxedc123
和queue/reply-userthnujm456
,它会将您的消息与2个目的地发送给您的消息代理。
消息代理接收消息并将其提供回服务器实例,该实例持有与每个会话相对应的会话(WebSocket会话可由一个或多个服务器保存)。 Spring会将消息转换为destination
(例如: user/queue/reply
)和session id
(例如: wsxedc123
)。 然后,它将消息发送到相应的Websocket session
啊,我发现了我的问题。 首先,我没有在简单代理上注册/user
前缀
<websocket:simple-broker prefix="/topic,/user" />
然后我发送时不需要额外/user
前缀:
convertAndSendToUser(principal.getName(), "/reply", reply);
Spring会自动将"/user/" + principal.getName()
到目标,因此它会解析为“/ user / bob / reply”。
这也意味着在javascript中我必须为每个用户订阅不同的地址
stompClient.subscribe('/user/' + userName + '/reply,...)
我的解决方案基于Thanh Nguyen Van的最佳解释,但另外我已经配置了MessageBrokerRegistry:
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/queue/", "/topic/");
...
}
...
}
我也使用STOMP创建了一个示例websocket项目。 我注意到的是
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");// including /user also works
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/getfeeds").withSockJS();
}
}
无论“/ user”是否包含在config.enableSimpleBroker中(...
我完全相同,它没有使用用户工作
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic" , "/queue");
config.setApplicationDestinationPrefixes("/app");
}
}
在下面的解决方案中,我为客户端和后端编写了代码片段。 我们需要将/user
放在客户端代码的套接字主题的开头。 否则,客户端无法监听套接字。 依赖性
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
WebSocketConfig.java
package com.oktaykcr.notificationservice.config;
import 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 registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/socket").setAllowedOriginPatterns("*");
registry.addEndpoint("/socket").setAllowedOriginPatterns("*").withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/file");
registry.setApplicationDestinationPrefixes("/app");
}
}
WebSocketContoller.java
package com.oktaykcr.notificationservice.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import 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 WebSocketController {
Logger logger = LoggerFactory.getLogger(WebSocketController.class);
@MessageMapping("/socket")
@SendTo("/file/status")
public String fileStatus(@Payload String message) {
logger.info(message);
return message;
}
}
您可以从任何地方向套接字发送消息。 在我的例子中,我的 principal.getName() 等于 userId。
simpMessagingTemplate.convertAndSendToUser(userId, "/file/status", socketMessage);
客户端(带有react-stomp 的ReactJs) App.js
import './App.css';
import SockJsClient from 'react-stomp'
import { useRef } from 'react';
function App() {
const clientRef = useRef();
const sendMessage = (msg) => {
clientRef.current.sendMessage('/app/socket', msg);
}
return (
<div className="App">
<div>
<button onClick={() => sendMessage("Hola")}>Send</button>
</div>
<SockJsClient url='http://localhost:9090/notification-service/socket' topics={['/user/file/status']}
onMessage={(msg) => { console.log(msg); }}
ref={(client) => { clientRef.current = client }} />
</div>
);
}
export default App;
SockJsClient 元素的url属性是http://localhost:9090/notification-service/socket
。 http://localhost:9090
是 Api 网关 ip 地址, notification-service
是微服务的名称, /socket
在WebSocketConfig.java中定义。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.