Spring WebSocket实现实时通信的详细教程

简介

WebSocket 是基于TCP/IP协议,独立于HTTP协议的通信协议。WebSocket 连接允许客户端和服务器之间的全双工通信,以便任何一方都可以通过已建立的连接将数据推送到另一方。

我们常用的HTTP是客户端通过「请求-响应」的方式与服务器建立通信的,必须是客户端主动触发的行为,服务端只是做好接口被动等待请求。而在某些场景下的动作,是需要服务端主动触发的,比如向客户端发送消息、实时通讯、远程控制等。客户端是不知道这些动作几时触发的,假如用HTTP的方式,那么设备端需要不断轮询服务端,这样的方式对服务器压力太大,同时产生很多无效请求,且具有延迟性。于是才采用可以建立双向通讯的长连接协议。通过握手建立连接后,服务端可以实时发送数据与指令到设备端,服务器压力小。

Spring WebSocket是Spring框架的一部分,提供了在Web应用程序中实现实时双向通信的能力。本教程将引导你通过一个简单的例子,演示如何使用Spring WebSocket建立一个实时通信应用。

准备工作

确保你的项目中已经引入了Spring框架的WebSocket模块。你可以通过Maven添加以下依赖:

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

创建WebSocket配置类(实现WebSocketConfigurer接口)

首先,创建一个配置类,用于配置WebSocket的相关设置。

package com.ci.erp.human.config;

import com.ci.erp.human.handler.WebSocketHandler;
import com.ci.erp.human.interceptor.WebSocketHandleInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 *
 * Websocket配置类
 *
 * @author lucky_fd
 * @since 2024-01-17
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册websocket处理器和拦截器
        registry.addHandler(webSocketHandler(), "/websocket/server")
                .addInterceptors(webSocketHandleInterceptor()).setAllowedOrigins("*");
        registry.addHandler(webSocketHandler(), "/sockjs/server").setAllowedOrigins("*")
                .addInterceptors(webSocketHandleInterceptor()).withSockJS();
    }

    @Bean
    public WebSocketHandler webSocketHandler() {
        return new WebSocketHandler();
    }

    @Bean
    public WebSocketHandleInterceptor webSocketHandleInterceptor() {
        return new WebSocketHandleInterceptor();
    }
}

上面的配置类使用@EnableWebSocket注解启用WebSocket,并通过registerWebSocketHandlers方法注册WebSocket处理器。

  • registerWebSocketHandlers:这个方法是向spring容器注册一个handler处理器及对应映射地址,可以理解成MVC的Handler(控制器方法),websocket客户端通过请求的url查找处理器进行处理

  • addInterceptors:拦截器,当建立websocket连接的时候,我们可以通过继承spring的HttpSessionHandshakeInterceptor来做一些事情。

  • setAllowedOrigins:跨域设置,*表示所有域名都可以,不限制, 域包括ip:port, 指定*可以是任意的域名,不加的话默认localhost+本服务端口

  • withSockJS: 这个是应对浏览器不支持websocket协议的时候降级为轮询的处理。

创建WebSocket消息处理器(实现TextWebSocketHandler 接口)

接下来,创建一个消息处理器,处理客户端发送的消息。

package com.ci.erp.human.handler;

import cn.hutool.core.util.ObjectUtil;
import com.ci.erp.common.core.utils.JsonUtils;
import com.ci.erp.human.domain.thirdVo.YYHeartbeat;
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.HashMap;
import java.util.Map;

/**
 *
 * websocket处理类
 * 实现WebSocketHandler接口
 *
 * - websocket建立连接后执行afterConnectionEstablished回调接口
 * - websocket关闭连接后执行afterConnectionClosed回调接口
 * - websocket接收客户端消息执行handleTextMessage接口
 * - websocket传输异常时执行handleTransportError接口
 *
 * @author lucky_fd
 * @since 2024-01-17
 */

public class WebSocketHandler extends TextWebSocketHandler {

    /**
     * 存储websocket客户端连接
     * */
    private static final Map<String, WebSocketSession> connections = new HashMap<>();

    /**
     * 建立连接后触发
     * */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("成功建立websocket连接");
        // 建立连接后将连接以键值对方式存储,便于后期向客户端发送消息
        // 以客户端连接的唯一标识为key,可以通过客户端发送唯一标识
        connections.put(session.getRemoteAddress().getHostName(), session);
        System.out.println("当前客户端连接数:" + connections.size());
    }

    /**
     * 接收消息
     * */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("收到消息: " + message.getPayload());
		
		// 收到客户端请求消息后进行相应业务处理,返回结果
        this.sendMessage(session.getRemoteAddress().getHostName(),new TextMessage("收到消息: " + message.getPayload()));
    }

    /**
     * 传输异常处理
     * */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        super.handleTransportError(session, exception);
    }

    /**
     * 关闭连接时触发
     * */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("触发关闭websocket连接");
        // 移除连接
        connections.remove(session.getRemoteAddress().getHostName());
    }

    @Override
    public boolean supportsPartialMessages() {
        return super.supportsPartialMessages();
    }

    /**
     * 向连接的客户端发送消息
     *
     * @author lucky_fd
     * @param clientId 客户端标识
     * @param message 消息体
     **/
    public void sendMessage(String clientId, TextMessage message) {
        for (String client : connections.keySet()) {
            if (client.equals(clientId)) {
                try {
                    WebSocketSession session = connections.get(client);
                    // 判断连接是否正常
                    if (session.isOpen()) {
                        session.sendMessage(message);
                    }
                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
                break;
            }
        }
    }
}

通过消息处理器,在开发中我们就可以实现向指定客户端或所有客户端发送消息,实现相应业务功能。

创建拦截器

拦截器会在握手时触发,可以用来进行权限验证

package com.ci.erp.human.interceptor;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;

import java.util.Map;

/**
 *
 * Websocket拦截器类
 *
 * @author lucky_fd
 * @since 2024-01-17
 */

public class WebSocketHandleInterceptor extends HttpSessionHandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        System.out.println("拦截器前置触发");
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
        System.out.println("拦截器后置触发");
        super.afterHandshake(request, response, wsHandler, ex);
    }
}

创建前端页面客户端

最后,创建一个简单的HTML页面,用于接收用户输入并显示实时聊天信息。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Spring WebSocket Chat</title>
    <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
    <script src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
</head>
<body>

请输入:<input type="text" id="message" placeholder="Type your message">
<button onclick="sendMessage()">Send</button>
<button onclick="websocketClose()">关闭连接</button>
<div id="chat"></div>

<script>
    var socket = null;
    if ('WebSocket' in window) {
    	// 后端服务port为22900
        socket = new WebSocket("ws://localhost:22900/websocket/server");
    } else if ('MozWebSocket' in window) {
        socket = new MozWebSocket("ws://localhost:22900/websocket/server");
    } else {
        socket = new SockJS("http://localhost:22900/sockjs/server");
    }

    // 接收消息触发
    socket.onmessage = function (event) {
        showMessage(event.data);
    };
    // 创建连接触发
    socket.onopen = function (event) {
        console.log(event.type);
    };
    // 连接异常触发
    socket.onerror = function (event) {
        console.log(event)
    };
    // 关闭连接触发
    socket.onclose = function (closeEvent) {
        console.log(closeEvent.reason);
    };

    //发送消息
    function sendMessage() {
        if (socket.readyState === socket.OPEN) {
            var message = document.getElementById('message').value;
            socket.send(message);
            console.log("发送成功!");
        } else {
            console.log("连接失败!");
        }

    }

    function showMessage(message) {
        document.getElementById('chat').innerHTML += '<p>' + message + '</p>';
    }

    function websocketClose() {
        socket.close();
        console.log("连接关闭");
    }

    window.close = function () {
        socket.onclose();
    };

</script>

</body>
</html>

这个页面使用了WebSocket对象来建立连接,并通过onmessage监听收到的消息。通过输入框发送消息,将会在页面上显示。

测试结果:

后端日志:

在这里插入图片描述

前端界面:

在这里插入图片描述

Java客户端

添加依赖

<dependency>
      <groupId>org.java-websocket</groupId>
      <artifactId>Java-WebSocket</artifactId>
      <version>1.4.0</version>
</dependency>

创建客户端类(继承WebsocketClient)

package com.river.websocket;
 
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
 
import java.net.URI;
import java.net.URISyntaxException;
 
public class MyWebSocketClient extends WebSocketClient {
 
    MyWebSocketClient(String url) throws URISyntaxException {
        super(new URI(url));
    }
 	// 建立连接
    @Override
    public void onOpen(ServerHandshake shake) {
        System.out.println(shake.getHttpStatusMessage());
    }
 	// 接收消息
    @Override
    public void onMessage(String paramString) {
        System.out.println(paramString);
    }
 	// 关闭连接
    @Override
    public void onClose(int paramInt, String paramString, boolean paramBoolean) {
        System.out.println("关闭");
    }
 	// 连接异常
    @Override
    public void onError(Exception e) {
        System.out.println("发生错误");
    }
}

测试websocket

package com.river.websocket;
 
import org.java_websocket.enums.ReadyState;
 
import java.net.URISyntaxException;
 
/**
 * @author lucky_fd
 * @date 2024-1-17
 */
public class Client {
    public static void main(String[] args) throws URISyntaxException, InterruptedException {
        MyWebSocketClient client = new MyWebSocketClient("ws://localhost:22900/websocket/server");
        client.connect();
        while (client.getReadyState() != ReadyState.OPEN) {
            System.out.println("连接状态:" + client.getReadyState());
            Thread.sleep(100);
        }
        client.send("测试数据!");
        client.close();
    }
}

参考链接:

  • 通过注解方式实现websocket:springboot集成websocket持久连接(权限过滤+拦截)
  • spring websocket实现前后端通信

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/331444.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电力市场知识及市场出清电价(market clearing price)程序分享!

​Main-导览 一、电力市场概述 2000以前&#xff0c;国内并不存在电力市场&#xff0c;而是叫计划电力经济。发电侧为卖方&#xff0c;核算发电成本和利润上报国家&#xff0c;审核通过后就是上网电价。用户侧为买方&#xff0c;被动执行国家制定的分时电价。计划电力经济的优…

奇异值分解(SVD)【详细推导证明】

机器学习笔记 机器学习系列笔记&#xff0c;主要参考李航的《机器学习方法》&#xff0c;见参考资料。 第一章 机器学习简介 第二章 感知机 第三章 支持向量机 第四章 朴素贝叶斯分类器 第五章 Logistic回归 第六章 线性回归和岭回归 第七章 多层感知机与反向传播【Python实例…

使用opencv把视频转换为灰色并且逐帧率转换为图片

功能介绍 使用opencv库把视频转换为灰色&#xff0c;并且逐帧率保存为图片到本地 启动结果 整体代码 import cv2 import osvc cv2.VideoCapture(test.mp4)if vc.isOpened():open, frame vc.read() else:open Falseos.makedirs("grayAll", exist_okTrue) i 0 wh…

【Docker】Linux中使用Docker安装Nginx部署前后端分离项目应用

目录 一、概述 1. Nginx介绍 2. Nginx优势 3. Nginx的工作原理 二、容器创建 1. Mysql容器 2. Tomcat容器 3. Nginx容器 每篇一获 一、概述 1. Nginx介绍 Nginx&#xff08;发音为 "engine x"&#xff09;是一个开源的、高性能的 HTTP 服务器和反向代理服务…

项目开发中安全问题以及解决办法——客户请求需要校验

Slf4j RequestMapping("trustclientdata") Controller public class TrustClientDataController {//所有支持的国家private HashMap<Integer, Country> allCountries new HashMap<>();public TrustClientDataController() {allCountries.put(1, new Cou…

【音视频】基于NGINX如何播放rtmp视频流

背景 现阶段直播越来越流行&#xff0c;直播技术发展也越来越快。Webrtc、rtmp、rtsp是比较火热的技术&#xff0c;而且应用也比较广泛。本文通过实践来展开介绍关于rtmp如何播放。 概要 本文重点介绍基于NGINX如何播放rtmp视频流 正文 1、构造rtsp视频流 可以参考上一篇…

开发「定位线上问题」小工具总结

文章目录 1. 写在最前面1.1 背景1.2 思路 2. 如何快速解决问题2.1 分析问题2.2 补救问题2.2.1 思路2.2.2 实现 3. 碎碎念 1. 写在最前面 1.1 背景 同事给处理各种线上问题以及处理紧急要交付的需求版本的我&#xff0c;紧急插入了一个线上的问题&#xff1a; 问题说明&#…

甜蜜而简洁——深入了解Pytest插件pytest-sugar

在日常的软件开发中,测试是确保代码质量的关键步骤之一。然而,对于测试报告的生成和测试结果的可读性,一直以来都是开发者关注的焦点。Pytest插件 pytest-sugar 以其清晰而美观的输出,为我们提供了一种愉悦的测试体验。本文将深入介绍 pytest-sugar 插件的基本用法和实际案…

界面设计与品牌一致性

活动是电子商务行业最常见的运营手段之一&#xff0c;将借助各种节日不断推出促销活动&#xff1b;例如&#xff0c;从1月的元旦到12月的圣诞节&#xff0c;让用户关注节日的仪式&#xff0c;通过各种折扣促进用户订单&#xff0c;提高订单率。 让我们来思考一下活动页面是如何…

C++设计模式(李建忠)笔记4(完结)

C设计模式&#xff08;李建忠&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。 参考链接 Youtube: C设计模式 Gtihub源码与PPT&#xff1a;https://github.com/ZachL1/Bilibili-plus 豆瓣: 设计模式–可复用面向对象软件的基础 总结23种设计模式…

Pymol快速做出Surface-cartoon图详细步骤

在使用Pymol时&#xff0c;每当想对某个部分进行单独操作时&#xff0c;可以通过选中以后&#xff0c;复制该对象并重命名的方式。例如以2j1x.pdb为例&#xff0c;原始导入文件后如下&#xff1a; 移除溶剂 PyMOL>remove solvent 对象选择 右下方的selecting表示选择的类…

Linux--进程控制

进程终止 进程终止是指一个正在运行的进程结束其执行并释放占用的系统资源的过程。进程可以通过以下几种方式终止&#xff1a; 正常终止&#xff1a;进程完成了它的任务&#xff0c;或者遇到了终止条件&#xff0c;例如调用了exit()函数或主函数执行完毕。 异常终止&#xff1…

Opencv小项目——手势数字刷TIKTOK

​ 写在前面&#xff1a; 很久没更新了&#xff0c;之前的实习的记录也算是烂尾了&#xff0c;但是好在自己的实习记录还是有的&#xff0c;最近也忙碌了很多&#xff0c;终于放假了&#xff0c;今天下午正好没事&#xff0c;闲来无事就随便做个小玩意吧。 思来想去&#xff…

Docker registry镜像仓库,私有仓库及harbor管理详解

目录 registry镜像仓库概述 Docker 镜像仓库&#xff08;Docker Registry&#xff09;&#xff1a; registry 容器&#xff1a; 私有仓库概述 搭建本地私有仓库示例 Harbor概述 harbor架构 详解构成 Harbor由容器构成 Harbor部署示例 环境准备 部署Docker-Compose服…

多路开关状态指示

1&#xff0e;  实验任务 AT89S51单片机的P1.0&#xff0d;P1.3接四个发光二极管L1&#xff0d;L4&#xff0c;P1.4&#xff0d;P1.7接了四个开关K1&#xff0d;K4&#xff0c;编程将开关的状态反映到发光二极管上。&#xff08;开关闭合&#xff0c;对应的灯亮&#xff0c;开…

Docker安装Nginx并部署MySQL容器构建

一.MySQL容器的构建 1.创建MySQL根目录及配置文件夹&data文件夹 mkdir -p mysql/{conf,data} 2.上传配置文件 将配置文件上传到conf文件夹&#xff08;数据库配置文件已放到置顶资源中&#xff09; 3.命令构建MySQL容器 /soft/mysql/conf/my.cnf:/etc/my.cnf目录为我们…

electron+vite+vue3 快速入门教程

文章目录 前言一、electron是什么&#xff1f;二、electron 进程模型1.主进程2.渲染进程3.预加载脚本4.进程通信4.1 sendon&#xff08;单向&#xff09;4.2 invokehandle (双向)4.3 主进程向渲染进程发送事件 三、窗口创建与应用事件四、技术栈和构建工具五、electron-vite安装…

网络编程【1】

【 1 】什么是网络编程 网络编程是指通过计算机网络进行数据交换和通信的编程过程。它涉及到使用网络协议和通信接口&#xff0c;使不同计算机之间能够进行数据传输和通信。 总结&#xff1a; 网络编程的研究前提就是基于互联网 网络编程就是基于互联网写代码 【 2 】为什么…

当前vscode环境下 多进程多线程运行情况探究

我的代码 其中在“打开图片时”、“进入子进程之前”、“子进程join前”、“进入子进程区域后”&#xff0c;“子进程join后”、“进入子线程区域后”分别打印了进程线程的编号和数量。 # -*- coding: utf-8 -*-# Form implementation generated from reading ui file test2.…

【总结】Linux命令中文帮助手册

1. 为什么要总结Linux命令中文帮助手册 Linux 官方并不提供中文的 help、man 帮助手册。网络上已有的前人翻译过的中文手册版本比较老&#xff0c;且翻译存在误差。从记忆角度来看&#xff0c;Linux 很多命令都不一定记得住详细的用法&#xff0c;易遗忘&#xff0c;缺少经验总…