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

在现代应用中,实时协作已经成为了非常重要的功能,尤其是在文档编辑、聊天系统和在线编程等场景中。通过实时共享文档,多个用户可以同时对同一份文档进行编辑,并能看到其他人的编辑内容。这种功能广泛应用于 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 仍然是一个非常强大的工具,适用于许多实时协作的场景。

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

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

相关文章

统信桌面专业版部署postgresql-14.2+postgis-3.2方法介绍

文章来源&#xff1a;统信桌面专业版部署postgresql-14.2postgis-3.2方法介绍 | 统信软件-知识分享平台 应用场景 CPU架构&#xff1a;X86&#xff08;海光C86-3G 3350&#xff09; OS版本信息&#xff1a;1070桌面专业版 软件信息&#xff1a;postgresql-14.2postgis-3.2 …

jmeter 提取数据写入文件

BeanShell PostProcessor FileWriter file new FileWriter("E:\\IOT\\cui家庭中心\\v3.8.0\\123.txt",true); BufferedWriter out new BufferedWriter(file); out.write(vars.get("localKey")"\n"); log.info("到这里了吗"); out.c…

在ensp中ACL路由控制实验

一、实验目的 掌握ACL路由控制管理 二、实验要求 要求&#xff1a; 配置路由策略&#xff0c;左右两边不公开区域对方不可达&#xff0c;其他区域可以互相ping通 设备&#xff1a; 1、三台路由器 2、四台交换机 3、四台电脑 4、四台服务器 使用ensp搭建实验环境,如图所…

MySQL 实现分库分表详解

MySQL 实现分库分表详解 为什么要分库分表什么是分库分表分库分表的几种方式垂直拆分数据库垂直拆分表垂直拆分垂直拆分特点垂直拆分优缺点优点缺点 水平拆分数据库水平拆分表水平拆分水平拆分的其他方式水平拆分特点水平拆分优缺点优点缺点 分库分表带来的问题分库分表技术如何…

如何让Google快速收录你的页面?

要让Google更快地收录你的网站内容&#xff0c;首先需要理解“爬虫”这个概念。Google的爬虫是帮助它发现和评估网站内容质量的工具&#xff0c;如果你的页面质量高且更新频率稳定&#xff0c;那么Google爬虫更可能频繁光顾。通常情况下&#xff0c;通过Google Search Console&…

游戏引擎学习第36天

仓库 :https://gitee.com/mrxiao_com/2d_game 回顾之前的内容 在这个程序中&#xff0c;目标是通过手动编写代码来从头开始制作一个完整的游戏。整个过程不使用任何库或现成的游戏引擎&#xff0c;这样做的目的是为了能够全面了解游戏执行的每一个细节。开发过程中&#xff0…

【Linux】系统信息和状态命令

步骤 1&#xff1a;显示系统信息 命令&#xff1a; uname -a 1.打开终端。 2.输入命令并按回车键。 3.观察&#xff1a;输出将显示包括内核版本、主机名、硬件架构等在内的系统信息。 步骤 2&#xff1a;显示或设置系统的主机名 命令&#xff1a; hostname 1.打开终端。…

IDEA创建Spring Boot项目配置阿里云Spring Initializr Server URL【详细教程-轻松学会】

1.首先打开idea选择新建项目 2.选择Spring Boot框架(就是选择Spring Initializr这个) 3.点击中间界面Server URL后面的三个点更换为阿里云的Server URL Idea中默认的Server URL地址&#xff1a;https://start.spring.io/ 修改为阿里云Server URL地址&#xff1a;https://star…

获得日志记录之外的新视角:应用程序性能监控简介(APM)

作者&#xff1a;来自 Elastic David Hope 日志记录领域即将发生改变。在这篇文章中&#xff0c;我们将概述从单纯的日志记录到包含日志、跟踪和 APM 的完全集成解决方案的推荐流程。 通过 APM 和跟踪优先考虑客户体验 企业软件开发和运营已成为一个有趣的领域。我们拥有一些非…

Qt之第三方库‌QXlsx使用(三)

Qt开发 系列文章 - QXlsx&#xff08;三&#xff09; 目录 前言 一、Qt开源库 二、QXlsx 1.QXlsx介绍 2.QXlsx下载 3.QXlsx移植 4.修改项目文件.pro 三、使用技巧 1.添加头文件 2.写入数据 3.读出数据 总结 前言 Qt第三方控件库是指非Qt官方提供的、用于扩展Qt应用…

Codeforces Round 992 (Div. 2)

传送门&#xff1a;Dashboard - Codeforces Round 992 (Div. 2) - Codeforces A. Game of Division 思路&#xff1a;模拟 AC代码&#xff1a;Submission #295676347 - Codeforces B. Paint a Strip 思路&#xff1a;数学 贪心 放置的位置一定是 1 4 10 22 48 ....…

MySQL并发控制(二):锁

只改一行语句&#xff0c;为什么锁那么多 注1&#xff1a;MySQL后面的版本可能会改变加锁策略&#xff0c; 所以这个规则只限于截止到现在的最新版本&#xff0c; 即5.x系列 注2&#xff1a;因为间隙锁在可重复读隔离级别下才有效&#xff0c; 所以本篇文章接下来的描述&#…

ThinkPHP+Layui开发的ERP管理系统

ERP采购生产销售系统&#xff0c;一款基于ThinkPHPLayui开发的ERP管理系统&#xff0c;帮助中小企业实现ERP管理规范化&#xff0c;此系统能为你解决五大方面的经营问题&#xff1a;1.采购管理 2.销售管理 3.仓库管理 4.资金管理 5.生产管理&#xff0c;适用于&#xff1a;服装…

vue的初步使用

一. vue的初步使用 1.引入相关依赖 //<!-- 引入一个vue文件 --><script src"https://cdn.jsdelivr.net/npm/vue2.7.16/dist/vue.js"></script>2. 给出相应的数据 <!DOCTYPE html> <html lang"en"> <head><meta ch…

计算机网络-Wireshark探索ARP

使用工具 Wiresharkarp: To inspect and clear the cache used by the ARP protocol on your computer.curl(MacOS)ifconfig(MacOS or Linux): to inspect the state of your computer’s network interface.route/netstat: To inspect the routes used by your computer.Brows…

开发一套SDK 第一弹

自动安装依赖包 添加条件使能 #ex: filetypesh bash_ls 识别 达到预期,多个硬件环境 等待文件文件系统挂在完成 或者创建 /sys/class/ 属性文件灌入配置操作 AI 提供的 netlink 调试方法,也是目前主流调用方法,socket yyds #include <linux/module.h> #include <linux…

Facebook 人工智能:重塑社交新未来

在数字化迅速发展的今天&#xff0c;人工智能(AI)已经深入了我们的生活&#xff0c;尤其是在社交媒体领域。Facebook作为全球最大的社交平台之一&#xff0c;正利用AI技术&#xff0c;革新其服务和用户体验&#xff0c;为用户打造社交互动的新未来。 首先&#xff0c;人工智能…

Plugin - 插件开发03_Spring Boot动态插件化与热加载

文章目录 Pre方案概览使用插件的好处流程CodePlugin 定义Plugin 实现Plugin 使用方动态加载插件类加载器注册与卸载插件配置文件启动类测试验证 小结 Pre 插件 - 通过SPI方式实现插件管理 插件 - 一份配置&#xff0c;离插件机制只有一步之遥 插件 - 插件机制触手可及 Plug…

从单体到微服务:如何借助 Spring Cloud 实现架构转型

一、Spring Cloud简介 Spring Cloud 是一套基于 Spring 框架的微服务架构解决方案&#xff0c;它提供了一系列的工具和组件&#xff0c;帮助开发者快速构建分布式系统&#xff0c;尤其是微服务架构。 Spring Cloud 提供了诸如服务发现、配置管理、负载均衡、断路器、消息总线…

Flink学习连载文章13--FlinkSQL高级部分

eventTime 测试数据如下&#xff1a; {"username":"zs","price":20,"event_time":"2023-07-17 10:10:10"} {"username":"zs","price":15,"event_time":"2023-07-17 10:10:3…