使用 Spring Boot3.3 + Thymeleaf + WebSocket 实现服务端数据推送及心跳机制

原创 编程疏影 路条编程 2024年08月31日 07:31 河北

图片

使用 Spring Boot3.3 + Thymeleaf + WebSocket 实现服务端数据推送及心跳机制

在现代 Web 应用中,实时通信成为越来越多场景的核心需求。例如,在线聊天、股票行情推送、游戏服务器的实时状态更新等,都需要在客户端和服务器之间保持实时数据交互。传统的 HTTP 请求/响应模型虽然能够满足部分需求,但由于其基于轮询或长连接的实现方式,往往存在效率低、延迟高等问题。为了解决这些不足,WebSocket 应运而生。

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它能够在客户端和服务器之间建立一条持久的连接通道,允许双方随时推送数据,而不需要像传统 HTTP 那样频繁地建立和关闭连接。WebSocket 在降低通信延迟、减少网络资源消耗等方面具有显著优势,因此在高实时性、高并发的应用场景中被广泛采用。

然而,WebSocket 的优势也带来了新的挑战。由于 WebSocket 连接的长期性,如何保持连接的稳定性,尤其是在网络不稳定或长时间未进行数据传输的情况下,是一个需要重点关注的问题。如果不进行适当的处理,WebSocket 连接可能会因为各种原因被中断,导致数据无法及时推送到客户端,影响用户体验。因此,在实际开发中,我们通常需要引入“心跳机制”,通过定期发送心跳消息来检测连接的状态,并及时采取措施恢复中断的连接。

本文将通过一个基于 Spring Boot 和 Thymeleaf 的示例项目,深入探讨如何实现 WebSocket 服务端数据推送及心跳机制。我们将展示如何在服务端处理 WebSocket 连接的建立、消息的发送与接收、以及心跳机制的实现。同时,还将结合前端的实现,演示如何通过 JavaScript 与 WebSocket 进行交互,从而实现一个完整的实时通信系统。本篇文章将为大家提供一个全面、深入的指导,帮助在实际项目中高效实现 WebSocket 通信及其稳定性保障。

运行效果:

图片

若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。

项目配置
pom.xml 配置

首先,我们需要在 Spring Boot 项目的 pom.xml 文件中引入相关依赖,包括 WebSocket 支持、Thymeleaf 模板引擎以及前端相关的依赖:

<?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>3.3.3</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.icoderoad</groupId>
	<artifactId>heartbeat</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>heartbeat</name>
	<description>Demo project for Spring Boot</description>
	
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		
		<!-- Spring Boot Starter Web -->
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-web</artifactId>
	    </dependency>
	
	    <!-- Spring Boot WebSocket -->
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-websocket</artifactId>
	    </dependency>
	
	    <!-- Thymeleaf -->
	    <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-thymeleaf</artifactId>
	    </dependency>
	
	    <!-- Lombok (用于简化代码) -->
	    <dependency>
	        <groupId>org.projectlombok</groupId>
	        <artifactId>lombok</artifactId>
	        <scope>provided</scope>
	    </dependency>

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

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
2.2 application.yml 配置

在 application.yml 中,我们可以配置 WebSocket 的相关属性以及其他必要的配置项:

server:
  port: 8080

spring:
  thymeleaf:
    cache: false
    mode: HTML
    encoding: UTF-8
    servlet:
      content-type: text/html

logging:
  level:
    org.springframework.web.socket: DEBUG
WebSocket 服务端实现

应用启动类 HeartbeatApplication:

package com.icoderoad.heartbeat;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling  // 启用定时任务
public class HeartbeatApplication {

	public static void main(String[] args) {
		SpringApplication.run(HeartbeatApplication.class, args);
	}

}
配置 WebSocket 处理器

我们首先定义一个 WebSocket 处理器,用于处理客户端的连接、消息以及关闭事件:

package com.icoderoad.heartbeat.handler;

import java.util.HashSet;
import java.util.Set;

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;

@Component
public class WebSocketHandler extends TextWebSocketHandler {

    // 存储当前活跃的会话
    private static final Set<WebSocketSession> sessions = new HashSet<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 连接建立时添加到 sessions 集合
        sessions.add(session);
        System.out.println("连接建立:" + session.getId());
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 接收到消息时处理逻辑
        System.out.println("接收到消息:" + message.getPayload());
        session.sendMessage(new TextMessage("服务端响应:" + message.getPayload()));
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        // 连接关闭时从 sessions 集合移除
        sessions.remove(session);
        System.out.println("连接关闭:" + session.getId());
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        // 处理连接错误
        System.err.println("连接出错:" + exception.getMessage());
        sessions.remove(session);
    }

    // 发送心跳消息
    public void sendHeartbeat() {
        for (WebSocketSession session : sessions) {
            if (session.isOpen()) {
                try {
                    session.sendMessage(new TextMessage("heartbeat"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
WebSocket 配置类

接下来,我们配置 WebSocket 的映射路径并注册处理器:

package com.icoderoad.heartbeat.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

import com.icoderoad.heartbeat.handler.WebSocketHandler;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    private final WebSocketHandler myWebSocketHandler;

    public WebSocketConfig(WebSocketHandler myWebSocketHandler) {
        this.myWebSocketHandler = myWebSocketHandler;
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册 WebSocket 处理器并映射到 /ws 路径
        registry.addHandler(myWebSocketHandler, "/ws").setAllowedOrigins("*");
    }
}
心跳机制的实现

为了保持 WebSocket 连接的活跃,我们可以通过定时发送心跳消息来检测连接状态。可以使用 Spring 的 @Scheduled 注解实现定时任务:

package com.icoderoad.heartbeat.scheduler;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.icoderoad.heartbeat.handler.WebSocketHandler;

@Component
public class HeartbeatScheduler {

    private final WebSocketHandler myWebSocketHandler;

    public HeartbeatScheduler(WebSocketHandler myWebSocketHandler) {
        this.myWebSocketHandler = myWebSocketHandler;
    }

    @Scheduled(fixedRate = 30000)  // 每隔30秒发送一次心跳
    public void sendHeartbeat() {
        myWebSocketHandler.sendHeartbeat();
    }
}

视图控制类:

package com.icoderoad.heartbeat.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {
	
	@GetMapping("/")
    public String index(Model model) {
        return "index";
    }

}
前端实现

前端使用 Thymeleaf 模板引擎结合 Bootstrap 进行简单的界面展示,并通过 JavaScript 实现与 WebSocket 的通信。

Thymeleaf 模板

在 src/main/resources/templates 目录下创建 index.html 模板文件作为前端页面。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>WebSocket 示例</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
    <h1>WebSocket 示例</h1>
    <button id="connectBtn" class="btn btn-primary">连接</button>
    <button id="disconnectBtn" class="btn btn-danger">断开</button>
    <div class="mt-3">
        <input type="text" id="messageInput" class="form-control" placeholder="输入消息">
        <button id="sendBtn" class="btn btn-success mt-2">发送消息</button>
    </div>
    <div class="mt-3">
        <h3>接收消息:</h3>
        <div id="messages"></div>
    </div>
</div>

<script>
    let websocket;

    document.getElementById('connectBtn').addEventListener('click', function () {
        websocket = new WebSocket("ws://localhost:8080/ws");

        websocket.onopen = function () {
            console.log("WebSocket 连接成功");
        };

        websocket.onmessage = function (event) {
            console.log("收到消息:" + event.data);
            document.getElementById('messages').innerHTML += '<p>' + event.data + '</p>';
        };

        websocket.onclose = function () {
            console.log("WebSocket 连接关闭");
        };
    });

    document.getElementById('disconnectBtn').addEventListener('click', function () {
        if (websocket) {
            websocket.close();
        }
    });

    document.getElementById('sendBtn').addEventListener('click', function () {
        const message = document.getElementById('messageInput').value;
        if (websocket && websocket.readyState === WebSocket.OPEN) {
            websocket.send(message);
        }
    });
</script>
</body>
</html>
总结

本文详细介绍了如何在 Spring Boot 项目中使用 WebSocket 实现服务端数据推送和心跳机制。通过结合 Thymeleaf 模板引擎和 Bootstrap,我们实现了一个简单的前端页面,并展示了 WebSocket 的基本用法。WebSocket 的心跳机制有助于保持连接的稳定性,在实际项目中可以根据需要进一步优化心跳策略和消息处理逻辑。

今天就讲到这里,如果有问题需要咨询,大家可以直接留言或扫下方二维码来知识星球

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您可以参考以下步骤来实现: 1. 创建Spring Boot项目,并添加Mybatis、Thymeleaf等依赖。 2. 创建实体类Player,包含球员的基本信息,如姓名、年龄、球队等属性。 3. 创建Mapper接口PlayerMapper,定义按球队名查询球员信息的方法。 4. 创建Mapper.xml文件,实现PlayerMapper接口中定义的方法,并编写SQL语句。 5. 创建Controller类PlayerController,处理前端请求,并调用PlayerMapper中定义的方法获取球员信息。 6. 创建Thymeleaf模板,展示查询结果。 具体实现过程可以参考以下示例代码: 1. 实体类Player ```java public class Player { private String name; private int age; private String team; // 省略getter和setter方法 } ``` 2. Mapper接口PlayerMapper ```java public interface PlayerMapper { List<Player> findByTeam(String team); } ``` 3. Mapper.xml文件playerMapper.xml ```xml <mapper namespace="com.example.mapper.PlayerMapper"> <select id="findByTeam" resultMap="playerResultMap"> select name, age, team from player where team = #{team} </select> <resultMap id="playerResultMap" type="com.example.entity.Player"> <id property="name" column="name"/> <result property="age" column="age"/> <result property="team" column="team"/> </resultMap> </mapper> ``` 4. Controller类PlayerController ```java @Controller public class PlayerController { @Autowired private PlayerMapper playerMapper; @GetMapping("/players") public String findByTeam(@RequestParam("team") String team, Model model) { List<Player> players = playerMapper.findByTeam(team); model.addAttribute("players", players); return "playerList"; } } ``` 5. Thymeleaf模板playerList.html ```html <!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>球员列表</title> </head> <body> <h1 th:text="'属于' + ${players[0].team} + '的球员列表'"></h1> <table> <tr> <th>姓名</th> <th>年龄</th> </tr> <tr th:each="player : ${players}"> <td th:text="${player.name}"></td> <td th:text="${player.age}"></td> </tr> </table> </body> </html> ``` 以上就是按球队名查询球员信息的实现步骤。您可以根据自己的实际需求进行修改和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值