【Java】Spring Boot整合WebSocket

news2024/12/26 22:36:33

【Java】Spring Boot整合WebSocket

WebSocket简介

WebSocket是一种协议,用于实现客户端和服务器之间的双向通信。它可以在单个TCP连接上提供全双工通信,避免了HTTP协议中的请求-响应模式,从而实现更高效的数据交换。WebSocket协议最初由HTML5规范提出,现在已成为一种通用的网络协议,被广泛用于Web应用程序中。

WebSocket协议的主要特点包括:

  1. 建立在TCP上:WebSocket协议使用单个TCP连接进行全双工通信,避免了HTTP协议中的多次连接建立和断开操作,从而减少了网络延迟和带宽消耗。
  2. 双向通信:WebSocket协议支持双向通信,即客户端和服务器可以同时向对方发送和接收数据,实现更加灵活和高效的数据交换。
  3. 实时性:WebSocket协议可以实现实时通信,支持消息推送、实时聊天等功能,为Web应用程序带来更好的用户体验。
  4. 协议标准化:WebSocket协议已经被标准化,并且被广泛支持,几乎所有的现代浏览器都支持WebSocket协议。

WebSocket协议在Web应用程序中广泛使用,例如实现实时通信、在线游戏、即时消息等功能。开发者可以使用JavaScript编写客户端代码,使用Java、Node.js等语言编写服务器端代码,实现WebSocket协议的双向通信。

Pom

本次Spring Boot版本 2.7.8,WebSocket 版本 5.3.25.

parent

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.8</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

dependency

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

配置文件

WebsocketConfig

package cn.com.codingce.demo.config;

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.server.standard.ServerEndpointExporter;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;

@Configuration
// 开启 WebSocket 支持
@EnableWebSocket
public class WebSocketConfig {

    /**
     * 必须要有的
     *
     * @return serverEndpointExporter
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    /**
     * WebSocket 配置信息
     *
     * @return servletServerContainerFactoryBean
     */
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean bean = new ServletServerContainerFactoryBean();
        
        // 文本缓冲区大小
        bean.setMaxTextMessageBufferSize(8192);
        // 字节缓冲区大小
        bean.setMaxBinaryMessageBufferSize(8192);

        return bean;
    }

}

使用

WebSocket 的注解:

注解作用备注
@ServerEndpoint用于声明WebSocket响应类,有点像@RequestMapping@ServerEndpoint(“/websocket”)
@OnOpenWebSocket连接时触发参数有:Session session, EndpointConfig config
@OnMessage有消息时触发参数很多,一会再说
@OnClose连接关闭时触发参数有:Session session, CloseReason closeReason
@OnError有异常时触发参数有:Session session, Throwable throwable

Controller

BaseWebSocketController

package cn.com.codingce.demo.conrtoller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;

import javax.websocket.CloseReason;
import javax.websocket.EndpointConfig;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author ma
 */
@Controller
@ServerEndpoint("/websocket")
public class BaseWebSocketController {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    // ConcurrentHashMap, 保证线程安全, static全局共享 session

    // 这里之所以static,是因为这个类不是单例的!!
    // 它虽然有@Controller注解,但是不适用Ioc容器中拿对象,每一次请求过来都是一个新的对象

    /**
     * 存放 session
     */
    private final static Map<String, Session> SESSION_MAP = new ConcurrentHashMap<>();

    /**
     * OnOpen 在连接创建(用户进入聊天室)时触发
     *
     * @param session session
     * @param config  config
     */
    @OnOpen
    public void openSession(Session session, EndpointConfig config) {
        // 将session存起来, 用于服务器向浏览器发送消息
        SESSION_MAP.put(session.getId(), session);
        String res = "OnOpen [" + session.getId() + "]进入房间";
        sendAll(res);
        logger.info(res);
    }


    /**
     * 响应字符串
     *
     * @param session session
     * @param message message
     */
    @OnMessage
    public void onMessage(Session session, String message) {
        String res = "OnMessage [" + session.getId() + "]" + message;
        sendAll(res);
        logger.info(res);
    }

    /**
     * 响应字节流
     *
     * @param session session
     * @param message message
     */
    @OnMessage
    public void onMessage(Session session, byte[] message) {
        // 这个以后再说
    }

    /**
     * OnClose 在连接断开(用户离开聊天室)时触发
     *
     * @param session     session
     * @param closeReason closeReason
     */
    @OnClose
    public void closeSession(Session session, CloseReason closeReason) {
        //记得移除相对应的session
        SESSION_MAP.remove(session.getId());

        String res = "OnClose [" + session.getId() + "]离开了房间";
        sendAll(res);
        logger.info(res);
    }

    @OnError
    public void sessionError(Session session, Throwable throwable) {
        // 通常有异常会关闭 session
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * sendAll
     *
     * @param message message
     */
    private void sendAll(String message) {
        for (Session s : SESSION_MAP.values()) {
            // 获得session发送消息的对象
            // Basic是同步, 会阻塞
            // Async是异步, 这个会有多线程并发导致异常, 发送消息太快也会有并发异常, 需要有 消息队列 来辅助使用
            final RemoteEndpoint.Basic remote = s.getBasicRemote();

            try {
                // 发送消息
                remote.sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

前端

conrtoller

package cn.com.codingce.demo.conrtoller;

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

@Controller
public class IndexController {

    @RequestMapping("/index")
    public String helloThymeleaf(Model model) {
        return "index";
    }

}

web

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>websocket-demo</title>

    <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css">
</head>
<body>
<div class="container py-3">

    <div class="row">

        <div class="col-6">
            <div>
                <label for="messageArea">聊天信息:</label>
            </div>
            <div>
                <textarea id="messageArea" readonly class="w-100" style="height: 75vh;"></textarea>
            </div>
        </div>

        <div class="col">

            <div class="my-1">
                <label for="messageArea">用 户 名:</label>
            </div>

            <div class="my-1">
                <input type="text" id="username" autocomplete="off">
            </div>

            <div class="my-1">
                <button class="btn-info" id="joinRoomBtn">进入聊天室</button>
                <button class="btn-warning" id="leaveRoomBtn">离开聊天室</button>
            </div>

            <hr/>

            <div class="my-1">
                <label for="sendMessage">输入消息:</label>
            </div>
            <div>
                <textarea id="sendMessage" rows="5" class="w-100" style="max-height: 50vh"></textarea>
            </div>

            <div class="my-1">
                <button class="btn-primary" id="sendBtn">发送消息</button>
            </div>

        </div>

    </div>

</div>
]

<script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js"></script>

<script>
    let webSocket;
    // ip和端口号用自己项目的
    // {websocket}: 其实是刚刚那个@ServerEndpoint("/websocket")中定义的
    let url = 'ws://127.0.0.1:8091/websocket';

    $('#username').keyup(function (e) {
        let keycode = e.which;
        if (keycode === 13) {
            $('#joinRoomBtn').click();
        }
    });

    // 进入聊天室
    $('#joinRoomBtn').click(function () {
        let username = $('#username').val();
        webSocket = new WebSocket(url);
        webSocket.onopen = function () {
            console.log('webSocket连接创建。。。');
        }
        webSocket.onclose = function () {
            console.log('webSocket已断开。。。');
            $('#messageArea').append('websocket已断开\n');
        }
        webSocket.onmessage = function (event) {
            $('#messageArea').append(event.data + '\n');
        }
        webSocket.onerror = function (event) {
            console.log(event)
            console.log('webSocket连接异常。。。');
        }
    });

    // 退出聊天室
    $('#leaveRoomBtn').click(function () {
        if (webSocket) {
            // 关闭连接
            webSocket.close();
        }
    });

    // 发送消息
    $('#sendBtn').click(function () {
        var msg = $('#sendMessage').val();
        if (msg.trim().length === 0) {
            alert('请输入内容');
            return;
        }
        webSocket.send($('#sendMessage').val());

        $('#sendMessage').val('');
    });

</script>

</body>
</html>

测试

页面

在这里插入图片描述

用户一

在这里插入图片描述

用户二

在这里插入图片描述

用户一收到的信息

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算几何】贝塞尔曲线 B样条曲线简介及其离散化 + Python C++ 代码实现

文章目录一、贝塞尔曲线二、B样条曲线三、Python 代码实现B样条曲线离散化四、C 代码实现B样条曲线离散化4.1 主要代码4.2 其余类4.3 离散效果展示&#xff08;在CAD中展示&#xff09;本文只做简介&#xff0c;关于贝塞尔曲线和B样条曲线的详细介绍&#xff0c;请参考&#xf…

unity UGUI系统梳理 - 基本布局

偷懒了&#xff0c;部分节选unity API API 1、矩形工具 为了便于布局&#xff0c;每个 UI 元素都表示为矩形。可使用工具栏中的__矩形工具 (Rect Tool)__ 在 Scene 视图中操纵此矩形。矩形工具既可用于 Unity 的 2D 功能&#xff0c;也可用于 UI&#xff0c;实际上甚至还可用…

C/C++开发,无可避免的多线程(篇三).协程及其支持库

一、c20的协程概念 在c20标准后&#xff0c;在一些函数中看到co_await、co_yield、co_return这些关键词&#xff0c;这是c20为协程实现设计的运算符。 协程是能暂停执行以在之后恢复的函数。原来我们调用一个功能函数时&#xff0c;只要调用了以后&#xff0c;就要完整执行完该…

【Kettle-佛系总结】

Kettle-佛系总结Kettle-佛系总结1.kettle介绍2.kettle安装3.kettle目录介绍4.kettle核心概念1.转换2.步骤3.跳&#xff08;Hop&#xff09;4.元数据5.数据类型6.并行7.作业5.kettle转换1.输入控件1.csv文件输入2.文本文件输入3.Excel输入4.XML输入5.JSON输入6.表输入2.输出控件…

百度Apollo规划算法——轨迹拼接

百度Apollo规划算法——轨迹拼接引言轨迹拼接1、什么是轨迹拼接&#xff1f;2、为什么要进行轨迹拼接&#xff1f;3、结合Apollo代码为例理解轨迹拼接的细节。参考引言 在apollo的规划算法中&#xff0c;在每一帧规划开始时会调用一个轨迹拼接函数&#xff0c;返回一段拼接轨迹…

Kubernetes之服务发布

学了服务发现后&#xff0c;svc的IP只能被集群内部主机及pod才可以访问&#xff0c;要想集群外的主机也可以访问svc&#xff0c;就需要利用到服务发布。 NodePort Nodeport服务是外部访问服务的最基本方式。当我们创建一个服务的时候&#xff0c;把服务的端口映射到kubernete…

【大数据AI人工智能】常见的归一化函数有哪些?分别用数学公式详细介绍

常见的归一化函数有哪些?分别用数学公式详细介绍一下。 常见的归一化函数 常见的归一化函数包括: Min-Max 归一化Z-Score 归一化Log 归一化Sigmoid 归一化下面分别介绍这些归一化函数以及它们的数学公式。 1. Min-Max 归一化 Min-Max 归一化是将原始数据线性映射到 [0,1]…

dp模型——状态机模型C++详解

状态机定义状态机顾名思义跟状态有关系&#xff0c;但到底有什么关系呢。在实际解决的时候&#xff0c;通常把状态想成节点&#xff0c;状态的转换想成有向边的有向图&#xff0c;我们来举个例子。相信大家都玩过类似枪战的游戏&#xff08;没玩过的也听说过吧&#xff09;&…

4.创建和加入通道相关(network.sh脚本createChannel函数分析)[fabric2.2]

fabric的test-network例子有一个orderer组织、两个peer组织、每个组织一个节点&#xff0c;只有系统通道&#xff08;system-channel&#xff09;&#xff0c;没有其他应用通道。我们可以使用./network.sh createChannel命令来创建一个名为mychannel的应用通道。 一、主要概念 …

【Java开发】JUC进阶 04:线程池详解

1 线程池介绍由于频繁创建销毁线程要调用native方法比较消耗资源&#xff0c;为了保证内核的充分利用&#xff0c;所以引入了线程池的概念。&#x1f4cc; 线程池优点降低资源消耗提高响应速度方便管理&#x1f4cc; 创建线程池使用Executors创建使用ThreadPoolExecutor创建&am…

Git图解-为啥是Git?怎么装?

目录 零、学习目标 一、版本控制 1.1 团队开发问题 1.2 版本控制思想 1.2.1 版本工具 二、Git简介 2.1 简介 2.2 Git环境的搭建 三、转视频版 零、学习目标 掌握git的工作流程 熟悉git安装使用 掌握git的基本使用 掌握分支管理 掌握IDEA操作git 掌握使用git远程仓…

【教程】记录Typecho Joe主题升级与Joe魔改版

目录 升级Joe 其他魔改版 Joe主题挺好看的&#xff0c;很早之前我就装了。后来官方升级了主题&#xff0c;但没有给升级教程。这里记录一下我的升级过程&#xff0c;供大家参考。 Joe Github&#xff1a;GitHub - HaoOuBa/Joe: A Theme of Typecho 升级站点&#xff1a;小锋学…

WSL2使用Nvidia-Docker实现CUDA版本自由切换

众所周知&#xff0c;深度学习的环境往往非常麻烦&#xff0c;经常不同的项目所依赖的 torch、tensorflow 包对 CUDA 的版本也有不同的要求&#xff0c;Linux 下进行 CUDA 的管理比较麻烦&#xff0c;是一个比较头疼的问题。 随着 WSL2 对物理机显卡的支持&#xff0c;Nvidia-…

用二极管和电容过滤电源波动,实现简单的稳压 - 小水泵升压改装方案

简而言之&#xff0c;就是类似采样保持电路&#xff0c;当电源电压因为电机启动而骤降时&#xff0c;用二极管避免电容电压跟着降低&#xff0c;从而让电容上连接的低功耗芯片有一个比较稳定的供电电压。没什么特别的用处&#xff0c;省个LDO 吧&#xff0c;电压跌幅太大的时候…

最详细Sql语句优化大汇总 面试必问 含解释

欢迎补充和纠正&#xff01;&#xff01;&#xff01; 目录 欢迎补充和纠正&#xff01;&#xff01;&#xff01; 基础知识 相关索引的创建 一条sql语句的执行过程 sql语句关键字的执行顺序 SQL优化 使用explain来分析Sql语句 尽量用varchar代替char 使用数值代替字符…

Vector - CAPL - 定时器函数和使用

定时器在C语言中的使用我想学习过C编程的都不会陌生&#xff0c;它能够提供延时&#xff0c;完成等待一定的时间&#xff1b;它也可以实现多线程的操作&#xff0c;并行实行某些软件功能。那在CAPL中&#xff0c;定时器又能做哪些工作呢&#xff1f;又是怎么使用的呢&#xff1…

SPringCloud:Nacos快速入门及相关属性配置

目录 一、Nacos快速入门 1、在父工程中添加spring-cloud-alilbaba的管理依赖 2、如果有使用eureka依赖&#xff0c;将其注释 3、添加nacos的客户端依赖 4、修改yml文件&#xff0c;注释eureka配置 5、启动测试 二、Nacos相关属性配置 1、Nacos服务分级存储 2、根据集群…

ELasticsearch基本使用——基础篇

1.初识elasticsearch1.1.了解ES1.1.1.elasticsearch的作用elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容例如&#xff1a;在GitHub搜索代码在电商网站搜索商品在谷歌搜索答案在打车软件搜索…

Oracle中merge Into的用法

Oracle中merge Into的用法 使用场景 在操作数据库时&#xff0c;数据存在的情况下&#xff0c;进行update操作&#xff1b;不存在的情况下&#xff0c;进行insert操作&#xff1b;在Oracle数据库中&#xff0c;能够使用merge into来实现。 基本语法 merge into table_name …

Go项目的目录结构基本布局

前言 随着项目的代码量在不断地增长&#xff0c;不同的开发人员按自己意愿随意布局和创建目录结构&#xff0c;项目维护性就很差&#xff0c;代码也非常凌乱。良好的目录与文件结构十分重要&#xff0c;尤其是团队合作的时候&#xff0c;良好的目录与文件结构可以减少很多不必要…