使用Springboot 2.7+Websocket+js实现服务端消息实时推送

简介: 使用Springboot 2.7+Websocket+js实现服务端消息实时推送

诉求

      模拟服务端主动推送消息给客户端,同时展示客户端发送给客户端的消息以及服务端推送给客户的消息。

相关技术

      Springboot(2.7.0)+Websocket+javascript

相关步骤

项目创建

      创建一个Spring Boot项目,并在pom.xml文件中添加Websocket依赖。完整依赖如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>8</java.version>
        <java.encoding>UTF-8</java.encoding>
        <slf4j.version>1.7.30</slf4j.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--  springboot集成websocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- 引入日志管理相关依赖-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-to-slf4j</artifactId>
            <version>2.14.0</version>
        </dependency>
    </dependencies>
    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <target>${java.version}</target>
                        <source>${java.version}</source>
                        <encoding>${java.encoding}</encoding>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.6</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-release-plugin</artifactId>
                    <configuration>
                        <arguments>-Prelease</arguments>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-source-plugin</artifactId>
                    <version>2.1</version>
                    <configuration>
                        <attach>true</attach>
                    </configuration>
                    <executions>
                        <execution>
                            <phase>compile</phase>
                            <goals>
                                <goal>jar</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

编写相关配置

      编写WebSocketConfig和WebSocketHandler配置类,实现对WebSocket的配置。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.*;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
 * @author 
 * @date 2023年01月30日 14:07
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
//    @Override
//    public void configureMessageBroker(MessageBrokerRegistry registry) {
//        registry.enableSimpleBroker("/topic");
//        registry.setApplicationDestinationPrefixes("/app");
//    }
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/websocket").withSockJS();
    }
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
 * handler 
 * @date 2023年01月30日 14:08
 */
@Component
public class WebSocketHandler extends TextWebSocketHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketHandler.class);
    private static final List<WebSocketSession> sessions = new ArrayList<>();
    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        LOGGER.info("Received message: {}", message.getPayload());
        for (WebSocketSession webSocketSession : sessions) {
            try {
                webSocketSession.sendMessage(message);
            } catch (IOException e) {
                LOGGER.error("Error: {}", e.getMessage());
            }
        }
    }
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        sessions.add(session);
    }
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        sessions.remove(session);
    }
}

消息逻辑处理

      编写一个WebSocketServer类,实现服务端主动推送消息的逻辑(此处服务端推送客户端消息的逻辑采用定时任务自动推送,⚠️注意:必须在启动类中加入@EnableScheduling注解,开启定时任务的支持)。

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
/**
 * @author 
 * @date 2023年01月30日 11:19
 */
@ServerEndpoint("/websocket")
@Component
public class WebSocketServer {
    private Session session;
    /*    <sessionId, WebSocketServer> 用于存储websocket连接,key为sessionId  */
    private static ConcurrentHashMap<String, WebSocketServer> webSocketServerConcurrentHashMap = new ConcurrentHashMap();
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketServerConcurrentHashMap.put(session.getId(), this);
        System.out.println("WebSocket opened: " + session.getId());
    }
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("WebSocket message received: " + message);
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis());
        try {
            //发送的消息也返回给当前连接,用于展示
            session.getBasicRemote().sendText(dateStr + "发送消息:" + message);
            //写入DB或者其他存储系统中。。。
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @OnClose
    public void onClose(Session session, CloseReason closeReason) {
        webSocketServerConcurrentHashMap.remove(session.getId());
        System.out.println("WebSocket closed: " + closeReason);
    }
    @OnError
    public void onError(Session session, Throwable throwable) {
        System.out.println("WebSocket error: " + throwable);
    }
    /**
     * 模拟服务端消息推送,5s推送一次(服务端 -> 客户端)
     */
    @Scheduled(fixedRate = 5000)
    public void sendMessageToClient() {
        //没有连接时不做任何事情
        if (CollectionUtils.isEmpty(webSocketServerConcurrentHashMap)){
            return;
        }
        System.out.println("服务端发送消息到客户端");
        String dateStr = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis());
        long number = new Random().nextInt(10000);
        webSocketServerConcurrentHashMap.forEach((k, v) -> {
            try {
                v.session.getBasicRemote().sendText(dateStr + "收到消息:" + number);
                //写入DB或者其他存储系统中。。。
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

启动类中加入@EnableScheduling注解以支持定时任务

73d8c9be8b2a4960a39693770de0ac9a.png

使用JavaScript实现Websocket的前端功能

      在页面中使用JavaScript实现Websocket的前端实现,建立连接,接收并显示消息。在页面关闭时通过JavaScript关闭Websocket连接,以确保连接正常关闭。

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Example</title>
    <script>
        var socket;
        //防止每次刷新页面产生新的WebSocket连接,在此加入判断
        if (socket === undefined || socket.readyState === WebSocket.CLOSED) {
            connect();
        }
        //建立websocket连接
        function connect() {
            var endpoint = "ws://localhost:8080/websocket";
            socket = new WebSocket(endpoint);
            socket.onopen = function (event) {
                console.log("WebSocket opened: " + endpoint);
            }
            socket.onmessage = function (event) {
                console.log("WebSocket message received: " + event.data);
                addMessageToList(event.data);
            }
            socket.onclose = function (event) {
                console.log("WebSocket closed");
            }
        }
        //追加信息到列表中
        function addMessageToList(message) {
            var list = document.getElementById("message-list");
            var item = document.createElement("li");
            item.innerHTML = message;
            list.appendChild(item);
        }
        //模拟触发服务端发送信息
        function send() {
            var message = document.getElementById("message").value;
            socket.send(message);
        }
        //断开连接
        function disconnect() {
            socket.close();
        }
    </script>
</head>
<!--<body οnlοad="connect()">-->
<!--关闭页面时关闭连接-->
<body onbeforeunload="disconnect()">
<input type="text" id="message" placeholder="Type your message here">
<button onclick="send()">Send msg to server</button>
<button onclick="disconnect()">Disconnect</button>
<button onclick="connect()">Open connect</button>
<ul id="message-list"></ul>
</body>
</html>

功能测试

前端效果

image.png

后端日志消息

73d8c9be8b2a4960a39693770de0ac9a.png

相关文章
|
2月前
|
开发框架 前端开发 网络协议
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
461 1
|
22天前
|
JavaScript 前端开发 Java
springboot解决js前端跨域问题,javascript跨域问题解决
本文介绍了如何在Spring Boot项目中编写Filter过滤器以处理跨域问题,并通过一个示例展示了使用JavaScript进行跨域请求的方法。首先,在Spring Boot应用中添加一个实现了`Filter`接口的类,设置响应头允许所有来源的跨域请求。接着,通过一个简单的HTML页面和jQuery发送AJAX请求到指定URL,验证跨域请求是否成功。文中还提供了请求成功的响应数据样例及请求效果截图。
springboot解决js前端跨域问题,javascript跨域问题解决
|
1月前
|
JavaScript 前端开发 中间件
JS服务端技术—Node.js知识点
本文介绍了Node.js中的几个重要模块,包括NPM、Buffer、fs模块、path模块、express模块、http模块以及mysql模块。每部分不仅提供了基础概念,还推荐了相关博文供深入学习。特别强调了express模块的使用,包括响应相关函数、中间件、Router和请求体数据解析等内容。文章还讨论了静态资源无法访问的问题及其解决方案,并总结了一些通用设置。适合Node.js初学者参考学习。
36 1
|
1月前
|
开发框架 JavaScript 前端开发
Node.js日记:客户端和服务端介绍、Node.js介绍
Node.js日记:客户端和服务端介绍、Node.js介绍
|
2月前
|
JavaScript Java PHP
快速对比:Django、Spring Boot、Node.js 和 PHP
快速对比:Django、Spring Boot、Node.js 和 PHP
89 7
|
2月前
|
前端开发 Java C++
RSocket vs WebSocket:Spring Boot 3.3 中的两大实时通信利器
本文介绍了在 Spring Boot 3.3 中使用 RSocket 和 WebSocket 实现实时通信的方法。RSocket 是一种高效的网络通信协议,支持多种通信模式,适用于微服务和流式数据传输。WebSocket 则是一种标准协议,支持全双工通信,适合实时数据更新场景。文章通过一个完整的示例,展示了如何配置项目、实现前后端交互和消息传递,并提供了详细的代码示例。通过这些技术,可以大幅提升系统的响应速度和处理效率。
|
3月前
Nest.js 实战 (十三):实现 SSE 服务端主动向客户端推送消息
这篇文章介绍了在Nest.js应用中使用Server-Sent Events (SSE)的技术。文章首先讨论了在特定业务场景下,为何选择SSE而不是WebSocket作为实时通信系统的实现方式。接着解释了SSE的概念,并展示了如何在Nest.js中实现SSE。文章包含客户端实现的代码示例,并以一个效果演示结束,总结SSE在Nest.js中的应用。
Nest.js 实战 (十三):实现 SSE 服务端主动向客户端推送消息
|
4月前
|
开发框架 网络协议 Java
SpringBoot WebSocket大揭秘:实时通信、高效协作,一文让你彻底解锁!
【8月更文挑战第25天】本文介绍如何在SpringBoot项目中集成WebSocket以实现客户端与服务端的实时通信。首先概述了WebSocket的基本原理及其优势,接着详细阐述了集成步骤:添加依赖、配置WebSocket、定义WebSocket接口及进行测试。通过示例代码展示了整个过程,旨在帮助开发者更好地理解和应用这一技术。
390 1
|
2月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
172 1
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
111 62