SpringBoot + Websocket 实现实时聊天

2023-11-09

SpringBoot + WebSocket 实现实时聊天

最近有点小时间,上个项目正好用到了websocket实现广播消息来着,现在来整理一下之前的一些代码,分享给大家。

WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

一、环境介绍

开发工具:IntelliJ IDEA

运行环境:SpringBoot2.x、ReconnectingWebSocket、JDK1.8+、Maven 3.6 +

ReconnectingWebSocket 是一个小型的 JavaScript 库,封装了 WebSocket API 提供了在连接断开时自动重连的机制。

只需要简单的将:

ws = new WebSocket('ws://....');

替换成:

ws = new ReconnectingWebSocket('ws://....');

WebSocket 属性ws.readyState:

​ 0 - 表示连接尚未建立。

​ 1 - 表示连接已建立,可以进行通信。

​ 2 - 表示连接正在进行关闭。

​ 3 - 表示连接已经关闭或者连接不能打开。

WebSocket事件:

事件 事件处理程序 描述
open ws.onopen 连接建立时触发
message ws.onmessage 客户端接收服务端数据时触发
error ws.onerror 通信发生错误时触发
close ws.onclose 连接关闭时触发

WebSocket方法:

方法 描述
Socket.send() 使用连接发送数据
Socket.close() 关闭连接

二、代码实现

(一)、创建SpringBoot项目

在这里插入图片描述

(二)、添加 pom 依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
   <!-- springbooot 集成 websocket -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.5</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.71</version>
    </dependency>
</dependencies>

(三)、编写前端模板index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>SpringBoot-ws</title>
    <script src="../js/reconnectingwebsocket.js" type="text/javascript" charset="utf-8"></script>
    <!--    <script src="../js/sockjs.min.js" type="text/javascript" charset="utf-8"></script>-->
    <script src="../js/jquery.min.js" type="text/javascript" charset="utf-8"></script>
    <link rel="stylesheet" type="text/css" href="../css/style.css">
</head>
<body>
<div id="info">
    <div>发送人:<input type="text" id="suer" required="required" placeholder="请输入发送人"></div>
    <div>接收人:<input type="text" id="ruser" required="required" placeholder="请输入接收人"></div>
</div>
<div id="index">
</div>
<div class="msg">
    <textarea id="send_content" placeholder="在此输入消息..."></textarea>
</div>
<div class="ibtn c">
    <button onclick=openWebsocket()>开启连接</button>
    <button onclick=closeWebsocket()>关闭连接</button>
    <button onclick=sendMessage()>发送消息</button>
</div>
<script type="text/javascript">
    document.getElementById('send_content').focus();

    var websocket = null;

    //关闭websocket
    function closeWebsocket() {
        //3代表已经关闭
        if (3 != websocket.readyState) {
            websocket.close();
        } else {
            alert("websocket之前已经关闭");
        }
    }

    // 开启websocket
    function openWebsocket() {
        username = $("#suer").val()
        if (username != "") {

            //当前浏览前是否支持websocket
            if ("WebSocket" in window) {
                websocket = new ReconnectingWebSocket("ws://localhost:8080/send/" + username);
                websocket.reconnectInterval = 3000 //每3s进行一次重连,默认是每秒
            } else if ('MozWebSocket' in window) {
                websocket = new MozWebSocket("ws://localhost:8080/send/" + username);
            } else {
                //低版本
                websocket = new SockJS("http://localhost:8080/sockjs/send/" + username);
            }
        }
        websocket.onopen = function (event) {
            setMessage("打开连接");
        }

        websocket.onclose = function (event) {
            setMessage("关闭连接");
        }

        websocket.onmessage = function (event) {
            // setMessage(event.data);
            setMessageTxt(event.data)

        }

        websocket.onerror = function (event) {
            setMessage("连接异常,正在重连中...");
        }

        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function () {
            closeWebsocket();
        }
    }

    //将消息显示在网页上
    function setMessage(message) {
        alert(message)
    }

    function setMessageTxt(message) {
        mObj = JSON.parse(message)
        var div = document.createElement('div')
        div.innerHTML = "<div class='name l'><h2>" + mObj['from_topic'] + "</h2></div>" +
            "<div class='content w l'>" + mObj['content'] + "</div>"
        div.setAttribute("class", "from_info")
        document.getElementById('index').appendChild(div)
    }

    // 发送消息
    function sendMessage() {
        //1代表正在连接
        if (1 == websocket.readyState) {
            var message = document.getElementById('send_content').value;
            var div = document.createElement('div')
            div.innerHTML = "<div class='name r rcontent'><h2> Me </h2></div>" +
                "<div class='content w r'>" + message + "</div>"
            div.setAttribute("class", "send_info")
            document.getElementById('index').appendChild(div)
            ruser = document.getElementById("ruser").value;
            message = "{'content':'" + message + "','to_topic':'" + ruser + "'}"
            websocket.send(message);
        } else {
            alert("websocket未连接");
        }
        document.getElementById('send_content').value = "";
        document.getElementById('send_content').focus();
    }
</script>
</body>
</html>

(四)、服务端代码编写

  1. 编写 SWCrontroller.java 类
 package com.jhzhong.swchat.controller;
 
 import com.jhzhong.swchat.websocket.WebSocketServer;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.*;
 
 @Controller
 public class SWController {
 
     @Autowired
     private WebSocketServer webSocketServer;
 
     /**
      * author: jhzhong95@gmail.com
      * date: 2020-06-24 12:35 AM
      * desc: 跳转index.html页面
      * @return
      */
     @RequestMapping("/")
     public String index() {
         return "index";
     }
 }
  1. 编写WebSocketConfig.java 类,开启WebSocket支持。
 package com.jhzhong.swchat.websocket;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.socket.server.standard.ServerEndpointExporter;
 
 /**
  * author: jhzhong95@gmail.com
  * date: 2020-06-24 12:28 AM
  * desc: 开启WebSocket支持
  */
 @Configuration
 public class WebSocketConfig {
 
     @Bean
     public ServerEndpointExporter serverEndpointExporter(){
         return new ServerEndpointExporter();
     }
 }
  1. 编写核心代码类 WebSocketServer.java
 package com.jhzhong.swchat.websocket;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import freemarker.log.Logger;
 import org.apache.commons.lang.StringUtils;
 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.concurrent.ConcurrentHashMap;
 
 /**
  * author: jhzhong95@gmail.com
  * date: 2020-06-24 12:40 AM
  * desc: WebSocket服务端
  */
 @ServerEndpoint("/send/{topic}")
 @Component
 public class WebSocketServer {
     static Logger logger = Logger.getLogger("WebSocketServer");
     /**
      * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
      */
     private static int onlineCount = 0;
     /**
      * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
      */
     private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
     /**
      * 与某个客户端的连接会话,需要通过它来给客户端发送数据
      */
     private Session session;
     /**
      * 接收频道topic
      */
     private String topic = "";
 
     /**
      * 连接建立成功调用的方法
      */
     @OnOpen
     public void onOpen(Session session, @PathParam("topic") String topic) {
         this.session = session;
         this.topic = topic;
         if (webSocketMap.containsKey(topic)) {
             webSocketMap.remove(topic);
             webSocketMap.put(topic, this);
             //加入set中
         } else {
             webSocketMap.put(topic, this);
             //加入set中
             addOnlineCount();
             //在线数加1
         }
 
         logger.info("用户连接:" + topic + ",当前在线人数为:" + getOnlineCount());
         try {
             sendMessage("连接成功");
         } catch (IOException e) {
             logger.error("用户:" + topic + ",网络异常!!!!!!");
         }
     }
 
 
     /**
      * 连接关闭调用的方法
      */
     @OnClose
     public void onClose() {
         if (webSocketMap.containsKey(topic)) {
             webSocketMap.remove(topic);
             //从set中删除
             subOnlineCount();
         }
         logger.info("用户退出:" + topic + ",当前在线人数为:" + getOnlineCount());
     }
 
     /**
      * 收到客户端消息后调用的方法
      *
      * @param message 客户端发送过来的消息
      */
     @OnMessage
     public void onMessage(String message, Session session) {
         logger.info("用户:" + topic + ",信息:" + message);
         //可以群发消息
         //消息保存到数据库、redis
         if (StringUtils.isNotBlank(message)) {
             try {
                 //解析发送的报文
                 JSONObject jsonObject = JSON.parseObject(message);
                 //追加发送人(防止串改)
                 jsonObject.put("from_topic", this.topic);
                 String to_topic = jsonObject.getString("to_topic");
                 //传送给对应toUserId用户的websocket
                 if (StringUtils.isNotBlank(to_topic) && webSocketMap.containsKey(to_topic)) {
                     webSocketMap.get(to_topic).sendMessage(jsonObject.toJSONString());
                 } else {
                     logger.error("请求的to_topic:" + to_topic + "不在该服务器上");
                     //否则不在这个服务器上,发送到mysql或者redis
                 }
             } catch (Exception e) {
                 e.printStackTrace();
             }
         }
     }
 
     /**
      * @param session
      * @param error
      */
     @OnError
     public void onError(Session session, Throwable error) {
         logger.error("用户错误:" + this.topic + ",原因:" + error.getMessage());
         error.printStackTrace();
     }
 
     /**
      * 实现服务器主动推送
      */
     public void sendMessage(String message) throws IOException {
         this.session.getBasicRemote().sendText(message);
     }
 
 
     /**
      * 发送自定义消息
      */
     public static void sendInfo(String message, @PathParam("topic") String topic) throws IOException {
         logger.info("发送消息到:" + topic + ",信息:" + message);
         if (StringUtils.isNotBlank(topic) && webSocketMap.containsKey(topic)) {
             webSocketMap.get(topic).sendMessage(message);
         } else {
             logger.error("用户" + topic + ",不在线!");
         }
     }
 
     public static synchronized int getOnlineCount() {
         return onlineCount;
     }
 
     public static synchronized void addOnlineCount() {
         WebSocketServer.onlineCount++;
     }
 
     public static synchronized void subOnlineCount() {
         WebSocketServer.onlineCount--;
     }
 }

三、运行截图

  1. 首页截图

  2. 视频效果

SpringBoot+WebSocket实现消息广播

如需源码请参考: sw-chat.zip 源码下载

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot + Websocket 实现实时聊天 的相关文章

  • 如何使用 Apache POI API 将图像添加到 pptx 中添加的图像占位符?

    我已经预定义了带有文本和图像占位符的 pptx 模板 我如何从模板访问和修改这些占位符 我可以使用 POI pptx API 直接将图像和文本添加到幻灯片中 但如何将其添加到模板的占位符中 请参阅链接以了解如何添加占位符来创建固定模板 ht
  • Java - 为什么不允许 Enum 作为注释成员?

    It says 原始 String Class an Enum 另一个注释 上述任何一个的数组 只有这些类型才是合法的 Annotation 成员 为什么泛型 Enum 不能成为 Annotation 的成员 例如 Retention Re
  • OSGi:如果不取消服务会发生什么

    这是我获取 OSGi 服务的方式 ServiceReference reference bundleContext getServiceReference Foo class getName Foo foo Foo bundleContex
  • Java AES 128 加密方式与 openssl 不同

    我们遇到了一种奇怪的情况 即我们在 Java 中使用的加密方法会向 openssl 生成不同的输出 尽管它们在配置上看起来相同 使用相同的键和 IV 文本 敏捷的棕色狐狸跳过了懒狗 加密为 Base64 字符串 openssl A8cMRI
  • Cassandra java驱动程序协议版本和连接限制不匹配

    我使用的java驱动程序版本 2 1 4卡桑德拉版本 dsc cassandra 2 1 10cql 的输出给出以下内容 cqlsh 5 0 1 Cassandra 2 1 10 CQL spec 3 2 1 Native protocol
  • 当从服务类中调用时,Spring @Transactional 不适用于带注释的方法

    在下面的代码中 当方法内部 是从内部调用的方法外部 应该在交易范围内 但事实并非如此 但当方法内部 直接从调用我的控制器class 它受到事务的约束 有什么解释吗 这是控制器类 Controller public class MyContr
  • 如何模拟从抽象类继承的受保护子类方法?

    如何使用 Mockito 或 PowerMock 模拟由子类实现但从抽象超类继承的受保护方法 换句话说 我想在模拟 doSomethingElse 的同时测试 doSomething 方法 抽象超类 public abstract clas
  • 画透明圆,外面填充

    我有一个地图视图 我想在其上画一个圆圈以聚焦于给定区域 但我希望圆圈倒转 也就是说 圆的内部不是被填充 而是透明的 其他所有部分都被填充 请参阅这张图片了解我的意思 http i imgur com zxIMZ png 上半部分显示了我可以
  • Hazelcast 分布式锁与 iMap

    我们目前使用 Hazelcast 3 1 5 我有一个简单的分布式锁定机制 应该可以跨多个 JVM 节点提供线程安全性 代码非常简单 private static HazelcastInstance hInst getHazelcastIn
  • hibernate锁等待超时超时;

    我正在使用 Hibernate 尝试模拟对数据库中同一行的 2 个并发更新 编辑 我将 em1 getTransaction commit 移至 em1 flush 之后我没有收到任何 StaleObjectException 两个事务已成
  • 很好地处理数据库约束错误

    再一次 它应该很简单 我的任务是在我们的应用程序的域对象中放置一个具有唯一约束的特定字段 这本身并不是一个很大的挑战 我刚刚做了以下事情 public class Location more fields Column unique tru
  • 如何在 Java 中测试一个类是否正确实现了 Serialized(不仅仅是 Serialized 的实例)

    我正在实现一个可序列化的类 因此它是一个与 RMI 一起使用的值对象 但我需要测试一下 有没有办法轻松做到这一点 澄清 我正在实现该类 因此在类定义中添加 Serialized 很简单 我需要手动序列化 反序列化它以查看它是否有效 我找到了
  • Javafx过滤表视图

    我正在尝试使用文本字段来过滤表视图 我想要一个文本字段 txtSearch 来搜索 nhs 号码 名字 姓氏 和 分类类别 我尝试过在线实施各种解决方案 但没有运气 我对这一切仍然很陌生 所以如果问得不好 我深表歉意 任何帮助将不胜感激 我
  • 如何知道抛出了哪个异常

    我正在对我们的代码库进行审查 有很多这样的陈述 try doSomething catch Exception e 但我想要一种方法来知道 doSomething 抛出了哪个异常 在 doSomething 的实现中没有 throw 语句
  • java.lang.NumberFormatException: Invalid int: "3546504756",这个错误是什么意思?

    我正在创建一个 Android 应用程序 并且正在从文本文件中读取一些坐标 我在用着Integer parseInt xCoordinateStringFromFile 将 X 坐标转换为整数 Y 坐标的转换方法相同 当我运行该应用程序时
  • Netty:阻止调用以获取连接的服务器通道?

    呼吁ServerBootstrap bind 返回一个Channel但这不是在Connected状态 因此不能用于写入客户端 Netty 文档中的所有示例都显示写入Channel从它的ChannelHandler的事件如channelCon
  • 我可以创建自定义 java.* 包吗?

    我可以创建一个与预定义包同名的自己的包吗在Java中 比如java lang 如果是这样 结果会怎样 这难道不能让我访问该包的受保护的成员 如果不是 是什么阻止我这样做 No java lang被禁止 安全管理器不允许 自定义 类java
  • 将 Azure AD 高级自定义角色与 Spring Security 结合使用以进行基于角色的访问

    我创建了一个演示 Spring Boot 应用程序 我想在其中使用 AD 身份验证和授权 并使用 AD 和 Spring Security 查看 Azure 文档 我执行了以下操作 package com myapp contactdb c
  • 为什么C++代码执行速度比java慢?

    我最近用 Java 编写了一个计算密集型算法 然后将其翻译为 C 令我惊讶的是 C 的执行速度要慢得多 我现在已经编写了一个更短的 Java 测试程序和一个相应的 C 程序 见下文 我的原始代码具有大量数组访问功能 测试代码也是如此 C 的
  • 如何修复:“无法解析类型 java.lang.CharSequence。它是从所需的 .class 文件间接引用的”消息? [复制]

    这个问题在这里已经有答案了 我正在尝试使用这个字符串 amountStr amountStr replace replace replace 但我收到一条错误消息 我知道我收到的错误消息是因为我刚刚发布的字符串已过时 所以我想知道该字符串的

随机推荐

  • Centos安装配置git

    最简单的安装方式直接使用yaml命令 yum install git all 服务器回应 其实除了安装git之外 也会安装其他需要的依赖文件 yum 安装的git默认是 git version 1 8 3 1 git官网下载压缩包 本地配置
  • JPA快速开发之查询接口Repository

    什么是JPA SpringData是Spring提供的一个数据操作框架 而SpringData JPA则是该框架下基于JPA标准进行数据操作的模块 SpringData JPA简化了持久层代码的操作 只需编写接口即可实现 JPA 全称Jav
  • Python全栈开发【基础-10】流程控制之for循环

    专栏介绍 本专栏为Python全栈开发系列文章 技术包括Python基础 函数 文件 面向对象 网络编程 并发编程 MySQL数据库 HTML JavaScript CSS JQuery bootstrap WSGI Django Flas
  • JetBrains产品教育版申请

    JetBrains产品教育版申请 Pycharm和IntelliJ都是申请的JetBarins官方edu版 功能上应该和professional一样 虽然咱不一定能用得到pro 以下内容以申请Pycharm为例 事实上 只要能认证成功 所有
  • 蓝牙DA14580学习教程(附开源可编程手环/手表全套学习资料下载地址)

    DA14580学习 DA14580用来干什么 1 超长待机的智能手环 手表和其他智能穿戴设备 2 智能鼠标 键盘 遥控器 触控板 语音和手势识别控制板等 3 计步 如小米手环 活动和睡眠监测器 血压血糖心率监测器 4 多感测器运动电脑平台
  • 数据处理的关键组件:编程与数据加工

    编程是计算机科学中至关重要的一环 它涉及到对数据进行加工与处理的过程 通过编程 我们可以创建算法和程序来操作和转换数据 使其具有意义和价值 在本文中 我们将探讨编程在数据处理中的作用 并提供一些使用中文编写的源代码示例 编程语言是用来编写程
  • spring boot2 与dubbo整合经验

    开始接触spring boot2整合dubbo时 参考了好多文章 都不太完全 导致整合屡次失败 坚信降低门槛才会普及大众 否则技术再好束之高阁也白搭 dubbo配置文件分好几种 xml类 properties类 api配置类 注解类 另外注
  • 目标跟踪算法研究整理

    最近项目有用到目标跟踪的算法 用的还是传统opencv 整理一下 1 基础框架 目标跟踪基础认识 视频图像跟踪算法综述 opencv实现目标跟踪的八种算法 2 CSRT追踪器 CSRT追踪器 官方描述 在具有通道和空间可靠性的判别相关滤波器
  • JSX初探

    需求 动态的创建HTML页面 假设有一个布尔变量 editable 为true时创建A界面 为false时创建B界面 A界面 div class div B界面 div class div 现有的实现思路有两个 一 JS动态创建HTML标签
  • Unity3D物体上下左右旋转(不受物体自身坐标轴影响【万向锁问题解决】)

    直接将代码挂载到需要旋转的物体上 按上下左右键旋转即可 using UnityEngine using System Collections public class SpinObject MonoBehaviour public Game
  • FineReport.10 一(帆软)(报表基础练习)

    文章目录 一 FineReport 1 报表 2 安装 二 安装目录 2 1 设计器相关目录 2 2 工程相关文件夹 2 3 缓存文件 2 3 1 FineReportEnv xml 三 初始练习 四 遇见的问题记录 1 初始数据库出问题需
  • Python基础入门(五)——关于一个Number类型的小实例

    Number类型数据是我们在python当中经常要用到的数据类型 先上一些小知识点 再上实例 相关知识点 Number类型 Number类型 Number类型的定义和删除 一句话 赋值就是定义 删除要用DEL 定义 var1 1 var2
  • 大语言模型的演进

    大语言模型的演进 借着上次科技树剪枝的话题 大语言模型为人工智能科技树再次剪枝 让我们再来看看大语言模型这个分枝是如何生长的 也是经历6年的Google和OpenAI两家公司几次大战后的结果 第一回合 2017年6月 Google的6500
  • 从代码生成到问题解答,InsCode AI助你成为编程及写作高手

    目录 前言 InsCode AI写作助手 创造更好的写作体验 InsCode AI 创作助手 如何帮助创作者高效创作文章 结论 个人感受 一 你平时会使用这类AI工具吗 你对这类型的工具有什么看法 二 你可以花几分钟体验一下InsCode
  • 新的刷脸支付方式掘起手机支付将会終结

    手机支付将会終结 新的支付方式掘起 新的支付方式对很多人还是很陌生的 这就要很好的推广和布置 现在推出了二代的蜻蜓刷脸设备 向商户销售出了舒缓的二代蜻蜓刷脸支付设备 超市 快餐厅 自动贩卖机都已经实现商业直播 相信很快在每个城市 都会发现这
  • 重载new/delete(C++中的new/delete与operator new/operator delete)

    转 http blog csdn net zhangxiao93 article details 50768025 原文 http www cnblogs com luxiaoxun archive 2012 08 10 2631812 h
  • k8s的dashboard日常操作

    k8s的dashboard日常操作 一 k8s的dashboard介绍 1 dashboard介绍 2 dashboard的功能 集群管理 工作负载 服务发现和负载均衡 存储 配置 日志视图 三 查看集群的所有角色 四 查看集群的命令空间
  • Python数据结构-----递归实现快速排序

    目录 前言 快速排序 1 概念 2 示例 Python代码实现 递归实现快速排序 前言 今天我们就来一起学习快速排序的思想方法 然后通过Python语言来实现快速排序的功能 下面我们就开始今天的学习吧 快速排序 1 概念 快速排序其实是冒泡
  • 使用ggplot2包绘制分组带状图实战

    使用ggplot2包绘制分组带状图实战 在数据可视化中 分组带状图是一种常用的图表类型 可以直观地展示多个组别之间的差异和变化趋势 而R语言中的ggplot2包提供了丰富的绘图函数 其中geom jitter函数可以用来创建分组带状图 下面
  • SpringBoot + Websocket 实现实时聊天

    SpringBoot WebSocket 实现实时聊天 最近有点小时间 上个项目正好用到了websocket实现广播消息来着 现在来整理一下之前的一些代码 分享给大家 WebSocket协议是基于TCP的一种新的网络协议 它实现了浏览器与服