Springboot 整合 WebSocket ,使用STOMP协议 ,前后端整合实战 (一)

简介: Springboot 整合 WebSocket ,使用STOMP协议 ,前后端整合实战 (一)

前言



websocket ,对于我来说已经是老朋友了。


很久很久以前,我写过两篇websocket 相关的文章。



 一篇极简风,最最最基础的方式整合websocket :


《SpringBoot 整合WebSocket 简单实战案例》


一篇极限风,最最最完善的方式整合websocket (stomp+rabbitmq处理负载):


《Springboot 整合Websocket,Stomp协议,使用rabbitmq做消息代理,消息缓存》


但是按照最近看官们给我反应情况来看, 一个极简不符合需求,因为看官们虽然想简单,但是也想用stomp。


然而那个极限的呢,又太复杂,看官们不乐意去整合rabbitmq。


那么,这篇文章的意义就出来了。


正文



本篇内容:


1.后端整合websocket (STOMP协议)


2.群发、指定单发


3.前端简单页面示例(接收、发送消息)


事不宜迟,开始敲代码。


先看下这次实战案例项目结构:


image.png


1. pom.xml 核心依赖的导入:


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>


2.application.yml  (我就单纯设置一下端口)

 


3.WebSocketConfig.java

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;
/**
 * @Author JCccc
 * @Description   EnableWebSocketMessageBroker-注解开启STOMP协议来传输基于代理的消息,此时控制器支持使用@MessageMapping
 * @Date 2021/6/30 8:53
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        //topic用来广播,user用来实现点对点
        config.enableSimpleBroker("/topic", "/user");
    }
    /**
     * 开放节点
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //注册两个STOMP的endpoint,分别用于广播和点对点
        //广播
        registry.addEndpoint("/publicServer").setAllowedOrigins("*").withSockJS();
        //点对点
        registry.addEndpoint("/privateServer").setAllowedOrigins("*").withSockJS();
    }
}


4.推送消息的实体类 Message.java


/**
 * @Author JCccc
 * @Description
 * @Date 2021/8/20 9:26
 */
public class Message {
    /**
     * 消息编码
     */
    private String code;
    /**
     * 来自(保证唯一)
     */
    private String form;
    /**
     * 去自(保证唯一)
     */
    private String to;
    /**
     * 内容
     */
    private String content;
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public String getForm() {
        return form;
    }
    public void setForm(String form) {
        this.form = form;
    }
    public String getTo() {
        return to;
    }
    public void setTo(String to) {
        this.to = to;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
}


5.前端简单调试页面


① publicExample.html 监听广播消息的测试页面


<html>
<head>
    <meta charset="UTF-8">
    <title>等系统推消息</title>
    <script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
    <script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.2.0.min.js"
            integrity="sha256-JAW99MJVpJBGcbzEuXk4Az05s/XyDdBomFqNlM3ic+I=" crossorigin="anonymous"></script>
    <script type="text/javascript">
        var stompClient = null;
        function setConnected(connected) {
            document.getElementById("connect").disabled = connected;
            document.getElementById("disconnect").disabled = !connected;
            $("#response").html();
        }
        function connect() {
             var socket = new SockJS("http://localhost:9908/publicServer");
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function (frame) {
                setConnected(true);
                console.log('Connected: ' + frame);
                stompClient.subscribe('/topic/all', function (response) {
                    var responseData = document.getElementById('responseData');
                    var p = document.createElement('p');
                    p.style.wordWrap = 'break-word';
                    p.appendChild(document.createTextNode(response.body));
                    responseData.appendChild(p);
                });
            },{});
        }
        function disconnect() {
            if (stompClient != null) {
                stompClient.disconnect();
            }
            setConnected(false);
            console.log("Disconnected");
        }
        function sendMsg() {
            var content = document.getElementById('content').value;
            stompClient.send("/all",{},JSON.stringify({'content': content}));
        }
    </script>
</head>
<body onload="disconnect()">
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div>
    <div>
        <labal>连接广播频道</labal>
        <button id="connect" onclick="connect();">Connect</button>
        <labal>取消连接</labal>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>
    </div>
    <div id="conversationDiv">
        <labal>广播消息</labal>
        <input type="text" id="content"/>
        <button id="sendMsg" onclick="sendMsg();">Send</button>
    </div>
    <div>
        <labal>接收到的消息:</labal>
        <p id="responseData"></p>
    </div>
</div>
</body>
</html>


简析:


 image.png


趁热打铁,我们模拟系统后端给前端推送广播消息,通过接口模拟:

 

TestController.java


/**
 * @Author JCccc
 * @Description
 * @Date 2021/8/20 8:53
 */
@Controller
public class TestController {
    @Autowired
    public SimpMessagingTemplate template;
    /**
     * 广播
     *
     * @param msg
     */
    @ResponseBody
    @RequestMapping("/pushToAll")
    public void subscribe( @RequestBody Message msg) {
        template.convertAndSend("/topic/all", msg.getContent());
    }
}


简析:


我们推送消息,直接用 SimpMessagingTemplate ,


用的是convertAndSend 广播方式推送到对于的主题目的地 destination 。


(可以看到其实还有convertAndSendToUser ,不着急,后面会说,这是发送给某个连接用户的)


image.png


直接把项目跑起来,打开页面开始测试:


image.png


我们先点击connect ,连接成功:


image.png


可以看到实际上stomp.min.js 最终也是转化成为 ws/wss这种方式成功连接:


image.png


调用测试接口,推送广播消息:


image.pngimage.png


在console其实也能看到:


image.png 


广播功能就到这,接下来是 点对点。


前端页面:


privateExample.html


<html>
<head>
    <meta charset="UTF-8">
    <title>聊起来</title>
    <script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
    <script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.2.0.min.js"
            integrity="sha256-JAW99MJVpJBGcbzEuXk4Az05s/XyDdBomFqNlM3ic+I=" crossorigin="anonymous"></script>
    <script type="text/javascript">
        var stompClient = null;
        function setConnected(connected) {
            document.getElementById("connect").disabled = connected;
            document.getElementById("disconnect").disabled = !connected;
            $("#response").html();
        }
        function connect() {
            var socket = new SockJS("http://localhost:9908/privateServer");
            stompClient = Stomp.over(socket);
            stompClient.heartbeat.outgoing = 20000;
            // client will send heartbeats every 20000ms
            stompClient.heartbeat.incoming = 0;
            stompClient.connect({}, function (frame) {
                setConnected(true);
                console.log('Connected: ' + frame);
                stompClient.subscribe('/user/' + document.getElementById('user').value + '/message', function (response) {
                    var responseData = document.getElementById('responseData');
                    var p = document.createElement('p');
                    p.style.wordWrap = 'break-word';
                    p.appendChild(document.createTextNode(response.body));
                    responseData.appendChild(p);
                });
            });
        }
        function disconnect() {
            if (stompClient != null) {
                stompClient.disconnect();
            }
            setConnected(false);
            console.log("Disconnected");
        }
        function sendMsg() {
            var headers = {
                login: 'mylogin',
                passcode: 'mypasscode',
// additional header
                'accessToken': 'HWPO325J9814GBHJF933'
            };
            var content = document.getElementById('content').value;
            var to = document.getElementById('to').value;
            stompClient.send("/alone", {'accessToken': 'HWPO325J9814GBHJF933'}, JSON.stringify({
                'content': content,
                'to': to
            }));
        }
    </script>
</head>
<body onload="disconnect()">
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div>
    <div>
        <labal>连接用户</labal>
        <input type="text" id="user"/>
        <button id="connect" onclick="connect();">Connect</button>
    </div>
    <div>
        <labal>取消连接</labal>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">Disconnect</button>
    </div>
    <div id="conversationDiv">
        <labal>发送消息</labal>
        <div>
            <labal>内容</labal>
            <input type="text" id="content"/>
        </div>
        <div>
            <labal>发给谁</labal>
            <input type="text" id="to"/>
        </div>
        <button id="sendMsg" onclick="sendMsg();">Send</button>
    </div>
    <div>
        <labal>接收到的消息:</labal>
        <p id="responseData"></p>
    </div>
</div>
</body>
</html>


简析:


image.png


趁热打铁,我们模拟系统后端给前端推送点对点消息(指定某个用户),通过接口模拟:


    /**
     * 点对点
     */
    @ResponseBody
    @RequestMapping("/pushToOne")
    public void queue(@RequestBody Message msg) {
        System.out.println("进入方法");
        /*使用convertAndSendToUser方法,第一个参数为用户id,此时js中的订阅地址为
        "/user/" + 用户Id + "/message",其中"/user"是固定的*/
        template.convertAndSendToUser(msg.getTo(), "/message", msg.getContent());
    }


用的是convertAndSendToUser 推送到指定的用户 ,对于的主题目的地 destination(/message)


也许看到这里,你会觉得很奇怪,为什么我们推的主题是 /message,但是前端订阅的却是


"/user/" + 用户Id + "/message"


我们直接看源码:

image.pngimage.png


ok,应该不用多说,代码帮我们自己拼接起来了,跟前端订阅规则保持一致。

直接跑项目,测试一下:


模拟我们连接的用户标识 19901 ,连接成功


image.png


使用postman调用我们的测试接口,模拟系统指定推送消息到 19901 这个人 :


image.png


然后也给20011发送一下消息:


image.png 


然后看到对应的用户也能收到对应的消息:


image.png


对点推送,广播推送,也已经完毕了 。


这种情况就是相当于使用http接口方式,去撮合后端服务做 消息推送。


其实spring还提供了一些好玩的注解,


@MessageMapping  这个注解是对称于  @EnableWebSocketMessageBroker


image.png


也就是说,如果我们使用@EnableWebSocketMessageBroker ,那么我们在接口上面其实就能直接使用  @MessageMapping。


怎么用?image.png


然后前端代码里面 的使用:


image.png


这样我们就可以通过前端直接调用相关接口了:


image.png


个人认为其实没有必要使用这个注解,直接通过前端调用后端服务代码,我们服务端来根据Message里面 的 发送方、接收方、消息类型(点对点、广播)就可以直接完成相关也业务场景了。


ok,该篇就到此。


下一篇,给大家带来怎么解决websocket负载问题 。

相关实践学习
Serverless极速搭建Hexo博客
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
相关文章
|
26天前
|
开发框架 前端开发 网络协议
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
251 1
|
1月前
|
前端开发 JavaScript Python
Python Web应用中的WebSocket实战:前后端分离时代的实时数据交换
在前后端分离的Web应用开发模式中,如何实现前后端之间的实时数据交换成为了一个重要议题。传统的轮询或长轮询方式在实时性、资源消耗和服务器压力方面存在明显不足,而WebSocket技术的出现则为这一问题提供了优雅的解决方案。本文将通过实战案例,详细介绍如何在Python Web应用中运用WebSocket技术,实现前后端之间的实时数据交换。
75 0
|
4月前
|
监控 前端开发 API
实战指南:使用Python Flask与WebSocket实现高效的前后端分离实时系统
【7月更文挑战第18天】构建实时Web应用,如聊天室,可借助Python的Flask和WebSocket。安装Flask及Flask-SocketIO库,创建Flask应用,处理WebSocket事件。前端模板通过Socket.IO库连接服务器,发送和接收消息。运行应用,实现实时通信。此示例展现了Flask结合WebSocket实现前后端实时交互的能力。
491 3
|
30天前
|
前端开发 Java C++
RSocket vs WebSocket:Spring Boot 3.3 中的两大实时通信利器
本文介绍了在 Spring Boot 3.3 中使用 RSocket 和 WebSocket 实现实时通信的方法。RSocket 是一种高效的网络通信协议,支持多种通信模式,适用于微服务和流式数据传输。WebSocket 则是一种标准协议,支持全双工通信,适合实时数据更新场景。文章通过一个完整的示例,展示了如何配置项目、实现前后端交互和消息传递,并提供了详细的代码示例。通过这些技术,可以大幅提升系统的响应速度和处理效率。
|
2月前
|
移动开发 JSON Java
Jmeter实现WebSocket协议的接口测试方法
WebSocket协议是HTML5的一种新协议,实现了浏览器与服务器之间的全双工通信。通过简单的握手动作,双方可直接传输数据。其优势包括极小的头部开销和服务器推送功能。使用JMeter进行WebSocket接口和性能测试时,需安装特定插件并配置相关参数,如服务器地址、端口号等,还可通过CSV文件实现参数化,以满足不同测试需求。
239 7
Jmeter实现WebSocket协议的接口测试方法
|
1月前
|
消息中间件 网络协议 安全
C# 一分钟浅谈:WebSocket 协议应用
【10月更文挑战第6天】在过去的一年中,我参与了一个基于 WebSocket 的实时通信系统项目,该项目不仅提升了工作效率,还改善了用户体验。本文将分享在 C# 中应用 WebSocket 协议的经验和心得,包括基础概念、C# 实现示例、常见问题及解决方案等内容,希望能为广大开发者提供参考。
101 0
|
2月前
|
机器学习/深度学习 自然语言处理 网络协议
为什么ChatGPT采用SSE协议而不是WebSocket?
在探讨大型语言模型ChatGPT的技术实现时,一个引人注目的细节是其选择使用SSE(Server-Sent Events)协议而非WebSocket来实现数据的实时推送。这一选择背后,蕴含着对技术特性、应用场景及资源效率的深思熟虑。本文将深入探讨ChatGPT为何偏爱SSE,以及这一决策背后的技术逻辑。
237 3
|
3月前
|
开发框架 网络协议 Java
SpringBoot WebSocket大揭秘:实时通信、高效协作,一文让你彻底解锁!
【8月更文挑战第25天】本文介绍如何在SpringBoot项目中集成WebSocket以实现客户端与服务端的实时通信。首先概述了WebSocket的基本原理及其优势,接着详细阐述了集成步骤:添加依赖、配置WebSocket、定义WebSocket接口及进行测试。通过示例代码展示了整个过程,旨在帮助开发者更好地理解和应用这一技术。
271 1
|
3月前
|
小程序 Java API
springboot 微信小程序整合websocket,实现发送提醒消息
springboot 微信小程序整合websocket,实现发送提醒消息
|
3月前
|
JavaScript 前端开发 网络协议
WebSocket在Java Spring Boot+Vue框架中实现消息推送功能
在现代Web应用中,实时消息提醒是一项非常重要的功能,能够极大地提升用户体验。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为实现实时消息提醒提供了高效且低延迟的解决方案。本文将详细介绍如何在Java Spring Boot后端和Vue前端框架中利用WebSocket实现消息提醒功能。
158 0