Websocket在Java中的实践——STOMP通信的最小Demo

《Websocket在Java中的实践——SockJS连接服务端》中,我们介绍了如何使用SockJS和Websocket通信。本文我们将介绍如何使用StompJS和Websocket服务端通信。

STOMP(Simple Text Orientated Messaging Protocol)介绍

STOMP,即简单面向文本的消息协议,是一种为处理在消息中间件上传输的文本消息而设计的简单协议。它提供了一种类似于HTTP或SMTP的文本帧格式,允许客户端与消息中间件进行交互,如发布、订阅和处理消息。STOMP的设计初衷是提供一个简单、可互操作的协议,以便在多种不同的消息中间件产品之间实现通信。

主要特点

  • 简单性:STOMP使用简单的文本命令和响应格式,易于理解和实现。这使得开发人员可以轻松地构建基于STOMP的客户端和服务器应用程序。
  • 可互操作性:由于STOMP是标准化的协议,因此它可以在不同的消息中间件产品之间实现通信。这意味着开发人员可以选择最适合其需求的消息中间件,而无需担心与现有系统不兼容的问题。
  • 灵活性:STOMP支持多种消息类型(如文本、二进制、JSON等),并且允许在消息中添加自定义头部字段。这使得开发人员可以根据需要灵活地处理消息。
  • 可靠性:STOMP提供了可靠的消息传递机制,确保消息能够准确地从发送者传递到接收者。此外,它还支持持久化消息和事务处理等功能,以满足对可靠性的高要求。
  • 可扩展性:STOMP是一种可扩展的协议,可以根据需要进行扩展以满足特定需求。例如,可以添加新的命令、头部字段或消息类型来支持特定的应用场景。

样例

STOMP是一种基于“帧”的协议。其内容样例如下:

COMMAND
header1:value1
header2:value2

Body^@

应用场景

STOMP广泛应用于各种需要可靠消息传递的场景,如企业集成、物联网、实时数据流处理等。它适用于各种编程语言和框架,并且已经有许多现成的客户端库和工具可供开发人员使用。通过使用STOMP,开发人员可以轻松地构建高性能、可伸缩的消息传递系统,以满足各种复杂的应用需求。

STOMP客户端与Websocket的通信过程

握手

Websocket服务需要提供一个地址供STOMP客户端与其握手。
在我们的案例中,STOMP客户端会发出帧的内容如下:

CONNECT
accept-version:1.2,1.1,1.0
heart-beat:4000,4000

如果连接建立成功过,则会收到如下帧:

CONNECTED
heart-beat:0,0
version:1.2
content-length:0
^@

订阅

STOMP客户端发送下面帧请求订阅服务端消息

SUBSCRIBE
id:sub-0
destination:/receive/msg-to-user
^@

发送

STOMP客户端发送下面帧向服务端发送消息

SEND
destination:/send/msg-from-user
content-length:22
{“content”:“messages”}^@

接收消息

接收到消息的帧内容如下:

MESSAGE
content-length:22
message-id:33cbe0ce-39e3-12da-b711-9d4351b01dcc-1
subscription:sub-0
content-type:text/plain;charset=UTF-8
destination:/receive/msg-to-user
content-length:22
{“content”:“messages”}^@

服务端

提供handshake地址

src/main/java/com/nyctlc/stomp/config/WebSocketConfig.java

package com.nyctlc.stomp.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("/handshake");
    }

创建代理

在这个框架中,Spring程序会充当STOMP客户端的代理(Broker)。所以上面代码使用了EnableWebSocketMessageBroker注解来开启代理。

EnableWebSocketMessageBroker的作用

  • 支持WebSocket通信:在传统的HTTP通信中,客户端向服务器发送请求,服务器响应请求后关闭连接。而在WebSocket通信中,客户端和服务器之间的连接始终保持打开状态,允许双方随时互相发送消息,实现实时通信。EnableWebSocketMessageBroker注解为Spring Boot应用程序提供了对WebSocket的支持,使得应用程序能够支持WebSocket通信。
  • 配置消息代理:该注解通过配置消息代理来支持WebSocket通信。在使用EnableWebSocketMessageBroker注解之前,需要先定义一个WebSocket配置类,并通过@Configuration注解标记该类为配置类。在该配置类中,EnableWebSocketMessageBroker注解会自动配置一个WebSocketMessageBrokerConfigurer实例,并将其注册到Spring应用程序上下文中。WebSocketMessageBrokerConfigurer是Spring框架中用于配置WebSocket消息代理的接口。
  • 设置消息代理参数:通过实现WebSocketMessageBrokerConfigurer接口,可以配置WebSocket消息代理的相关参数,如消息代理的地址、消息类型、消息发送和接收的线程池等。
  • 定义消息处理方法:在启用了WebSocket消息代理之后,可以使用@MessageMapping注解来定义WebSocket消息的处理方法。@MessageMapping注解用于指定WebSocket请求的地址,当客户端向该地址发送请求时,会自动调用对应的处理方法进行处理。

配置发布-订阅端口

setApplicationDestinationPrefixes方法用于配置发布端口。它允许你定义这些目的地的前缀,使得服务器能够识别并正确地将消息路由到相应的处理程序(Handler)。这样我们就可以在后续使用MessageMapping定义多个接受消息的端点。本例中我们的消息发布端点是/send/msg-from-user。
enableSimpleBroker表示启用一个内置的内存级消息代理,从而不需要外置的诸如Rabbitmq之类的其他中间件。给它传递的"/receive"是STOMP订阅端点的前缀。本例中我们的消息订阅端点是/receive/msg-to-user。

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/send");
        registry.enableSimpleBroker("/receive");
    }
}

配置消息发布端点逻辑

src/main/java/com/nyctlc/stomp/controller/WebSocketController.java
下面代码融合服务端接受消息以及服务端发送消息的两个逻辑:

  • @MessageMapping(“/msg-from-user”)注解表示/send/msg-from-user端点过来的消息交由该函数处理。
  • @SendTo(“/receive/msg-to-user”)表示这个函数的返回值发送给"/receive/msg-to-user",进而让客户端可以订阅到消息。
package com.nyctlc.stomp.controller;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

import jakarta.websocket.server.PathParam;

@Controller
public class WebSocketController {
    @MessageMapping("/msg-from-user")
    @SendTo("/receive/msg-to-user")
    public String handle(String msg) {
        System.out.println("Received message: " + msg);
        return msg;
    }
}

测试

测试页面

我们使用了stompjs来建立连接,发送消息。
需要注意的是,如果连接建立使用的是SockJS的话,handshake接口要做相应改动。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>STOMP over WebSocket Example with StompJs.Client</title>
    <script src="https://cdn.jsdelivr.net/npm/@stomp/stompjs"></script>
</head>
<body>
    <h2>STOMP over WebSocket Example with StompJs.Client</h2>
    <button id="connectButton">Connect</button>
    <form id="messageForm">
        <input type="text" id="messageInput" placeholder="Type a message..."/>
        <button type="submit">Send</button>
    </form>
    <div id="messages"></div>

    <script>
        var client = null;

        function connect() {
            client = new StompJs.Client({
                brokerURL: 'ws://localhost:8080/handshake', // WebSocket服务端点
                connectHeaders: {},
                debug: function (str) {
                    console.log(str);
                },
                reconnectDelay: 5000,
                heartbeatIncoming: 4000,
                heartbeatOutgoing: 4000,
            });

            client.onConnect = function(frame) {
                console.log('Connected: ' + frame);
                client.subscribe('/receive/msg-to-user', function(message) { // 订阅端点
                    showMessageOutput(JSON.parse(message.body).content);
                });
            };

            client.onStompError = function(frame) {
                console.error('Broker reported error: ' + frame.headers['message']);
                console.error('Additional details: ' + frame.body);
            };

            client.activate();
        }

        function sendMessage(event) {
            event.preventDefault(); // 阻止表单默认提交行为
            var messageContent = document.getElementById('messageInput').value.trim();
            if(messageContent && client && client.connected) {
                var chatMessage = { content: messageContent };
                client.publish({destination: "/send/msg-from-user", body: JSON.stringify(chatMessage)}); // 发送端点
                document.getElementById('messageInput').value = '';
            }
        }

        function showMessageOutput(message) {
            var messagesDiv = document.getElementById('messages');
            var messageElement = document.createElement('div');
            messageElement.appendChild(document.createTextNode(message));
            messagesDiv.appendChild(messageElement);
        }

        document.getElementById('messageForm').addEventListener('submit', sendMessage);

        document.getElementById('connectButton').addEventListener('click', connect);
    </script>
</body>
</html>

配置Controller

这个主要是为了让上面页面可以通过URL访问。
src/main/java/com/nyctlc/stomp/controller/FileController.java

package com.nyctlc.stomp.controller;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;

@Controller
public class FileController {

    @GetMapping("/")
    public String index() {
        return "index"; // 返回index.html
    }

    @RequestMapping(value = "/favicon.ico")
    @ResponseStatus(value = HttpStatus.NO_CONTENT)
    public void favicon() {
        // No operation. Just to avoid 404 error for favicon.ico
    }
}

测试样例

在这里插入图片描述

整体流程

在这里插入图片描述

参考资料

  • 30
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breaksoftware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值