Spring WebSocket @SendToSession:向特定会话发送消息

Tob*_*bia 36 java spring stomp spring-mvc spring-websocket

是否可以向特定会话发送消息?

我在客户端和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?
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您在此代码中看到的,客户端可以启动异步作业,当它完成时,它需要结束消息.显然,我只需要向申请人发送信息而不是向所有人广播.有一个@SendToSession注释或messagingTemplate.convertAndSendToSession方法会很棒.

UPDATE

我试过这个:

messagingTemplate.convertAndSend("/queue/jobend", true, Collections.singletonMap(SimpMessageHeaderAccessor.SESSION_ID_HEADER, sessionId));
Run Code Online (Sandbox Code Playgroud)

但这会向所有会话广播,而不仅仅是指定的会话.

更新2

使用convertAndSendToUser()方法进行测试.这个测试是和正式的Spring教程的破解:https://spring.io/guides/gs/messaging-stomp-websocket/

这是服务器代码:

@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;
}
Run Code Online (Sandbox Code Playgroud)

这是客户端代码:

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));
                });
            });
        }
Run Code Online (Sandbox Code Playgroud)

不幸的是,客户端没有像预期的那样每5000毫秒收到一次会话回复.我确定"1"是连接的第二个客户端的有效sessionId,因为我在调试模式下看到它SimpMessageHeaderAccessor.getSessionId()

背景情景

我想为远程作业创建进度条,客户端要求服务器提供异步作业,并通过服务器发送的websocket消息检查其进度.这不是文件上传而是远程计算,因此只有服务器知道每个作业的进度.我需要向特定会话发送消息,因为每个作业都是由会话启动的.客户端要求远程计算服务器启动此作业,并为每个作业步骤回复申请人客户端及其作业进度状态.客户端获取有关其作业的消息并构建进度/状态栏.这就是我需要每会话消息的原因.我也可以使用每用户消息,但Spring 不为每个用户提供未经请求的消息.(无法使用Spring Websocket发送用户消息)

工作解决方案

 __      __ ___   ___  _  __ ___  _  _   ___      ___   ___   _    _   _  _____  ___  ___   _  _ 
 \ \    / // _ \ | _ \| |/ /|_ _|| \| | / __|    / __| / _ \ | |  | | | ||_   _||_ _|/ _ \ | \| |
  \ \/\/ /| (_) ||   /| ' <  | | | .` || (_ |    \__ \| (_) || |__| |_| |  | |   | || (_) || .` |
   \_/\_/  \___/ |_|_\|_|\_\|___||_|\_| \___|    |___/ \___/ |____|\___/   |_|  |___|\___/ |_|\_|
Run Code Online (Sandbox Code Playgroud)

从UPDATE2解决方案开始,我必须使用last param(MessageHeaders)完成convertAndSendToUser方法:

messagingTemplate.convertAndSendToUser("1","/queue/test", new Return("test"), createHeaders("1"));
Run Code Online (Sandbox Code Playgroud)

createHeaders()这个方法在哪里:

private MessageHeaders createHeaders(String sessionId) {
        SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
        headerAccessor.setSessionId(sessionId);
        headerAccessor.setLeaveMutable(true);
        return headerAccessor.getMessageHeaders();
    }
Run Code Online (Sandbox Code Playgroud)

Bri*_*zel 33

无需创建特定目的地,从Spring 4.1开始就已经开箱即用(参见SPR-11309).

在用户订阅/user/queue/something队列的情况下,您可以使用以下命令向单个会话发送消息:

正如SimpMessageSendingOperations Javadoc中所述,由于您的用户名实际上是一个sessionId,因此您必须将其设置为标头,否则DefaultUserDestinationResolver将无法路由该消息并将其丢弃.

SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor
    .create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);

messagingTemplate.convertAndSendToUser(sessionId,"/queue/something", payload, 
    headerAccessor.getMessageHeaders());
Run Code Online (Sandbox Code Playgroud)

您无需为此用户进行身份验证.

  • 最后,我明白了……我还应该将标头参数传递给convertAndSendToUser()使其起作用。请使用最后一个参数更新您的答案,然后我将其作为答案! (2认同)