从零开始的使用SpringBoot和WebSocket打造实时共享文本应用

news2025/1/11 7:55:05

在现代应用中,实时协作已经成为了非常重要的功能,尤其是在文档编辑、聊天系统和在线编程等场景中。通过实时共享文档,多个用户可以同时对同一份文档进行编辑,并能看到其他人的编辑内容。这种功能广泛应用于 Google Docs、Notion 等产品中。

在本文中,我们将实现一个简单的共享文本框,使用 WebSocket 技术来实现多人实时编辑同一份文本。通过 WebSocket 协议,客户端和服务器可以保持一个持续的连接,使得文档的内容能够实时同步到所有参与者。
(引流:https://juejin.cn/post/7445187277558628387)
效果图如下:

tutieshi_640x272_3s

1. 什么是 WebSocket?

WebSocket 是一种网络协议,它提供了一个全双工的通信通道,允许客户端和服务器之间进行实时、双向的数据传输。与传统的 HTTP 协议不同,WebSocket 连接在建立后会保持打开状态,不需要频繁的建立连接,从而大大提高了数据交换的效率。

WebSocket 协议通常用于实时聊天、在线游戏、金融行情推送等场景。在本文中,我们将利用 WebSocket 来实现一个共享文本框。

2. 项目需求

我们的目标是实现一个简单的共享文本框功能,要求如下:

  • 多个用户可以同时连接到同一个文档并进行编辑。
  • 每次用户编辑文本时,修改内容会即时同步到其他用户的浏览器。
  • 实现基本的文本框功能,包括输入和显示。

3. 技术栈

  • 前端:HTML、CSS、JavaScript(使用 WebSocket API)
  • 后端:SpringBoot
  • 通信协议:WebSocket

4. 实现步骤

4.1 搭建 WebSocket 服务端

首先,我们需要创建一个 WebSocket 服务器来处理客户端连接。具体步骤如下:

  1. 是maven依赖中引入websocket的依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  1. 对WebSocket进行一些配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @ Description: 开启WebSocket支持
 * 用于在Spring框架的应用中配置和启用WebSocket功能。
 * 通过相关注解和方法的定义,使得应用能够正确地处理WebSocket连接和通信。
 */
@Configuration
public class WebSocketConfig {
    //Bean生命周期的初始化
    // 用于将方法返回的ServerEndpointExporter对象作为一个Bean注册到Spring的容器中
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        //创建并返回一个ServerEndpointExporter对象。
        // ServerEndpointExporter主要作用是扫描带有@ServerEndpoint注解的WebSocket端点类,并将它们注册到Servlet容器中,
        // 从而使得应用能够正确地处理WebSocket连接请求,实现WebSocket的通信功能。
        return new ServerEndpointExporter();
    }
}
  1. WebSocket服务器实现
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.stereotype.Service;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

@Service
@ServerEndpoint("/api/websocket/sharedText/{sid}")
public class WebSocketServer {

    // 每个连接的 Session
    private Session session;
    private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
    private static int onlineCount = 0;
    // 存储每个连接的 sid
    private String sid = "";
    // 存储每个连接的内容
    private static String content = "";

    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        this.session = session;
        // 使用 URL 中的 sid 参数为当前连接设置 sid
        this.sid = sid;
        webSocketSet.add(this); // 将当前连接添加到 WebSocket 客户端集合中
        addOnlineCount(); // 增加在线连接数
        try {
            sendMessage(this.content); // 向当前客户端发送连接成功消息
            System.out.println("有新窗口开始监听:" + sid + ", 当前在线人数为:" + getOnlineCount());
        } catch (IOException e) {
            System.out.println("websocket IO Exception");
        }
    }

    @OnClose
    public void onClose() {
        webSocketSet.remove(this); // 从 WebSocket 客户端集合中移除当前连接
        subOnlineCount(); // 减少在线连接数
        System.out.println("释放的 sid 为:" + sid);
        System.out.println("有一连接关闭!当前在线人数为 " + getOnlineCount());
    }

    @OnMessage
    public void onMessage(String message, Session session) throws JsonProcessingException {
        this.content = message;
        // 打印来自某个 sid 的消息内容
        System.out.println("收到来自窗口 " + sid + " 的信息: " + message);

        // 群发消息给所有已连接的客户端
        for (WebSocketServer item : webSocketSet) {
            try {
                item.sendMessage(message); // 向所有连接的客户端广播消息
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }

    // 向客户端发送消息
    public void sendMessage(String message) throws IOException {
        if (this.session != null && this.session.isOpen()) {
            this.session.getBasicRemote().sendText(message); // 发送消息
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }

    public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
        return webSocketSet;
    }
}

上述代码中,我们创建了一个 WebSocket 服务器并监听了 8080 端口。当有客户端连接时,服务器会触发 connection 事件,处理来自客户端的消息并将其广播给所有已连接的客户端。

4.2 创建前端页面

接下来,我们需要创建一个前端页面,用户可以在其中输入文本并实时看到其他用户的编辑内容。我们将使用 WebSocket API 与后端建立连接。

<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>实时共享文本框 - WebSocket 实现</title>
  <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
  <style>
    body {
      font-family: Arial, sans-serif;
      background-color: #f4f4f9;
      padding: 20px;
      display: flex;
      flex-direction: column;
      align-items: center;
    }

    h2 {
      margin-bottom: 20px;
    }

    #textBox {
      width: 80%;
      max-width: 900px;
      height: 300px;
      padding: 10px;
      font-size: 16px;
      border: 1px solid #ddd;
      border-radius: 5px;
      background-color: #fff;
      resize: none;
      box-sizing: border-box;
    }

    #message {
      margin-top: 20px;
      padding: 10px;
      width: 80%;
      max-width: 900px;
      border: 1px solid #ddd;
      border-radius: 5px;
      background-color: #fafafa;
      font-size: 14px;
      color: #555;
    }

    #message span {
      font-weight: bold;
    }

    .status {
      margin: 10px 0;
      color: #333;
    }

    .error {
      color: red;
    }

    .success {
      color: green;
    }

    .info {
      color: #555;
    }
  </style>
</head>

<body>
<h2>实时共享文本框</h2>
<textarea id="textBox" rows="20" cols="80" placeholder="开始编辑文本..."></textarea><br />

<script type="text/javascript">
  let websocket = null;
  const sid = "100"; // 这里可以更改为动态获取的 sid,例如通过 URL 获取

  // 判断浏览器是否支持 WebSocket
  if ('WebSocket' in window) {
    websocket = new WebSocket(`ws://192.168.113.45:8080/api/websocket/sharedText/${sid}`);
  } else {
    alert('当前浏览器不支持 WebSocket');
  }

  // 连接错误时处理
  websocket.onerror = () => {
    updateStatus('WebSocket连接发生错误', 'error');
  };

  // 连接成功时处理
  websocket.onopen = () => {
    updateStatus('WebSocket连接成功', 'success');
  };

  // 接收消息时处理
  websocket.onmessage = (event) => {
    console.log(event);
    updateTextBox(event.data);
  };

  // 连接关闭时处理
  websocket.onclose = () => {
    updateStatus('WebSocket连接关闭', 'info');
  };

  // 窗口关闭时确保关闭 WebSocket 连接
  window.onbeforeunload = () => {
    closeWebSocket();
  };

  // 更新状态消息
  function updateStatus(message, type) {
    const statusDiv = document.getElementById('message');
    statusDiv.innerHTML = `<span class="${type}">${message}</span>`;
  }

  // 关闭 WebSocket 连接
  function closeWebSocket() {
    if (websocket) {
      websocket.close();
    }
  }

  // 监听文本框输入事件
  document.getElementById('textBox').addEventListener('input', function () {
    const message = this.value;
    if (message !== previousMessage) {
      websocket.send(message); // 发送消息到 WebSocket
      previousMessage = message; // 更新当前文本
    }
  });

  let previousMessage = ''; // 用于记录文本框内容,避免重复发送

  // 更新文本框内容
  function updateTextBox(content) {
    // 防止不停地将同一内容发送给其他用户
    if (document.getElementById('textBox').value !== content) {
      document.getElementById('textBox').value = content;
    }
  }
</script>
</body>

</html>

在前端页面中,我们创建了一个简单的文本框 (<textarea>) 供用户输入文本。当用户在文本框中输入内容时,input 事件会触发,内容会通过 WebSocket 发送给服务器。服务器收到消息后,会将其广播给所有其他连接的客户端,客户端接收到广播消息后会更新自己的文本框内容。

4.3 测试与运行

直接启动SpringBoot服务即可,同时打开web网页。

最终效果如下:

在一个网页端编辑,另一个网页端能及时收到变更。

5. 小结

通过这篇博客,我们实现了一个简单的实时共享文本框,利用 WebSocket 技术来实现多人实时编辑同一份文本。每当一个用户编辑文本时,服务器会将该编辑广播给其他在线用户,从而实现实时同步。这是一个基本的多人协作编辑功能,适用于在线文档编辑、聊天系统等场景。

在实际应用中,我们可以根据需求扩展更多功能,例如用户身份管理、权限控制、文本格式化、撤销/重做功能等。通过 WebSocket,我们不仅可以实现实时通信,还能为用户提供流畅的协作体验。在开发中,WebSocket 仍然是一个非常强大的工具,适用于许多实时协作的场景。

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

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

相关文章

「Mac畅玩鸿蒙与硬件43」UI互动应用篇20 - 闪烁按钮效果

本篇将带你实现一个带有闪烁动画的按钮交互效果。通过动态改变按钮颜色&#xff0c;用户可以在视觉上感受到按钮的闪烁效果&#xff0c;提升界面互动体验。 关键词 UI互动应用闪烁动画动态按钮状态管理用户交互 一、功能说明 闪烁按钮效果应用实现了一个动态交互功能&#xf…

SSM报错:表现层方法应该返回字符串,但是返回页面

在进行SSM项目时&#xff0c;后端表现层应该返回给前端字符串&#xff0c;但是却跳转页面 1.首先检查是否使用ResponseBody注解 ResponseBody注解 作用&#xff1a;将java对象转为json格式的数据。将controller的方法返回的对象通过适当的转换器转换为指定的格式之后&#xff0…

重生之我在异世界学编程之C语言:深入结构体篇(上)

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文《1》 结构体的两种声明一、结构…

Diffusion中guidance_scale 的理解

guidance_scale 是一个控制生成图像引导程度的参数。它的含义和使用与论文 Imagen: Photorealistic Text-to-Image Diffusion Models with Composable Conditions 中的公式 (2) 的引导权重 类似。 1. Classifier-Free Guidance 的背景 Classifier-Free Guidance 是一种在扩散…

【kettle】mysql数据抽取至kafka/消费kafka数据存入mysql

目录 一、mysql数据抽取至kafka1、表输入2、json output3、kafka producer4、启动转换&#xff0c;查看是否可以消费 二、消费kafka数据存入mysql1、Kafka consumer2、Get records from stream3、字段选择4、JSON input5、表输出 一、mysql数据抽取至kafka 1、表输入 点击新建…

新书速览|循序渐进Node.js企业级开发实践

《循序渐进Node.js企业级开发实践》 1 本书内容 《循序渐进Node.js企业级开发实践》结合作者多年一线开发实践&#xff0c;系统地介绍了Node.js技术栈及其在企业级开发中的应用。全书共分5部分&#xff0c;第1部分基础知识&#xff08;第1&#xff5e;3章&#xff09;&#xf…

基于大模型的图像重命名工具ai-renamer

文章目录 基础使用语言问题命名风格 基础使用 ai-renamer是一款自动为图片重命名的工具&#xff0c;由于需要调用基于本地大模型&#xff0c;在使用之前需要用Ollama或者LM Studio配置好至少一个大模型&#xff0c;比如Llava, Gemma, Llamad等。如果想要为视频重命名&#xff…

element Cascader级联选择器 点文字即可选中,去掉radio按钮

需求 将示例的点击radio和点击文字功能结合在一起。可以选择任意一级的内容&#xff0c;直接点击文字即可选中&#xff0c;同时如果有下一级就展示&#xff0c;去掉radio标签。 实现方法 通过css将radio标签做成文字框一样大小并且透明覆盖在整个文字上方&#xff0c;点击文…

【已解决】黑马点评项目中启动Spring Boot服务失败,com.sun.tools.javac.tree.JCTree qualid

黑马点评项目中启动Spring Boot服务失败 报错提示 java: java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field com.sun.tools.javac.tree.JCTree qualid这是因为 lombok 版本不兼容造成的 找到 pom.xml 文件&#xff0…

C#,人工智能,深度学习,目标检测,OpenCV级联分类器数据集的制作与《层级分类器一键生成器》源代码

一、目标识别技术概述 1、摘要 目标检测是计算机视觉中最基本和最具挑战性的问题之一&#xff0c;它试图从自然图像中的大量预定义类别中定位目标实例。深度学习技术已成为直接从数据中学习特征表示的强大策略&#xff0c;并在通用目标检测领域取得了显著突破。鉴于这一快速发…

绿虫光伏设计系统:清洁能源的未来

煤炭、石油、天然气是我们现在依赖的重要能源&#xff0c;但这些能源难以再生&#xff0c;而且开采过程中会产生污染。太阳能发电作为清洁能源的一种重要形式&#xff0c;受到了越来越多的关注。绿虫光伏发电系统&#xff0c;不仅考虑到其发电效率&#xff0c;还可以考虑其经济…

2025澄迈漓岛音乐节品牌招商大会成功举行

——共谋音乐盛事&#xff0c;携手推动文化经济发展 12月6日&#xff0c;“2025澄迈漓岛音乐节品牌招商大会”&#xff08;以下简称“招商大会”&#xff09;在澄迈举行。本次大会由澄迈福山发展有限公司、福山咖啡文化风情镇旅游区联合主办&#xff0c;海南绿发投资有限公司承…

基于Java和Vue开发的漫画阅读软件漫画阅读小程序漫画APP

前景分析 受众广泛&#xff1a;漫画的受众群体广泛&#xff0c;不仅限于青少年&#xff0c;还涵盖了成年人等多个年龄层和社会阶层。漫画文化在全球范围内的影响力不断扩大&#xff0c;未来漫画软件创业可以考虑全球市场的拓展。 市场需求大&#xff1a;数字化阅读趋势下&…

Lua中实现HTTP请求的User-Agent自定义

User-Agent&#xff08;用户代理&#xff09;是HTTP请求头的一部分&#xff0c;用于描述发出请求的客户端的信息&#xff0c;包括浏览器类型、版本和操作系统等。自定义User-Agent对于开发者来说是一个重要的功能&#xff0c;它可以帮助服务器识别请求来源&#xff0c;也可以模…

dell电脑开不了机怎么回事?戴尔电脑无法开机解决方法

dell戴尔电脑开不了机&#xff0c;这是很多使用dell电脑用户常遇到的问题。这种故障情况是由多种原因引起&#xff0c;包括硬件故障、软件问题或电源问题等等。dell电脑开不了机怎么办呢&#xff1f;下面便为大家介绍一下相关解决修复方法&#xff0c;帮助用户解决戴尔电脑无法…

148. 排序链表(java) 标记一下

我的思路是插入排序&#xff0c;然而时间超出限制&#xff01;&#xff01;&#xff01; 时间复杂度是 O(nlogn) 的排序算法包括归并排序、堆排序和快速排序&#xff08;快速排序的最差时间复杂度是 O(n 2)&#xff09;&#xff0c;最适合链表的排序算法是归并排序。 题目描述…

C语言实验 二维数组

时间:2024.12.6 一、实验 7-1 矩阵运算 代码 #include<stdio.h> int main(){int a[20][20]={0};int n,i,j;int sum=0;scanf("%d",&n);for(i=0;i<n;i++){for(j=0;j<n;j++){scanf("%d",&a[i][j]);if((i!=n-1)&&(j!=n-1)&am…

物通博联,smart200配置

设备&#xff1a;物通博联smart200plc交换机 网线直连&#xff0c;并网&#xff0c;本地链接&#xff0c;电脑直连 配置电脑ip&#xff0c;以太网修改ip网段为2段&#xff0c;ping通192.168.2.1 浏览器访问192.18.2.1&#xff0c;输入账号密码进入后台配置页面 &#xff0c;…

租赁小程序的优势与应用场景解析

内容概要 租赁小程序&#xff0c;听起来是不是很酷&#xff1f;其实&#xff0c;它就是一个让你可以方便地租借各种高成本但用得不频繁的商品的平台。想象一下&#xff0c;当你需要租一件派对用的华丽小礼服&#xff0c;或是想体验一下超酷的运动器材&#xff0c;租赁小程序就…

最简单的线性回归神经网络

数据&#xff1a; # 线性回归 import torch import numpy as np import matplotlib.pyplot as plt# 随机种子&#xff0c;确保每次运行结果一致 torch.manual_seed(42)# 生成训练数据 X torch.randn(100, 3) # 100 个样本&#xff0c;每个样本 3 个特征 true_w torch.tenso…