springboot2系列教程(十六)|整合websocket实现广播

一个优秀的废人      2022-05-16     625

关键词:

前言

如题,今天介绍的是 SpringBoot 整合 WebSocket 实现广播消息。

什么是 WebSocket ?

WebSocket 为浏览器和服务器提供了双工异步通信的功能,即浏览器可以向服务器发送信息,反之也成立。

WebSocket 是通过一个 socket 来实现双工异步通信能力的,但直接使用 WebSocket ( 或者 SockJS:WebSocket 协议的模拟,增加了当前浏览器不支持使用 WebSocket 的兼容支持) 协议开发程序显得十分繁琐,所以使用它的子协议 STOMP。

STOMP 协议简介

它是高级的流文本定向消息协议,是一种为 MOM (Message Oriented Middleware,面向消息的中间件) 设计的简单文本协议。

它提供了一个可互操作的连接格式,允许 STOMP 客户端与任意 STOMP 消息代理 (Broker) 进行交互,类似于 OpenWire (一种二进制协议)。

由于其设计简单,很容易开发客户端,因此在多种语言和多种平台上得到广泛应用。其中最流行的 STOMP 消息代理是 Apache ActiveMQ。

STOMP 协议使用一个基于 (frame) 的格式来定义消息,与 Http 的 request 和 response 类似 。

广播

接下来,实现一个广播消息的 demo。即服务端有消息时,将消息发送给所有连接了当前 endpoint 的浏览器。

准备工作

  • SpringBoot 2.1.3
  • IDEA
  • JDK8

Pom 依赖配置

<dependencies>
        <!-- thymeleaf 模板引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- web 启动类 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- WebSocket 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <!-- test 单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

代码注释很详细,不多说。

配置 WebSocket

实现 WebSocketMessageBrokerConfigurer 接口,注册一个 STOMP 节点,配置一个广播消息代理

@Configuration
// @EnableWebSocketMessageBroker注解用于开启使用STOMP协议来传输基于代理(MessageBroker)的消息,这时候控制器(controller)
// 开始支持@MessageMapping,就像是使用@requestMapping一样。
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //注册一个 Stomp 的节点(endpoint),并指定使用 SockJS 协议。
        registry.addEndpoint("/endpointNasus").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 广播式配置名为 /nasus 消息代理 , 这个消息代理必须和 controller 中的 @SendTo 配置的地址前缀一样或者全匹配
        registry.enableSimpleBroker("/nasus");
    }
}

消息类

客户端发送给服务器:

public class Client2ServerMessage {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

服务器发送给客户端:

public class Server2ClientMessage {

    private String responseMessage;

    public Server2ClientMessage(String responseMessage) {
        this.responseMessage = responseMessage;
    }

    public String getResponseMessage() {
        return responseMessage;
    }

    public void setResponseMessage(String responseMessage) {
        this.responseMessage = responseMessage;
    }
}

演示控制器代码

@RestController
public class WebSocketController {

    @MessageMapping("/hello") // @MessageMapping 和 @RequestMapping 功能类似,浏览器向服务器发起消息,映射到该地址。
    @SendTo("/nasus/getResponse") // 如果服务器接受到了消息,就会对订阅了 @SendTo 括号中的地址的浏览器发送消息。
    public Server2ClientMessage say(Client2ServerMessage message) throws Exception {
        Thread.sleep(3000);
        return new Server2ClientMessage("Hello," + message.getName() + "!");
    }

}

引入 STOMP 脚本

将 stomp.min.js (STOMP 客户端脚本) 和 sockJS.min.js (sockJS 客户端脚本) 以及 Jquery 放在 resource 文件夹的 static 目录下。

演示页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8" />
    <title>Spring Boot+WebSocket+广播式</title>

</head>
<body onload="disconnect()">
<noscript><h2 style="color: #ff0000">貌似你的浏览器不支持websocket</h2></noscript>
<div>
    <div>
        <button id="connect" onclick="connect();">连接</button>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">断开连接</button>
    </div>
    <div id="conversationDiv">
        <label>输入你的名字</label><input type="text" id="name" />
        <button id="sendName" onclick="sendName();">发送</button>
        <p id="response"></p>
    </div>
</div>
<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{stomp.min.js}"></script>
<script th:src="@{jquery.js}"></script>
<script type="text/javascript">
    var stompClient = null;

    function setConnected(connected) {
        document.getElementById('connect').disabled = connected;
        document.getElementById('disconnect').disabled = !connected;
        document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
        $('#response').html();
    }
    
    function connect() {
        // 连接 SockJs 的 endpoint 名称为 "/endpointNasus"
        var socket = new SockJS('/endpointNasus'); 
        // 使用 STOMP 子协议的 WebSocket 客户端
        stompClient = Stomp.over(socket); 
        stompClient.connect({}, function(frame) {
            setConnected(true);
            console.log('Connected: ' + frame);
            // 通过 stompClient.subscribe 订阅 /nasus/getResponse 目标发送的信息,对应控制器的 SendTo 定义
            stompClient.subscribe('/nasus/getResponse', function(respnose){
            // 展示返回的信息,只要订阅了 /nasus/getResponse 目标,都可以接收到服务端返回的信息
            showResponse(JSON.parse(respnose.body).responseMessage);
            });
        });
    }
    
    
    function disconnect() {
        // 断开连接
        if (stompClient != null) {
            stompClient.disconnect();
        }
        setConnected(false);
        console.log("Disconnected");
    }

    function sendName() {
        // 向服务端发送消息
        var name = $('#name').val();
        // 通过 stompClient.send 向 /hello (服务端)发送信息,对应控制器 @MessageMapping 中的定义
        stompClient.send("/hello", {}, JSON.stringify({ 'name': name }));
    }

    function showResponse(message) {
          // 接收返回的消息
          var response = $("#response");
          response.html(message);
    }
</script>
</body>
</html>

页面 Controller

注意,这里使用的是 @Controller 注解,用于匹配 html 前缀,加载页面。

@Controller
public class ViewController {

    @GetMapping("/nasus")
    public String getView(){
        return "nasus";
    }
}

测试结果

打开三个窗口访问 http://localhost:8080/nasus ,初始页面长这样:

技术图片

三个页面全部点连接,点击连接订阅 endpoint ,如下图:

技术图片

技术图片

技术图片

在第一个页面,输入名字,点发送 ,如下图:

技术图片

在第一个页面发送消息,等待 3 秒,结果是 3 个页面都接受到了服务端返回的信息,广播成功。

技术图片

技术图片

技术图片

源码下载:

https://github.com/turoDog/Demo/tree/master/springboot_websocket_demo

如果觉得对你有帮助,请给个 Star 再走呗,非常感谢。

后语

如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。

另外,关注之后在发送 1024 可领取免费学习资料。

资料详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享

十六springboot2核心技术——整合jsp(代码片段)

一、整合jsp1.1、添加依赖<dependencies><!--springboot整合web框架起步依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId&g 查看详情

springboot2系列教程|整合thymeleaf

前言如题,今天介绍Thymeleaf,并整合Thymeleaf开发一个简陋版的学生信息管理系统。SpringBoot提供了大量模板引擎,包含Freemarker、Groovy、Thymeleaf、Velocity以及Mustache,SpringBoot中推荐使用Thymeleaf作为模板引擎,因为Thymeleaf提供了完美... 查看详情

springboot2系列教程|springboot整合mybatis

前言如题,今天介绍SpringBoot与Mybatis的整合以及Mybatis的使用,本文通过注解的形式实现。什么是MybatisMyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取... 查看详情

springboot2系列教程(十八)|整合mongodb

微信公众号:一个优秀的废人。如有问题,请后台留言,反正我也不会听。前言如题,今天介绍下SpringBoot是如何整合MongoDB的。MongoDB简介MongoDB是由C++编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统,它将... 查看详情

springboot2系列教程|整合数据缓存cache

如题,今天介绍SpringBoot的数据缓存。做过开发的都知道程序的瓶颈在于数据库,我们也知道内存的速度是大大快于硬盘的,当需要重复获取相同数据时,一次又一次的请求数据库或者远程服务,导致大量时间耗费在数据库查询... 查看详情

springboot2系列教程(十七)|整合websocket实现聊天室

微信公众号:一个优秀的废人。如有问题,请后台留言,反正我也不会听。前言昨天那篇介绍了WebSocket实现广播,也即服务器端有消息时,将消息发送给所有连接了当前endpoint的浏览器。但这无法解决消息由谁发送,又由谁接收... 查看详情

springboot应用系列5--springboot2整合logback

上一篇我们梳理了SpringBoot2整合log4j2的配置过程,其中讲到了SpringBoot2原装适配logback,并且在非异步环境下logback和log4j2的性能差别不大,所以对于那些日志量不算太高的项目来说,选择logback更简单方便。1.pom.xmlpom.xml不需要添加... 查看详情

springboot应用系列6--springboot2整合quartz

Quartz是实现定时任务的利器,Quartz主要有四个组成部分,分别是:1.Job(任务):包含具体的任务逻辑;2.JobDetail(任务详情):是对Job的一种详情描述;3. Trigger(触发器):负责管理触发JobDetail的机制;4. Scheduler(调度器):负... 查看详情

springboot2系列教程|集成swagger2构建强大的restfulapi文档

前言快过年了,不知道你们啥时候放年假,忙不忙。反正我是挺闲的,所以有时间写blog。今天给你们带来SpringBoot集成Swagger2的教程。什么是Swagger2Swagger是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服... 查看详情

springboot2.x系列教程48--多数据源配置之aop动态切换数据源

SpringBoot2.x系列教程48--多数据源配置之AOP动态切换数据源作者:一一哥在上一节中,我通过分包的方式实现了多数据源的配置,接下来我通过AOP切面的方式,带领大家实现第二种多数据源配置方式,该方式是在前面案例的基础上... 查看详情

springboot2.x系列教程48--多数据源配置之aop动态切换数据源

SpringBoot2.x系列教程48--多数据源配置之AOP动态切换数据源作者:一一哥在上一节中,我通过分包的方式实现了多数据源的配置,接下来我通过AOP切面的方式,带领大家实现第二种多数据源配置方式,该方式是在前面案例的基础上... 查看详情

springboot2系列教程(十五)|服务端参数校验之一

估计很多朋友都认为参数校验是客户端的职责,不关服务端的事。其实这是错误的,学过Web安全的都知道,客户端的验证只是第一道关卡。它的参数验证并不是安全的,一旦被有心人抓到可乘之机,他就可以有各种方法来摸拟系... 查看详情

springboot2.0整合同时支持jsp+html跳转(代码片段)

springboot项目创建教程 https://blog.csdn.net/q18771811872/article/details/88126835springboot2.0跳转html教程 https://blog.csdn.net/q18771811872/article/details/88312862springboot2.0跳转jsp教程 https:/ 查看详情

2018最新springboot2.0教程(零基础入门)

一、零基础快速入门SpringBoot2.01、SpringBoot2.x课程全套介绍和高手系列知识点简介:介绍SpringBoot2.x课程大纲章节java基础,jdk环境,maven基础2、SpringBoot2.x依赖环境和版本新特性说明简介:讲解新版本依赖环境和springboot2新特性概述3... 查看详情

springboot2.x最佳实践《一》之springboot2.x初体验

SpringBoot2.X最佳实践前言本系列文章,从零基础接触 SpringBoot2.x新版本,基础入门使用,热部署,到整合各个主流框架Redis4.x,消息队列AciveMQ,RocketMQ等,搜索框架ElasticSearch5.6版本,到web-flux反应式编程,到Actuator监控应用信息... 查看详情

springboot应用系列4--springboot2整合log4j2

一、背景1.log4j2传承于log4j和logback,它是目前性能最好的日志处理工具,有关它们的性能对比请看:2.除了性能好之外,log4j2有这么几个重要的新features:(1)自动热重载配置文件,而且重新加载期间不会丢失日志请求。logback也可以... 查看详情

十七springboot2核心技术——整合mybatis(代码片段)

整合mybatis1.1、添加mybatis、数据库驱动依赖<dependencies><!--SpringBoot框架web项目起步依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web&l 查看详情

十七springboot2核心技术——整合mybatis(代码片段)

整合mybatis1.1、添加mybatis、数据库驱动依赖<dependencies><!--SpringBoot框架web项目起步依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web&l 查看详情