WebSocket 快速入门 - springboo聊天功能

2024-04-22 06:52

本文主要是介绍WebSocket 快速入门 - springboo聊天功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、概述 

1、HTTP(超文本传输协议)

2、轮询和长轮询 

3、WebSocket

二、WebSocket快速使用

1、基于Java注解实现WebSocket服务器端

2、JS前端测试

三、WebSocket进阶使用

1、如何获取当前用户信息

2、 后端聊天功能实现


一、概述 

HTTP和WebSocket都是啥?

1、HTTP(超文本传输协议)

        比如我们去逛某宝的商品列表,从HTTP协议的角度来看,前端发送了一次HTTP请求,服务器返回一次HTTP响应。不过从始至终服务器都不会主动给客户端发送消息请求,这就是HTTP协议的特点。

        HTTP有轮询和长轮询是两种客户端与服务器之间进行数据交换的技术,常用于实现网页上的实时数据更新。这些技术在 WebSocket 出现之前特别流行,尤其是在需要从服务器实时获取更新的场景中。

2、轮询和长轮询 

轮询是一种客户端定时发送请求到服务器的技术,以检查是否有新数据可用。这是一种非常直接的方法,客户端每隔一定时间(如每5秒)向服务器发起HTTP请求,无论服务器是否真的有新数据提供。

  1. 客户端发起HTTP请求到服务器。
  2. 服务器立即响应,返回数据(如果有新数据)或者返回一个空响应。
  3. 客户端处理响应,然后等待一定的时间。
  4. 时间到了之后,客户端再次发送请求。
  5. 这个过程持续重复。

 缺点也很明显,这样频繁的向后端发起请求必定会给服务器带来大量不必要的数据流量和服务器负载,而且实时性不足,为此有了长轮询

长轮询是轮询的一个改进版本,可以提供更快的响应时间和更少的服务器负载。与传统轮询不同,长轮询在服务器没有数据可发送时不会立即响应客户端的请求。而是保持连接开放,直到有数据发送或达到预设的超时时间。

  1. 客户端发送HTTP请求到服务器。
  2. 服务器不立即响应。它将请求挂起,直到有新数据可用或超时。
  3. 一旦有新数据可用,服务器立即响应请求,发送数据给客户端。
  4. 客户端收到数据并处理后,立即发起新的请求,重复此过程。

这样减少了不必要的HTTP请求,因为每次请求都可能得到有效数据,而且响应速度更快,因为数据一旦可用,客户端会立即收到通知。

3、WebSocket

        又比如我们玩传奇一刀999的网页游戏,我们甚至全程都没有点一次鼠标,但是服务器就源源不断地将怪物的移动数据和攻击数据发给我们。这种服务器可以主动给客户端发送消息的可双向传输数据场景就是使用了WebSocket协议。

底层原理:

  • WebSocket协议建立在TCP协议基础上,所以服务器也容易实现,不同的语言都有支持。
  • tcp协议是全双工协议,http协议基于它,但设计成了单向
  • WebSocket没有同源限制。(什么是同源看这里: localhost跨域问题解决-CSDN博客)
  • WebSocket连接开始于一个HTTP请求,这个过程叫做握手这个请求包含特定的头部信息,表明客户端希望将连接“升级”为WebSocket。如果服务器接受请求,它会返回一个响应,同意升级到WebSocket。此后,这个TCP连接将用于WebSocket通信,而不是传统的HTTP请求和响应。

至于Socket协议,套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。它偏向于底层,与WebSocket的关系就如Java和JavaScript,可以说是除了名字相似之外没什么关系。 


二、WebSocket快速使用

1、基于Java注解实现WebSocket服务器端

需要使用到的类

首先这里需要着重说明一下,WebSocket中的Session对象与传统的前端发起HTTP请求时携带的那个session( token )概念是有所不同的。

它们有以下关键区别:

1、WebScokeT

  • Session在这里指的是由 Jakarta WebSocket API 提供的一个对象,它代表了一个 WebSocket 连接。
  • 每个 WebSocket的Session是独立的,代表了服务器与一个特定客户端之间的持续连接。
  • WebSocket连接是全双工的,允许数据在任何时候从任一端向另一端发送,适用于需要实时数据交换的场景。

2、HTTP

  • 在HTTP中,session 通常是用来在多个请求之间保存用户数据的一种方式,常通过 cookie 或 session 存储在服务器端。
  • Token,如 JWT(Json Web Tokens),通常用于身份验证和保持状态,是一个可以在客户端和服务器之间安全传输的加密信息。
  • HTTP请求是无状态的,通常每次请求之间不直接保持连接,每个请求都需要重新进行连接和身份验证。
/*** 监听WebSocket地址/ws*/
@ServerEndpoint("/ws")
@Component
@Slf4j
public class WebSocketServerEndpoint {//线程安全的HashMapstatic Map<String,Session> sessionMap = new ConcurrentHashMap<>();//websocket连接成功就立即触发该注解修饰的方法@OnOpenpublic void onOpen(Session session){sessionMap.put(session.getId(),session);log.info("WebSocket is open");}//websocket收到客户端消息便会立即触发该方法@OnMessagepublic String onMessage(String message){log.info("收到了一条消息: " + message);return "已经收到了你的消息";}//websocket关闭连接就立即触发该方法@OnClosepublic void onClose(Session session){sessionMap.remove(session);log.info("Websocket is close");}}

因此我们在这段基础代码中看到Session对象,实际上它就是每一个与该WebSocket服务器连接的独立的客户端对象。 

此时还不可以直接使用,虽然已经使用@Component修饰,但是还需要使用ServerEndpointExporter,它会扫描所有声明了@ServerEndpoint注解的Beans,并将它们注册到WebSocket服务器,使它们能够接收WebSocket连接请求。

@Configuration
public class WebSocketConfig {/*** 这个bean对象可以自动注册使用了@ServerEndpoint的Bean*/@Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}}

此时一个最基础的WebSocket服务器就搭建好了。

2、JS前端测试

写一个最简单的按钮,点击触发向ws发送一个Hello消息

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>ws-client</title>
</head>
<body>
<button id="sendMessage">打招呼!
</button>
</body>
<script>const button = document.getElementById('sendMessage');button.addEventListener("click", () => {const ws = new WebSocket("ws://localhost:8888/ws");ws.onopen = function () {console.log("Connection opened!");ws.send("hello!");};ws.onmessage = function (message) {console.log("Received:", message.data);};});
</script>
</html>


可以看到我们点击了按钮向后端发送了hello,之后后端就自动调用了@onMessage方法,向我们这个Session对象返回了消息message,最终调用了前端的onmessage将信息打印在了控制台上。

不过这样看起来似乎和一个HTTP协议没什么区别,同样都是前端发送一个请求然后后端返回一个数据。因此为了体现websocket能够主动的向客户端发送信息我们写一个定时器每两秒向服务器发送数据。

首先要在启动类上加上@EnableScheduling注解,这是用于在 Spring 应用中启用基于注解的定时任务。当你在配置类上加上@EnableScheduling注解后,Spring 的任务调度器将会被激活,使得你可以使用@Scheduled注解来定义具体的定时任务。
'

 /*** 模拟服务器每两秒主动向客户端发起请求* @throws IOException*/@Scheduled(fixedDelay = 2000)public void sendMessage() throws IOException {for (String key: sessionMap.keySet()){sessionMap.get(key).getBasicRemote().sendText("心跳");}}

成功实现服务器每2秒向客户端发起一次数据 


三、WebSocket进阶使用

1、如何获取当前用户信息

        我们平常做一个软件,肯定都会使用到身份验证用于区分不同的用户。但是我们前面刚刚提到过WebSocket的Session与Http的Session不是同一个东西,那么我们想要实现两个用户聊天该如何实现呢?

public class GetTokenConfig extends ServerEndpointConfig.Configurator {@Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {// 获取请求头中的token(假设token存放在名为"Authorization"的请求头中)List<String> tokenHeader = request.getHeaders().get("Authorization");if (tokenHeader != null && !tokenHeader.isEmpty()) {String token = tokenHeader.get(0);// 将token保存起来sec.getUserProperties().put("token", token);}}
}

在这段代码中,我们创建一个类继承ServerEndpointConfig.Configurator,重写其中的modifyHandshake方法

        其实顾名思义,就是对WebSocket的握手进行了操作,我们一开始就提到过,WebSocket开始于一个HTTP请求,这个过程被称为握手,之后才开始WebSocket连接。虽然WebSocket本身并没有在建立连接后发送请求头的能力,不过在一开始握手的时候中是可以携带请求头的。

其中HandshakeRequest虽然与我们常用的HttpRequest没有什么关系,不过在使用的时候你可以简单粗暴的将它看作HttpRequest。

其次的ServerEndpointConfig其实就是EndpointConfig,将token存储在这个里面就可以方便别的类使用这个token了

我们稍微修改一下我我们之前的代码

/*** 监听WebSocket地址/ws*/
@ServerEndpoint(value = "/ws", configurator = GetTokenConfig.class)
@Component
@Slf4j
public class WebSocketServerEndpoint {@Autowiredprivate JwtProperties jwtProperties;static Map<String, WebSocketServerEndpoint> onlineUsers = new ConcurrentHashMap<>();private Session session;private String token;//websocket连接成功就立即触发该注解修饰的方法@OnOpenpublic void onOpen(Session session, EndpointConfig config) {log.info("WebSocket is open,连接成功");this.session = session;// 从EndpointConfig获取tokenString token = (String) config.getUserProperties().get("token");this.token = token;log.info("token: " + token);this.jwtProperties = BeanUtil.getBean(JwtProperties.class);//从token中获取一下用户信息Integer userId = JwtUtil.getUserIdFromToken(jwtProperties.getUserSecretKey(), token);String username = JwtUtil.getUsernameFromToken(jwtProperties.getUserSecretKey(), token);log.info("当前用户:ID: " + userId + " Username " + username);}}

使用Postman测试一下,因为JavaScript原生的Websocket的API没有直接携带请求头的能力

2、 后端聊天功能实现

/*** 监听WebSocket地址/ws*/
@ServerEndpoint(value = "/ws", configurator = GetTokenConfig.class)
@Component
@Slf4j
public class WebSocketServerEndpoint {@Autowiredprivate JwtProperties jwtProperties;static Map<Integer, Session> onlineUsers = new ConcurrentHashMap<>();private Integer userId;//websocket连接成功就立即触发该注解修饰的方法@OnOpenpublic void onOpen(Session session, EndpointConfig config) {log.info("WebSocket is open,连接成功");// 从EndpointConfig获取tokenString token = (String) config.getUserProperties().get("token");log.info("token: " + token);this.jwtProperties = BeanUtil.getBean(JwtProperties.class);//从token中获取一下用户信息Integer userId = JwtUtil.getUserIdFromToken(jwtProperties.getUserSecretKey(), token);String username = JwtUtil.getUsernameFromToken(jwtProperties.getUserSecretKey(), token);this.userId = userId;// 将userId存储在Session的用户属性中session.getUserProperties().put("userId", userId);log.info("当前用户:ID: " + userId + " Username " + username);onlineUsers.put(userId,session);}//websocket收到客户端消息便会立即触发该方法@OnMessagepublic void onMessage(String message,Session session) {//将message转换成Message对象try {ObjectMapper mapper = new ObjectMapper();Message msg = mapper.readValue(message,Message.class);//获取要将数据发送给的用户Integer receiver = msg.getReceiver();//获取消息数据String data = msg.getMessage();//获取当前登录的这个用户,也就是发送者Integer senderId = (Integer) session.getUserProperties().get("userId");ResultMessage resultMessage = ResultMessage.builder().message(data).sender(senderId).build();//发送onlineUsers.get(receiver).getBasicRemote().sendText(mapper.writeValueAsString(resultMessage));} catch (IOException e) {e.printStackTrace();}}//websocket关闭连接就立即触发该方法@OnClosepublic void onClose(Session session) {Integer userId = (Integer) session.getUserProperties().get("userId");if (userId != null) {onlineUsers.remove(userId);log.info("WebSocket is closed and user removed from onlineUsers: UserID = " + userId);} else {log.warn("User ID not found in session properties on close.");}}}

测试

这篇关于WebSocket 快速入门 - springboo聊天功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/925082

相关文章

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

Spring Boot + MyBatis Plus 高效开发实战从入门到进阶优化(推荐)

《SpringBoot+MyBatisPlus高效开发实战从入门到进阶优化(推荐)》本文将详细介绍SpringBoot+MyBatisPlus的完整开发流程,并深入剖析分页查询、批量操作、动... 目录Spring Boot + MyBATis Plus 高效开发实战:从入门到进阶优化1. MyBatis

SpringKafka消息发布之KafkaTemplate与事务支持功能

《SpringKafka消息发布之KafkaTemplate与事务支持功能》通过本文介绍的基本用法、序列化选项、事务支持、错误处理和性能优化技术,开发者可以构建高效可靠的Kafka消息发布系统,事务支... 目录引言一、KafkaTemplate基础二、消息序列化三、事务支持机制四、错误处理与重试五、性能优

SpringIntegration消息路由之Router的条件路由与过滤功能

《SpringIntegration消息路由之Router的条件路由与过滤功能》本文详细介绍了Router的基础概念、条件路由实现、基于消息头的路由、动态路由与路由表、消息过滤与选择性路由以及错误处理... 目录引言一、Router基础概念二、条件路由实现三、基于消息头的路由四、动态路由与路由表五、消息过滤

Spring Boot 3.4.3 基于 Spring WebFlux 实现 SSE 功能(代码示例)

《SpringBoot3.4.3基于SpringWebFlux实现SSE功能(代码示例)》SpringBoot3.4.3结合SpringWebFlux实现SSE功能,为实时数据推送提供... 目录1. SSE 简介1.1 什么是 SSE?1.2 SSE 的优点1.3 适用场景2. Spring WebFlu

基于SpringBoot实现文件秒传功能

《基于SpringBoot实现文件秒传功能》在开发Web应用时,文件上传是一个常见需求,然而,当用户需要上传大文件或相同文件多次时,会造成带宽浪费和服务器存储冗余,此时可以使用文件秒传技术通过识别重复... 目录前言文件秒传原理代码实现1. 创建项目基础结构2. 创建上传存储代码3. 创建Result类4.

springboot security快速使用示例详解

《springbootsecurity快速使用示例详解》:本文主要介绍springbootsecurity快速使用示例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录创www.chinasem.cn建spring boot项目生成脚手架配置依赖接口示例代码项目结构启用s

Python+PyQt5实现多屏幕协同播放功能

《Python+PyQt5实现多屏幕协同播放功能》在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需,下面我们就来看看如何利用Python和PyQt5开发一套功能强大的跨屏播控系统吧... 目录一、项目概述:突破传统播放限制二、核心技术解析2.1 多屏管理机制2.2 播放引擎设计2.3 专

一文详解SpringBoot响应压缩功能的配置与优化

《一文详解SpringBoot响应压缩功能的配置与优化》SpringBoot的响应压缩功能基于智能协商机制,需同时满足很多条件,本文主要为大家详细介绍了SpringBoot响应压缩功能的配置与优化,需... 目录一、核心工作机制1.1 自动协商触发条件1.2 压缩处理流程二、配置方案详解2.1 基础YAML