Java Spring WebSocket @SendToSession:向特定会话发送消息
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/34929578/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring WebSocket @SendToSession: send message to specific session
提问by Tobia
Is it possible to send a message to specific session?
是否可以向特定会话发送消息?
I have an unauthenticated websocket between clients and a Spring servlet. I need to send an unsolicited message to a specific connection when an async job ends.
我在客户端和 Spring servlet 之间有一个未经身份验证的 websocket。当异步作业结束时,我需要向特定连接发送未经请求的消息。
@Controller
public class WebsocketTest {
@Autowired
public SimpMessageSendingOperations messagingTemplate;
ExecutorService executor = Executors.newSingleThreadExecutor();
@MessageMapping("/start")
public void start(SimpMessageHeaderAccessor accessor) throws Exception {
String applicantId=accessor.getSessionId();
executor.submit(() -> {
//... slow job
jobEnd(applicantId);
});
}
public void jobEnd(String sessionId){
messagingTemplate.convertAndSend("/queue/jobend"); //how to send only to that session?
}
}
As you can see in this code, the client can start an async job and when it finishes, it needs the end message. Obviously, I need to message only the applicant and not broadcast to everyone.
It would be great to have an @SendToSession
annotation or messagingTemplate.convertAndSendToSession
method.
正如您在这段代码中看到的,客户端可以启动一个异步作业,当它完成时,它需要结束消息。显然,我只需要向申请人发送消息,而不是向所有人广播。有一个@SendToSession
注释或messagingTemplate.convertAndSendToSession
方法会很棒。
UPDATE
更新
I tried this:
我试过这个:
messagingTemplate.convertAndSend("/queue/jobend", true, Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, sessionId));
But this broadcasts to all sessions, not only the one specified.
但这会广播到所有会话,而不仅仅是指定的会话。
UPDATE 2
更新 2
Test with convertAndSendToUser() method. This test is and hack of the official Spring tutorial: https://spring.io/guides/gs/messaging-stomp-websocket/
使用 convertAndSendToUser() 方法进行测试。这个测试是官方 Spring 教程的 hack:https: //spring.io/guides/gs/messaging-stomp-websocket/
This is the server code:
这是服务器代码:
@Controller
public class WebsocketTest {
@PostConstruct
public void init(){
ScheduledExecutorService statusTimerExecutor=Executors.newSingleThreadScheduledExecutor();
statusTimerExecutor.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"));
}
}, 5000,5000, TimeUnit.MILLISECONDS);
}
@Autowired
public SimpMessageSendingOperations messagingTemplate;
}
and this is the client code:
这是客户端代码:
function connect() {
var socket = new WebSocket('ws://localhost:8080/hello');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/user/queue/test', function(greeting){
console.log(JSON.parse(greeting.body));
});
});
}
Unfortunately client doesn't receive its per-session reply every 5000ms as expected. I'm sure that "1" is a valid sessionId for the 2nd client connected because I see it in debug mode with SimpMessageHeaderAccessor.getSessionId()
不幸的是,客户端没有按预期每 5000 毫秒收到一次会话回复。我确定“1”是连接的第二个客户端的有效 sessionId,因为我在调试模式下看到它SimpMessageHeaderAccessor.getSessionId()
BACKGROUND SCENARIO
背景场景
I want to create a progress bar for a remote job, client asks server for an async job and it checks its progress by websocket message sent from server. This is NOT a file upload but a remote computation, so only server knows the progress of each job. I need to send a message to specific session because each job is started by session. Client asks for a remote computation Server starts this job and for every job step reply to applicant client with its job progress status. Client gets messages about its job and build up a progress/status bar. This is why I need a per-session messages. I could also use a per-user messages, but Spring does not provideper user unsolicited messages. (Cannot send user message with Spring Websocket)
我想为远程作业创建一个进度条,客户端向服务器询问异步作业,并通过从服务器发送的 websocket 消息检查其进度。这不是文件上传而是远程计算,因此只有服务器知道每个作业的进度。我需要向特定会话发送消息,因为每个作业都是由会话启动的。客户端请求远程计算服务器启动此作业,并针对每个作业步骤回复申请人客户端及其作业进度状态。客户端获取有关其工作的消息并建立进度/状态栏。这就是为什么我需要每个会话的消息。我也可以使用每用户消息,但 Spring不提供每用户未经请求的消息。(无法使用 Spring Websocket 发送用户消息)
WORKING SOLUTION
工作解决方案
__ __ ___ ___ _ __ ___ _ _ ___ ___ ___ _ _ _ _____ ___ ___ _ _
\ \ / // _ \ | _ \| |/ /|_ _|| \| | / __| / __| / _ \ | | | | | ||_ _||_ _|/ _ \ | \| |
\ \/\/ /| (_) || /| ' < | | | .` || (_ | \__ \| (_) || |__| |_| | | | | || (_) || .` |
\_/\_/ \___/ |_|_\|_|\_\|___||_|\_| \___| |___/ \___/ |____|\___/ |_| |___|\___/ |_|\_|
Starting from the UPDATE2 solution I had to complete convertAndSendToUser method with last param (MessageHeaders):
从 UPDATE2 解决方案开始,我必须使用最后一个参数(MessageHeaders)完成 convertAndSendToUser 方法:
messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"), createHeaders("1"));
where createHeaders()
is this method:
createHeaders()
这个方法在哪里:
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
采纳答案by Brian Clozel
No need to create specific destinations, it's already done out of the box as of Spring 4.1 (see SPR-11309).
无需创建特定的目的地,从 Spring 4.1 开始,它已经开箱即用(参见SPR-11309)。
Given users subscribe to a /user/queue/something
queue, you can send a message to a single session with:
给定用户订阅/user/queue/something
队列,您可以使用以下命令向单个会话发送消息:
As stated in the SimpMessageSendingOperations Javadoc, since your user name is actually a sessionId, you MUST set that as a header as well otherwise the DefaultUserDestinationResolver
won't be able to route the message and will drop it.
如SimpMessageSendingOperations Javadoc 中所述,由于您的用户名实际上是 sessionId,因此您必须将其设置为标头,否则DefaultUserDestinationResolver
将无法路由消息并将其丢弃。
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor
.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
messagingTemplate.convertAndSendToUser(sessionId,"/queue/something", payload,
headerAccessor.getMessageHeaders());
You don't need users to be authenticated for this.
您不需要为此对用户进行身份验证。
回答by Aviad
It is very complicated and in my opinion, isn't worth it. You need to create a subscription for every user (even unauthenticated ones) by their session id.
这非常复杂,在我看来,不值得。您需要通过会话 ID 为每个用户(甚至未经身份验证的用户)创建订阅。
Let's say that every user subscribes to a unique queue only for him:
假设每个用户都为他订阅了一个唯一的队列:
stompClient.subscribe('/session/specific' + uuid, handler);
On the server, before the user subscribes you will need to notify and send a message for the specific session and save to a map:
在服务器上,在用户订阅之前,您需要通知并发送特定会话的消息并保存到地图:
@MessageMapping("/putAnonymousSession/{sessionId}")
public void start(@DestinationVariable sessionId) throws Exception {
anonymousUserSession.put(key, sessionId);
}
After that, when you want to send message to the user you will need to:
之后,当您想向用户发送消息时,您需要:
messagingTemplate.convertAndSend("/session/specific" + key);
But I don't really know what you are trying to do and how you will find the specific session (who is anonymous).
但是我真的不知道您要做什么以及您将如何找到特定会话(谁是匿名的)。
回答by Sandy
You need to simply add the session id in
您只需将会话 ID 添加到
Server Side
convertAndSendToUser(sessionId,apiName,responseObject);
Client Side
$stomp.subscribe('/user/+sessionId+'/apiName',handler);
服务器端
convertAndSendToUser(sessionId,apiName,responseObject);
客户端
$stomp.subscribe('/user/+sessionId+'/apiName',handler);
Note:
Dont forget to add '/user'
in your end point in server side.
注意:
不要忘记'/user'
在服务器端添加端点。