基于 WebSocket、Spring Boot 教你实现“QQ聊天功能”的底层简易demo

news/2024/11/29 1:46:22/

目录

前言

一、分析

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 就完成了~


http://www.ppmy.cn/news/30380.html

相关文章

SpringBoot With IoC,DI, AOP,自动配置

文章目录1 IoC&#xff08;Inverse Of Controller&#xff09;2 DI&#xff08;Dependency Injection&#xff09;3 AOP&#xff08;面向切面编程&#xff09;3.1 什么是AOP&#xff1f;3.2 AOP的作用&#xff1f;3.3 AOP的核心概念3.4 AOP常见通知类型3.5 切入点表达式4 自动配…

【Linux】-- 基本指令

目录 用户管理 adduser passwd userdel pwd ls指令 -l -a -d -F -r -t -R -1 which alias ll ls -n cd cd - cd ~ touch -d stat mkdir -p rmdir rm -r -f man cp ​编辑 -r -f mv cat -n tac more less -N head tail | 管道 dat…

Android Compose——一个简单的Bilibili APP

Bilibili移动端APP简介依赖效果登录效果WebView自定义TobRow的Indicator大小首页推荐LazyGridView使用Paging3热门排行榜搜索模糊搜索富文本搜索结果视频详情合集信息Coroutines进行网络请求管理&#xff0c;避免回调地狱添加suspendwithContextGit项目链接末简介 此Demo采用A…

koa-vue的分页实现

1.引言 最近确实体会到了前端找工作的难处&#xff0c;不过大家还是要稳住心态&#xff0c;毕竟有一些前端大神说的有道理&#xff0c;前端发展了近20年&#xff0c;诞生了很多leader级别的大神&#xff0c;这些大神可能都没有合适的坑位&#xff0c;我们新手入坑自然难一些&am…

hive真实表空间大小统计

1. 问题 如果是采用hdfs上传加载的表、或者是flume直接写hdfs的表空间通常看hive的属性是不准确的。 2. 思路 为了使结果更精确&#xff0c;我们直接使用linux下命令统计hive仓库目录下的每个表对应的文件夹目录占用空间的大小。 3. 解决方法 这里建立三层表结构 ods: 原始…

Cadence Allegro 导出Component Pin Report详解

⏪《上一篇》   🏡《总目录》   ⏩《下一篇》 目录 1,概述2,Component Pin Report作用3,Component Pin Report示例4,Component Pin Report导出方法4.1,方法14.2,方法2B站关注“硬小二”浏览更多演示视频 <

看完这篇入门性能测试

几十万人同时进入某媒体直播间&#xff0c;造成流量激增从监控上可以看出&#xff0c;QPS到达某峰值后&#xff0c;部分进入直播间即调用的接口&#xff08;这类接口属于高并发接口&#xff0c;在短时间内承载非常高的QPS&#xff09;以及对应接口的下游&#xff0c;均出现了非…

三个案例场景带你掌握Cisco交换机VLAN互通

VLAN间路由的方式现在主流的组网主要是依靠三层交换机通过配置SVI接口【有的厂商叫VLANIF接口】&#xff0c;当然也有比较小型的网络&#xff0c;它就一个出口路由器可管理的二层交换机&#xff0c;还有一种更加差的&#xff0c;就是出口路由一个可管理的二层交换机&#xff0c…