WebSocket实现在线聊天室

项目实现源码:

前端源码

后端源码

1.常见的消息推送方式

1.1 轮询

1.1.1 轮询的概念

客户端以固定的事件间隔(例如每秒或几分钟)向服务器发送HTTP请求,服务器收到请求后,处理请求并返回数据给客户端

轮询具体实现https://blog.csdn.net/m0_48333563/article/details/125968144

1.1.2 轮询的优点

  • 实现简单:轮询是一种相对简单的获取都武器更新的方法,易于理解和实现
  • 兼容性:由于轮询基于HYTTP请求和响应,因此他兼容几乎所有的网络服务和客户端

1.1.3 轮询的缺点

  • 数据更新不及时:客户端必须等待下一次轮询间隔才能接收到新数据,这可能导致数据更新不及时
  • 资源浪费:频繁的轮询可能会浪费服务器和客户端的资源,油漆是在没有新数据的情况下,大部分请求都是无效的。

1.2 长轮询

1.2.1长轮询的概念

长轮询是一种改进的轮询技术,客户端向服务器发送HTTP请求。服务器接收到请求后,会阻塞请求,直到有新数据达到指定的超时时间才会返回结果

  • 如果有新数据,服务器会立即返回结果并关闭连接
  • 如果没有新数据,服务器会在超时后关闭连接
  • 客户端收到响应或连接超时后,会再发送新的请求

1.2.2 长轮询的优点

  • 实时性提升:长轮询可以更快的接受到服务器的更新,因为他减少了客户端在两次请求之间的等待时长
  • 减少了无效请求:与定时轮询相比,长轮询减少了在没有数据更新时无效请求的次数,因为服务器仅在数据准备好时才发送响应。

1.2.3 长轮询的缺点

  • 资源占用:虽然轮询减少了请求次数,但他可能会长时间占用服务器资源,因为服务器需要保持连接打开直到新数据出现或超时。
  • 兼容性和复杂度:长轮询的实现比简单的轮询复杂,需要服务器编写额外的逻辑
@RestController
@RequestMapping("/longpolling")
public class LongPollingController {private static final ConcurrentHashMap<String, WebSocketSession> sessions = new ConcurrentHashMap<>();// 假设这是模拟的数据源private static final List<String> messages = new CopyOnWriteArrayList<>();@PostMapping("/subscribe")public Callable<String> subscribe(@RequestParam String clientId) {return () -> {// 模拟等待新数据synchronized (messages) {while (messages.isEmpty()) {try {messages.wait();} catch (InterruptedException e) {Thread.currentThread().interrupt();return null;}}String message = messages.remove(0);return message;}};}// 模拟数据推送public static void pushMessage(String message) {synchronized (messages) {messages.add(message);messages.notifyAll();}}// 注意:这里的示例为了简化并未真正使用WebSocketSession,实际中可能需要WebSocket来管理连接
}

1.3 WebSocket

1.4 SSE

SSE:服务器发送事件,主要用于服务器向客户端推送实时更新(不需要客户端主动请求)

  • SSE会在客户端和服务器之间打开一个单项通道
  • 服务器返回的不再是一次性的数据包,而是text/event-stream类型的数据流信息
  • 服务器有数据发生改变时会将数据以流的形式传输给客户端

SSE仅支持从服务器到客户端单项通信,客户端无法通过SSE发送到服务器

2.什么是WebSocket

2.1 全双工通信和半双工的概念

全双工:允许数据在两个方向上同时传输

半双工:允许数据在两个方向上传输,但是同一时间段只允许一个方向传输

2.2 WebScoket的概念

WebSocket 是一种基于 TCP 的网络通信协议,允许在客户端和服务器建立全双工的通信通道。这意味着客户端和服务器可以在任何时候互相发送消息,不需要像传统的 HTTP 请求那样等待响应。WebSocket 非常适合于需要实时更新数据的应用场景,如在线游戏、实时聊天、实时数据推送等

2.3WebSocket的原理

WebSocket 协议会在客户端和服务器之间建立一条持久的连接通道,连接建立后,双方可以在任意时间通过这个通道发送数据,每次请求无需重新建立连接

WebSocket 的数据传输是双向的,这意味着服务器可以主动向客户端推送数据,而不仅仅是响应客户端的请求

WebScoket连接建立的步骤:

1.客户端发起握手请求:客户端通过HTTP请求发起WebScoket握手请求

2.服务器响应握手请求:服务器收到握手请求后,如果同意升级协议,就会返回一个HTTP 101状态码,表示协议切换成功

3.连接建立:握手成功,客户端和服务器之间的连接切换成WebSockt协议,之后双方通过此连接进行双向通信。

3.浏览器中与WebSocket相关的API

3.1websocket对象创建

let ws = new WebSocket(URL);

3.2 websocket对象相关事件

3.3 websocket对象提供的方法

3.4具体代码实现:

3.4.1 前端

<script>let ws = new WebScoket("ws://localhost/chat");ws.onopen = function() {};ws.onmessage = function(evt) {// 通过evt.data 可以获取服务器发送的数据}
</script>

4.服务器API

Tomcat的7.0.5版本开始支持WebSocket,并实现了Java WebSocket规范。

Java WebSocket应用由一系列的Endpoint组成。Endpoint是一个java对象,代表WebSocket链接的一端,对于服务器,我们可以视为处理具体的WebSocket消息的接口。

我们可以通过两种方式定义Endpoint:

第一种是编程式,继承javax.websocket.Endpoint并实现其方法

第二种是注解式,定义一个POJO,并添加@ServerEndpoint

Endpoint实例在WebSocket握手时创建,并在客户端与服务器连接过程中有效,在最后关闭连接时结束。在Endpoint接口中明确定义与其生命周期县官的方法,规范实现者确保生命周期的各个阶段调用实例相关的方法。生命周期如下:

@ServerEndpoint("/chat")
@Component
public class ChatEndpoint{@OnOpen// 连接建立时被调用public void onOpen(Session session, EndpointConfig config){}@OnMessage// 接收到客户端发送的数据时被调用public void onMessage(String message) {}@OnClose// 连接关闭时被调用public void onClose(Session session) {}
}

5.需求分析

5.1通过websocket实现在线聊天室

登陆页面:

聊天页面:

5.2 流程分析

5.3消息格式:

客户端->服务器

{"toName":"张三","message":"你好"
}

服务器->客户端

①系统消息格式:

{"system":true,"fromName":null,"message"["李四","王五"]
}

②推送给某一个用户的消息格式:

{"system":false,"fromName":"张三","message""你好"
}

6.搭建websocket服务端

6.1 后端环境

  • SpringBoot: 2.6.7
  • JDK: 1.8

6.2 准备工作(导入Maven依赖)

WebSocket:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

Web:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

fastjson2:

<dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>2.0.50</version>
</dependency>

6.3 编写配置类,扫描添加有@ServerEndpoint注解的 Bean

@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

6.4 编写配置类,用于获取HttpSession对象

public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {@Overridepublic void modifyHandshake(ServerEndpointConfig serverEndpointConfig, HandshakeRequest request, HandshakeResponse response) {HttpSession httpSession = (HttpSession) request.getHttpSession();// 将 httpSession 对象存到 ServerEndpointConfig 对象中serverEndpointConfig.getUserProperties().put(HttpSession.class.getName(), httpSession);}}

6.4在@ServerEndPoint注解中指定配置类

import com.alibaba.fastjson2.JSON;
import jakarta.servlet.http.HttpSession;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;@ServerEndpoint(value = "/chat", configurator = GetHttpSessionConfig.class)
@Component
public class ChatEndpoint {// 保存在线的用户,key为用户名,value为 Session 对象private static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();private HttpSession httpSession;/*** 建立websocket连接后,被调用** @param session Session*/@OnOpenpublic void onOpen(Session session, EndpointConfig config) {this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());String user = (String) this.httpSession.getAttribute("currentUser");if (user != null) {onlineUsers.put(user, session);}// 通知所有用户,当前用户上线了String message = MessageUtils.getMessage(true, null, getFriends());broadcastAllUsers(message);}private Set<String> getFriends() {return onlineUsers.keySet();}private void broadcastAllUsers(String message) {try {Set<Map.Entry<String, Session>> entries = onlineUsers.entrySet();for (Map.Entry<String, Session> entry : entries) {// 获取到所有用户对应的 session 对象Session session = entry.getValue();// 使用 getBasicRemote() 方法发送同步消息session.getBasicRemote().sendText(message);}} catch (Exception exception) {exception.printStackTrace();}}/*** 浏览器发送消息到服务端时该方法会被调用,也就是私聊* 张三  -->  李四** @param message String*/@OnMessagepublic void onMessage(String message) {try {// 将消息推送给指定的用户Message msg = JSON.parseObject(message, Message.class);// 获取消息接收方的用户名String toName = msg.getToName();String tempMessage = msg.getMessage();// 获取消息接收方用户对象的 session 对象Session session = onlineUsers.get(toName);String currentUser = (String) this.httpSession.getAttribute("currentUser");String messageToSend = MessageUtils.getMessage(false, currentUser, tempMessage);session.getBasicRemote().sendText(messageToSend);} catch (Exception exception) {exception.printStackTrace();}}/*** 断开 websocket 连接时被调用** @param session Session*/@OnClosepublic void onClose(Session session) throws IOException {// 1.从 onlineUsers 中删除当前用户的 session 对象,表示当前用户已下线String user = (String) this.httpSession.getAttribute("currentUser");if (user != null) {Session remove = onlineUsers.remove(user);if (remove != null) {remove.close();}session.close();}// 2.通知其他用户,当前用户已下线// 注意:不是发送类似于 xxx 已下线的消息,而是向在线用户重新发送一次当前在线的所有用户String message = MessageUtils.getMessage(true, null, getFriends());broadcastAllUsers(message);}}

7.搭建WebSocket客户端

前端使用的技术:Vue3+Axois+Elementplus

7.1创建一个axios实例

import axios from 'axios'const request = axios.create({baseURL: '/api',timeout: 60000,headers: {'Content-Type': 'application/json;charset=UTF-8'}
})request.interceptors.request.use()request.interceptors.response.use(response => {if (response.data) {return response.data}return response
}, (error) => {return Promise.reject(error)
})export default request

7.2 编写代理规则

vite.config.js

import {fileURLToPath, URL} from 'node:url'import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'// https://vitejs.dev/config/
export default defineConfig({plugins: [vue()],resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}},server: {proxy: {'/api': {target: 'http://localhost:7024',changeOrigin: true,rewrite: (path) => {return path.replace('/api', '')}}}}
})

7.2创建WebSocket对象

webSocket.value = new WebSocket('ws://localhost:7024/chat')

7.3为WebSocket对象绑定事件

webSocket.value.onopen = onOpen// 接收到服务端推送的消息后触发
webSocket.value.onmessage = onMessagewebSocket.value.onclose = onClose

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

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

相关文章

POI获取模板文件,替换数据横纵动态表格、折线图、饼状图、折线饼状组合图

先说几个关键的点 pom.xml依赖 <dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version> </dependency> <dependency><groupId>com.deepoove</groupId>&…

鸭脖变“刺客”,啃不起了

撰文&#xff5c;ANGELICA 编辑&#xff5c;ANGELICA 审核&#xff5c;烨 Lydia 声明&#xff5c;图片来源网络。日晞研究所原创文章&#xff0c;如需转载请留言申请开白。 你有多久没吃卤味了&#xff1f; 2020年之后&#xff0c;人们对于几大卤味巨头的关注度正在下降。 …

【MySQL 04】数据类型

目录 1.数据类型分类 2.数值类型 2.1 tinyint 类型 2.2 bit类型 2.3 float类型 2.4decimal 3.字符串类型 3.1 char类型 3.2 varchar类型 4.日期和时间类型 6. enum和set类型 6.1.enum和set类型简介&#xff1a; 6.2.enum和set的一般使用方法 6.3.用数字的方式…

业务数据批量插入数据库实践

业务数据如何存储一直以来都是项目开发中的一个比较重要的话题。我们要从资源的利用率&#xff0c;业务场景和技术实现多个方面考虑存储的问题。“抛开业务谈技术就是耍流氓”&#xff0c;所有技术架构都要站在实际的业务场景中分析。比如个人端的产品&#xff0c;这种就属于读…

fiddler抓包11_列表显示服务器IP (配置文件)

请求列表默认不显示服务器IP字段&#xff0c;也无法从定制列窗口添加&#xff0c;可以修改CustomRules.js实现。 ① 菜单栏“Rules”&#xff08;规则&#xff09; - “Customize Rules...”&#xff08;自定义规则&#xff09;&#xff0c;打开CustomRules.js文件。 &#xf…

基于stm32的跑步机控制系统设计-设计说明书

设计摘要&#xff1a; 随着人们对健康和健身的关注增加&#xff0c;跑步机逐渐成为室内健身的主要设备之一。本文提出了一种基于STM32的跑步机控制系统设计&#xff0c;旨在实现对跑步机的运行速度、倾斜角度和运动模式等参数的精确控制&#xff0c;提供更好的健身体验。 首先…

vue3开发中易遗漏的常见知识点

文章目录 组件样式的特性Scoped CSS之局部样式的泄露Scoped CSS之深度选择器CSS Modules在CSS中使用v-bind 非props属性继承组件通信父子组件的相互通信props/$emit父组件传递数据给子组件子组件传递数据给父组件 非父子组件的相互通信Provide/inject全局事件总线 组件插槽作用…

LVGL 控件之消息框(lv_msgbox)

目录 一、概述二、消息框1、创建消息框2、获取消息框的组成部分3、关闭消息框部件4、消息框部件事件5、API 函数 一、概述 消息框部件是由多个小部件构建而成的&#xff0c;包括&#xff1a;lv_obj、lv_btn、lv_label 和 lv_btnmatrix 部件&#xff0c;示意图如下所示&#xf…

简单题83. 删除排序链表中的重复元素 (Java)20240920

问题描述&#xff1a; java&#xff1a; /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; th…

【LLM学习之路】9月23日24日 第十、十一天 Attention代码解读

【LLM学习之路】9月23日24日 第十、十一天 Attention代码解读 Transformer模型大致分为三类 纯 Encoder 模型&#xff08;例如 BERT&#xff09;&#xff0c;又称自编码 (auto-encoding) Transformer 模型&#xff1b;纯 Decoder 模型&#xff08;例如 GPT&#xff09;&#…

基于python的django微博内容网络分析系统,实现文本划分词结构

本项目旨在开发一个基于Python的Django框架的微博内容网络分析系统&#xff0c;聚焦于微博文本的分词处理、名词提取和主成分分析。该系统通过数据收集与预处理、分词及结构化文本分析&#xff0c;为舆情监测、话题分析和用户行为研究提供了一体化的解决方案。 主要功能包括&a…

lkhgjfjghkbhjk

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

LED驱动电路

LED驱动电路简介 摘要&#xff1a; LED照明是今年来快速兴起发展的一种新型光源&#xff0c;它的许多良好特点使得它的应用面越来越广。LED的单向导电特性使人一般认为应该用直流驱动&#xff0c;但是对直流恒压和限流的装置在保证比较好的限流特性时&#xff0c;自身功耗是很…

路政通 | OPENAIGC开发者大赛高校组AI创新之星奖

在第二届拯救者杯OPENAIGC开发者大赛中&#xff0c;涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到&#xff0c;我们特意开设了优秀作品报道专栏&#xff0c;旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者&#xff0c;希望能带给…

深圳某局联想SR850服务器黄灯 不开机维修

深圳 福田区1台Lenovo Thinksystem SR850 四路服务器黄灯问题现场处理。 1&#xff1a;型号&#xff1a;联想SR850 机架式2U服务器 2&#xff1a;故障&#xff1a;能通电&#xff0c;开机按钮快闪&#xff0c;随后叹号警告灯常亮 3&#xff1a;用户自行折腾无果后找到我们tech …

QT客户端发送HTTP请求此时服务器到底收到了哪些数据?

一个Http请求包括 请求行 请求头 空行 请求体 下面是示例&#xff1a; 1,2,3,4分别代表上面的四个部分&#xff0c;我只是做了一些解析&#xff0c;具体可以结合代码 1. post / HTTP/1.1 2.GET请求头包括Host(主机名),user-agent&#xff08;客户端标识符&#xff09;&am…

C++类和对象(中)【下篇】

&#x1f31f;个人主页&#xff1a;落叶 &#x1f31f;当前专栏: C专栏 目录 赋值运算符重载 运算符重载 赋值运算符重载 日期类实现 运算符重载<和运算符重载 运算符重载进行复用 运算符重载< 运算符重载> 运算符重载> 运算符重载! 获取某年某月的天数…

解决方案 | 镭速助力动漫游戏行业突破跨网文件交换瓶颈

在数字化浪潮推动下&#xff0c;动漫游戏行业蓬勃发展。随着高清技术的普及和云游戏的兴起&#xff0c;动漫游戏行业对动画的画质要求越来越高&#xff0c;数据量呈现爆炸式增长。然而&#xff0c;行业内的跨网文件交换难题也日益凸显&#xff0c;成为制约行业发展的瓶颈。 行业…

pyqt瀑布流布局

最近研究瀑布流布局&#xff0c;发现都是收费的&#xff0c;所以只能自己写算法写布局。 所以啥都不说直接上代码 ImageLabel 参考 pyqt5 QLabel显示网络图片或qfluentwidgets官网 代码 import math import sys from pathlib import Pathfrom PyQt5.Qt import * from qflue…

erlang学习:Linux命令学习4

顺序控制语句学习 if&#xff0c;else对文件操作 判断一个文件夹是否存在&#xff0c;如果存在则进行删除&#xff0c;如果不存在则创建该文件夹&#xff0c;并复制一份该脚本后&#xff0c;删除该脚本 if [ -d "/erlangtest/testdir"]; then echo "删除文件夹…