Java:SpringBoot整合WebSocket实现服务端向客户端推送消息

简介: Java:SpringBoot整合WebSocket实现服务端向客户端推送消息

SpringBoot WebSocket

思路:

后端通过websocket向前端推送消息,前端统一使用http协议接口向后端发送数据

本文仅放一部分重要的代码,完整代码可参看github仓库

websocket 前端测试 :http://www.easyswoole.com/wstool.html

image.png

依赖

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

项目目录

$ tree 
.
├── README.md
├── demo.iml
├── pom.xml
└── src
    ├── main
        ├── java
        │   └── com
        │       └── example
        │           └── demo
        │               ├── Application.java
        │               ├── config
        │               │   ├── WebMvcConfig.java
        │               │   └── WebSocketConfig.java
        │               ├── consts
        │               │   └── MessageTypeConst.java
        │               ├── controller
        │               │   ├── IndexController.java
        │               │   └── MessageController.java
        │               ├── dto
        │               │   └── MessageDto.java
        │               ├── service
        │               │   ├── MessageService.java
        │               │   └── impl
        │               │       └── MessageServiceImpl.java
        │               └── webscoket
        │                   └── WebSocketServer.java
        └── resources
            ├── application.properties
            ├── static
            │   ├── js
            │   │   ├── index.js
            │   │   └── utils.js
            │   └── libs
            │       └── axios
            │           └── 1.3.2
            │               └── axios.min.js
            └── templates
                └── websocket.html

完整依赖

<?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.7</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>demo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>demo</name>
  <description>Demo project for Spring Boot</description>
  <properties>
    <java.version>1.8</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.68</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </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>
        <configuration>
          <excludes>
            <exclude>
              <groupId>org.projectlombok</groupId>
              <artifactId>lombok</artifactId>
            </exclude>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

配置

package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
 * 启用WebSocket
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

WebSocketServer.java

package com.example.demo.webscoket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
 * WebSocket服务类
 */
@Component
@Slf4j
@ServerEndpoint("/ws/{userId}")
public class WebSocketServer {
    /**
     * 心跳消息
     */
    private final static String PING = "ping";
    private final static String PONG = "pong";
    /**
     * 存放每个客户端对应的 WebSocketServer 对象
     */
    private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;
    /**
     * 接收 userId
     */
    private String userId = "";
    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        // 加入
        webSocketMap.put(userId, this);
        log.info("新用户上线:" + userId + ", 当前在线人数为:" + getOnlineCount());
    }
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if (!webSocketMap.containsKey(userId)) {
            return;
        }
        webSocketMap.remove(userId);
        log.info("用户下线:" + userId + ", 当前在线人数为:" + getOnlineCount());
    }
    /**
     * 收到客户端消息后调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("用户消息:" + userId + ",报文:" + message);
        if (PING.equals(message)) {
            try {
                this.sendMessage(PONG);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }
    /**
     * 实现服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }
    /**
     * 群发消息
     */
    public static void sendMessageToAll(String message) throws IOException {
        for (Map.Entry<String, WebSocketServer> entry : webSocketMap.entrySet()) {
            WebSocketServer webSocketServer = entry.getValue();
            webSocketServer.sendMessage(message);
        }
    }
    /**
     * 单发消息
     */
    public static void sendMessageToUser(String toUserId, String message) throws IOException {
        if (webSocketMap.containsKey(toUserId)) {
            webSocketMap.get(toUserId).sendMessage(message);
        } else {
            log.error("请求的 userId:" + toUserId + "不在该服务器上");
        }
    }
    /**
     * 获取在线人数
     */
    public static int getOnlineCount() {
        return webSocketMap.size();
    }
    /**
     * 用户是否在线
     */
    public static Boolean isOnline(String userId) {
        return webSocketMap.containsKey(userId);
    }
    /**
     * 在线用户
     */
    public static Set<String> getOnlineUsers() {
        Set<String> set = new HashSet<>();
        Enumeration<String> enumeration = webSocketMap.keys();
        while (enumeration.hasMoreElements()) {
            set.add(enumeration.nextElement());
        }
        return set;
    }
}

前端页面 websocket.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Java后端WebSocket实现</title>
</head>
<body>
    <h2>Welcome WebSocket</h2>
    <div>
        <input id="textInput" type="text" placeholder="message"/>
        <button id="sendMessageButton">发送消息</button>
        <button id="closeConnectButton">关闭连接</button>
        <button id="sendPingButton">发送ping</button>
    </div>
    <hr/>
    <div id="message"></div>
    <script type="text/javascript" src="/static/libs/axios/1.3.2/axios.min.js"></script>
    <script type="text/javascript" src="/static/js/utils.js"></script>
    <script type="text/javascript" src="/static/js/index.js"></script>
</body>
</html>

前端逻辑 index.js

/**
 * 程序入口
 */
// 心跳消息
var PING = "ping";
var PONG = "pong";
// 获取一个用户id
var uuid = utils.getUUID();
var url = "ws://127.0.0.1:8080/ws/" + uuid;
//判断当前浏览器是否支持WebSocket
var websocket = null;
function initWebsocket() {
  if ("WebSocket" in window) {
    websocket = new WebSocket(url);
  } else {
    alert("当前浏览器 Not support websocket");
  }
  //连接成功建立的回调方法
  websocket.onopen = function () {
    setMessageInnerHTML("WebSocket连接成功");
  };
  //接收到消息的回调方法
  websocket.onmessage = function (event) {
    var data = event.data;
    // 忽略心跳消息
    if (data === PONG) {
      return;
    }
    setMessageInnerHTML(JSON.parse(event.data).text);
  };
  //连接关闭的回调方法
  websocket.onclose = function () {
    setMessageInnerHTML("WebSocket连接关闭");
  };
  //连接发生错误的回调方法
  websocket.onerror = function (e) {
    console.log(e);
    setMessageInnerHTML("WebSocket连接发生错误");
  };
}
//关闭WebSocket连接
function closeWebSocket() {
  websocket.close();
}
// 监听窗口关闭事件,当窗口关闭时
// 主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
  closeWebSocket();
};
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
  document.getElementById("message").innerHTML += innerHTML + "<br/>";
}
//发送消息
function sendMessage() {
  var text = textInput.value;
  if (!text) {
    return;
  }
  // 统一发送json格式,便于扩展
  var data = {
    type: "text",
    text: text,
  };
  // websocket.send(JSON.stringify(data));
  axios.post("/api/sendMessageToAllUser", data);
  // setMessageInnerHTML(data.text);
  textInput.value = "";
}
// 事件监听
function bindEventListener() {
  var textInput = document.querySelector("#textInput");
  var sendMessageButton = document.querySelector("#sendMessageButton");
  var closeConnectButton = document.querySelector("#closeConnectButton");
  var sendPingButton = document.querySelector("#sendPingButton");
  textInput.addEventListener("keypress", function (e) {
    if (e.key === "Enter") {
      sendMessage();
    }
  });
  sendMessageButton.addEventListener("click", function (e) {
    sendMessage();
  });
  sendPingButton.addEventListener("click", function (e) {
    websocket.send(PING);
  });
  closeConnectButton.addEventListener("click", function (e) {
    closeWebSocket();
  });
}
/**
 * 入口
 */
(function () {
  initWebsocket();
  bindEventListener();
})();

参考


相关文章
|
22天前
|
开发框架 前端开发 网络协议
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
233 1
|
20天前
|
JavaScript 前端开发 测试技术
前端全栈之路Deno篇(五):如何快速创建 WebSocket 服务端应用 + 客户端应用 - 可能是2025最佳的Websocket全栈实时应用框架
本文介绍了如何使用Deno 2.0快速构建WebSocket全栈应用,包括服务端和客户端的创建。通过一个简单的代码示例,展示了Deno在WebSocket实现中的便捷与强大,无需额外依赖,即可轻松搭建具备基本功能的WebSocket应用。Deno 2.0被认为是最佳的WebSocket全栈应用JS运行时,适合全栈开发者学习和使用。
|
27天前
|
前端开发 Java C++
RSocket vs WebSocket:Spring Boot 3.3 中的两大实时通信利器
本文介绍了在 Spring Boot 3.3 中使用 RSocket 和 WebSocket 实现实时通信的方法。RSocket 是一种高效的网络通信协议,支持多种通信模式,适用于微服务和流式数据传输。WebSocket 则是一种标准协议,支持全双工通信,适合实时数据更新场景。文章通过一个完整的示例,展示了如何配置项目、实现前后端交互和消息传递,并提供了详细的代码示例。通过这些技术,可以大幅提升系统的响应速度和处理效率。
|
1月前
|
Web App开发 消息中间件 监控
使用 Java + WebSocket 实现简单实时双人协同 pk 答题:技术干货分享
【10月更文挑战第4天】在现代互联网应用中,实时互动已经成为提升用户体验的重要一环。特别是在在线教育、游戏竞技等领域,实时协同功能显得尤为重要。今天,我们将围绕“使用 Java + WebSocket 实现简单实时双人协同 pk 答题”这一主题,分享相关技术干货,帮助你在工作和学习中更好地理解和应用这一技术。
58 2
|
2月前
|
JSON NoSQL Java
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
这篇文章介绍了在Java中使用Redis客户端的几种方法,包括Jedis、SpringDataRedis和SpringBoot整合Redis的操作。文章详细解释了Jedis的基本使用步骤,Jedis连接池的创建和使用,以及在SpringBoot项目中如何配置和使用RedisTemplate和StringRedisTemplate。此外,还探讨了RedisTemplate序列化的两种实践方案,包括默认的JDK序列化和自定义的JSON序列化,以及StringRedisTemplate的使用,它要求键和值都必须是String类型。
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
|
3月前
|
开发框架 网络协议 Java
SpringBoot WebSocket大揭秘:实时通信、高效协作,一文让你彻底解锁!
【8月更文挑战第25天】本文介绍如何在SpringBoot项目中集成WebSocket以实现客户端与服务端的实时通信。首先概述了WebSocket的基本原理及其优势,接着详细阐述了集成步骤:添加依赖、配置WebSocket、定义WebSocket接口及进行测试。通过示例代码展示了整个过程,旨在帮助开发者更好地理解和应用这一技术。
253 1
|
3月前
|
小程序 Java API
springboot 微信小程序整合websocket,实现发送提醒消息
springboot 微信小程序整合websocket,实现发送提醒消息
|
3月前
|
JavaScript 前端开发 网络协议
WebSocket在Java Spring Boot+Vue框架中实现消息推送功能
在现代Web应用中,实时消息提醒是一项非常重要的功能,能够极大地提升用户体验。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为实现实时消息提醒提供了高效且低延迟的解决方案。本文将详细介绍如何在Java Spring Boot后端和Vue前端框架中利用WebSocket实现消息提醒功能。
153 0
|
安全 Java 数据库连接
SpringBoot 实战 (十五) | 服务端参数校验之一
估计很多朋友都认为参数校验是客户端的职责,不关服务端的事。其实这是错误的,学过 Web 安全的都知道,客户端的验证只是第一道关卡。它的参数验证并不是安全的,一旦被有心人抓到可乘之机,他就可以有各种方法来摸拟系统的 Http 请求,访问数据库的关键数据。轻则导致服务器宕机,重则泄露数据。所以,这时就需要设置第二道关卡,服务端验证了。
SpringBoot 实战 (十五) | 服务端参数校验之一
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
137 1