基于 WebSocket、Spring Boot 教你实现“QQ聊天功能”的底层简易demo
目录
前言
一、分析
1.1、qq聊天功能分析
1.2、WebSocket介绍
1.2.1、什么是消息推送呢?
1.2.2、原理解析
1.2.3、报文格式
二、简易demo
2.1、后端实现
2.1.1、引入依赖
2.1.2、继承TextWebSocketHandler
2.1.3、实现 WebSocketConfigurer 接口
2.2、前端实现
2.3、执行效果
前言
基于 WebSocket、Spring Boot 下,用一个简易的小 demo 讲解一下“QQ聊天”功能原理~
一、分析
1.1、qq聊天功能分析
用户看到的实际效果:用户A 给用户B 发送了一条消息,用户B在聊天框看到了消息,接着用户B回复消息......
底层实现:主机A 给服务器发送了一个 HTTP 请求,这个请求中就包含了用户A 要给用户B 发送的消息,服务器将收到的请求进行解析,在将响应发送给 主机B,这个响应中就包含了用户A 要发送的消息~
对于以上场景,我们应该如何实现呢?接着往下看!
1.2、WebSocket介绍
WebSocket 是从 HTML5 开始支持的一种网页端和服务端保持长连接的 消息推送机制。
1.2.1、什么是消息推送呢?
传统的 web 程序都是 “一问一答” 的形式,客户端给服务器发送了个 HTTP 请求,服务器根据请求计算响应返回客户端。显然,这种传统的 web 程序如果客户端不主动发起请求,服务器就无法主动给客户端发送响应~
Ps:若想使用原生的 HTTP 协议实现消息推送,需要进行“轮询”,反复询问服务器是否收到请求,成本太高
而 WebSocket 则是更接近 TCP 这种级别的通信方式——一旦连接建立完成,客户端或服务器都可以主动向对方发送数据。
1.2.2、原理解析
WebSocket 协议本质上是基于 TCP 协议的,为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,通过这个附加头信息完成握手过程~

1.2.3、报文格式

- FIN: 为 1 表示要断开 websocket 连接。
- RSV1/RSV2/RSV3: 保留位, 一般为 0。
- opcode: 操作代码. 决定了如何理解后面的数据载荷。
- mask: 表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。(掩码算法主要是为了安全考虑的,避免缓冲区溢出攻击)。
- Payload length:数据载荷的长度,单位是字节。为7位,或7+16位,或1+64位。
- payload data: 报文携带的载荷数据。
Spring 中内置了 websocket ,可以直接进行使用,如何使用?往下看~
二、简易demo
2.1、后端实现
2.1.1、引入依赖
创建一个 Spring Boot 项目,接着我们需要引入 Spring Web 依赖,以及 Web Socket 依赖。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
2.1.2、继承TextWebSocketHandler
这里我们先创建一个类,自定义名为 TestAPI ,继承 TextWebSocketHandler 类,重写这个类下4个最关键的方法:
- afterConnectionEstablishe:客户端与服务器成功建立连接以后会调用此方法。
- handleTextMessage:客户端通过 WebSocket 类下的 send 方法发送给服务器数据时会调用此方法。
- handleTransportError:客户端与服务器连接异常时会调用此方法。
- afterConnectionClosed:客户端与服务器断开连接时会调用此方法。
这个类就是用来处理 WebSocket 相关请求的~
于是,我们就可这样写:
import org.springframework.stereotype.Component;
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;@Component
public class TestAPI extends TextWebSocketHandler {@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {System.out.println("连接成功");}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {System.out.println("收到消息:" + message.getPayload());//收到消息后向客户端反馈信息(这里反馈相同数据)session.sendMessage(message);}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {System.out.println("连接异常");}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {System.out.println("连接关闭");}}
2.1.3、实现 WebSocketConfigurer 接口
第一步,创建一个类,自定义类名为WebSocketConfig,实现 WebSocketConfigurer接口,这个接口下只需要实现一个方法 registerWebSocketHandlers,这个类就是用来注册添加 WebSocket (当前项目要添加的就是我们刚刚写的 TestAPI 这个类)以及注册路由(这个路由就将客户端与服务器联系起来了:前端创建 WebSocket 实例时,就需要写下对应的路由)。
第二步、注入 TestAPI 类,最后使用 addHander方法添加注册这个类,基于这个类才能进一步的找到 TestAPI 这个类,去处理 WebSocket相关的请求~
代码实现如下:
import com.example.demo.api.TestAPI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;@Configuration
@EnableWebSocket //启动 WebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Autowiredprivate TestAPI testAPI;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {webSocketHandlerRegistry.addHandler(testAPI, "/test");}
}
Ps:
@EnableWebSocket注解是用来让 Spring 框架认识这个类是用来配置 webSocket 的类,基于这个类才能进一步的找到 TestAPI 这个类,去处理 WebSocket相关的请求~
2.2、前端实现
前端需要创建 WebSocket 实例,并写下相应路由,这个路由就是与后端 webSocket 相对应的那个路由,如下:

接着前端也可以通过 WebSocket 的4个重要方法,根据响应打印对应日志:
- onopen:客户端与服务器建立连接后会调用该方法。
- onmessage:客户端收到服务器响应后会触发该方法。
- onerror:客户端与服务器连接异常时会触发该请求。
- conclose:客户端与服务器断开连接时会触发该请求。
最后通过按钮输入框的形式来发送相应的 HTTP 请求,代码如下:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><input type="text" id="message"><button id="submit">提交</button><script>// 创建 websocket 示例let websocket = new WebSocket("ws://127.0.0.1:8080/test");websocket.onopen = function() {console.log("建立连接");}websocket.onmessage = function(e) {console.log("收到消息: " + e.data);}websocket.onerror = function() {console.log("连接异常");}websocket.onclose = function() {console.log("连接关闭");}let input = document.querySelector("#message");//点击按钮发送请求let button = document.querySelector("#submit");button.onclick = function() {console.log("发送消息:" + input.value);websocket.send(input.value);}</script></body>
</html>
2.3、执行效果
启动程序,输入对应URL,如下结果;

发送消息,点击按钮(相当于主机A 给主机B 发送消息,这里实现的是一个简易版,是一个回显服务),如下结果:

最后关闭浏览器,断开连接:

这样一个简单 demo 就完成了~

相关文章:
基于 WebSocket、Spring Boot 教你实现“QQ聊天功能”的底层简易demo
目录 前言 一、分析 1.1、qq聊天功能分析 1.2、WebSocket介绍 1.2.1、什么是消息推送呢? 1.2.2、原理解析 1.2.3、报文格式 二、简易demo 2.1、后端实现 2.1.1、引入依赖 2.1.2、继承TextWebSocketHandler 2.1.3、实现 WebSocketConfigurer 接口 2.2、…...
13. 郭老师爱合并果子
1 题目描述 郭老师爱合并果子成绩20开启时间2021年10月8日 星期五 18:00折扣0.8折扣时间2021年10月26日 星期二 00:00允许迟交否关闭时间2021年12月1日 星期三 00:00 郭老师家有个果园,每年到了秋收的时候都会收获很多不同种类的果子。他决定把所有的果子合成一堆&…...
Method breakpoints may dramatically slow down debugging 解决方案
项目无法启动了 简单介绍一下事情的过程:昨天在进行代码调试的时候,代码部分处理完成之后,启动debug模式的热部署准备测试一下逻辑,结果左下角提示我热部署失败,需要重新启动Tomcat才能再次调试,所以只得重…...
ABAP ALV和OOALV设置单元格颜色,编辑
首先给大家分享一篇博客: REUSE_ALV_GRID_DISPLAY_LVC-可编辑单元格 文章目录单元格编辑单元格/行-颜色效果展示**需求:**我是想实现某个单元格可根据数据来判断是否是可以进行编辑的或要添加一个什么样的颜色. 我们需要用到下面的三个结构 ALV 控制: 单元格的类型表:LVC_T_ST…...
Java知识复习(十三)数据库和SQL
1、主键和外键 主键也叫主码。主键用于唯一标识一个元组,不能有重复,不允许为空。一个表只能有一个主键。外键也叫外码。外键用来和其他表建立联系用,外键是另一表的主键,外键是可以有重复的,可以是空值。一个表可以有…...
JVM虚拟机种类
1,Sun Classic VM: 1.现在此款虚拟机已经淘汰了,是历史上第一款商用的虚拟机。2.只能使用纯解释器的方式来执行Java代码。3.服役于 JDK 1.0、1.1、1.2;在 1.3、1.4 作为 HotSpot VM 的备选 VM;之后退出历史舞台;2,Sun Exact VM 1.…...
Linux操作系统学习(线程基础)
文章目录线程的基础概念线程控制内核LWP和线程ID的关系线程的基础概念 一般教材对线程描述是:是在进程内部运行的一个分支(执行流),属于进程的一部分,粒度要比进程更加细和轻量化 一个进程中是可能存在多个线程…...
YOLOv5源码逐行超详细注释与解读(1)——项目目录结构解析
前言 前面简单介绍了YOLOv5的网络结构和创新点(直通车:【YOLO系列】YOLOv5超详细解读(网络详解)) 在接下来我们会进入到YOLOv5更深一步的学习,首先从源码解读开始。 因为我是纯小白,刚开始下…...
前端开发总结的一些技巧和实用方法(2)
本文主要介绍一些JS中用到的小技巧和实用方法,可以在日常Coding中提升幸福度,也可以通过一些小细节来增加代码可读性,让代码看起来更加优雅,后续将不断更新1.数组 map 的方法 (不使用Array.Map) Array.from 还可以接受第二个参数…...
Docker搭建jenkins(Vue自动化部署)
前言 需要提前准备的条件 Docker环境 一、jenkins镜像 # 查询镜像 docker search jenkins# 下载镜像 # lts稳定版 docker pull jenkins/jenkins:lts#查看镜像 docker images二、启动Jenkins容器 创建挂载文件夹,并且进行文件授予权限 #创建文件夹 mkdir -p /home/j…...
ADCS攻击之CVE-2022–26923
CSDN自动博客文章迁移漏洞简介该漏洞允许低权限用户在安装了 Active Directory 证书服务 (AD CS) 服务器角色的默认 Active Directory 环境中将权限提升到域管理员。在默认安装的ADCS里就启用了Machine模板。漏洞利用添加机器账户,并将该机器账户dnsHostName指向DC[…...
AO3401-ASEMI低压P沟道MOS管AO3401
编辑:ll AO3401-ASEMI低压P沟道MOS管AO3401 型号:AO3401 品牌:ASEMI 封装:SOT-23 最大漏源电流:-4.2A 漏源击穿电压:-30V RDS(ON)Max:0.05Ω 引脚数量࿱…...
【STM32MP157应用编程】3.控制PWM
目录 PWM文件 指令操作PWM 程序操作PWM 程序说明 程序代码 3_PWM_1.c 启动交叉编译工具 编译 拷贝到开发板 测试 PWM文件 在/sys/class/pwm目录下,存放了PWM的文件。 pwmchip0和pwmchip4目录对应了MP157 SoC的2个PWM控制器,pwmchip0对应的是M…...
基于Python的selenium
一、安装 1.1安装Python,安装Python时需要勾选增加环境变量 如果之前已经安装过Python,需要将Python相关文件以及环境变量删除 1.2安装成功:在命令行界面下输入Python,最终展示>>>即可成功 2.1安装pycharm,直接自定义安装…...
Go底层原理:一起来唠唠GMP调度(一)
目录前言一、进程、线程、Goroutine1、进程与线程2、Goroutine二、Go调度器设计思想1、线程模型1.1 内核级线程模型1.2 用户级线程模型1.3 混合型线程模型2、 被废弃的 G-M 调度器2.1 了解 G-M 调度如何工作3、如今高效的 GMP 模型3.1 GMP模型调度流程3.2 GMP调度设计策略3.3 G…...
前端——1.相关概念
这篇文章主要介绍前端入门的相关概念 1.网页 1.1什么是网页? 网站:是指在因特网上根据一定的规则,使用HTML等制作的用于展示特定内容相关的网页集合 网页:是网站中的一“页”,通常是HTML格式的文件,它要…...
java四种线程池(基本使用)
标题java四种线程池及使用示例 1、线程工厂 1、我们先来写ThreadFactory,在创建线程池时候可以传入自定义的线程工厂,线程工厂说白了就是用来定制线程的一些属性:名字、优先级、是否为守护线程。直接看代码即可。 当然创建线程池的时候可以…...
float的表示范围为什么比long大
●很多人会有一个疑问, 一个用来表示小数的 float 为什么表示的范围会比 long 还要大呢 ? ●这次, 咱们就来详细说一说这个事情 从长计议 ●聊到这个话题, 我们就要从计算机存储数字这个位置说起了 ●计算机存储数字的方式其实就是 : 二进制 二进制是计算机中最基本的数字存储…...
Flutter Android 打包保姆式全流程 2023 版
大家好,我是 17。 为什么要写这篇文章呢?对于一没有 android 开发经验,从未有过打包经历的新人来说,要想成功打包,是很困难的。因为受到的阻碍太多,是完全陌生的领域,几乎是寸步难行。如果有老…...
C++笔记之lambda表达式
引言 Lambda表达式是从C 11版本引入的特性,利用它可以很方便的定义匿名函数对象,通常作为回调函数来使用。大家会经常拿它和函数指针,函数符放在一起比较,很多场合下,它们三者都可以替换着用。 语法 [ captures ] (…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
css实现圆环展示百分比,根据值动态展示所占比例
代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
CTF show 数学不及格
拿到题目先查一下壳,看一下信息 发现是一个ELF文件,64位的 用IDA Pro 64 打开这个文件 然后点击F5进行伪代码转换 可以看到有五个if判断,第一个argc ! 5这个判断并没有起太大作用,主要是下面四个if判断 根据题目…...
