利用websocket +定时器简易的实现一个网络聊天室

其实原理非常简单,就是客户端用户通过websoket来连接websocket服务端。然后服务端,收集每个用户发出的消息, 进而将每条用户的消息通过广播的形式推送到每个连接到服务端的客户端。从而实现用户的实时聊天。

// TODO : 我主要是讲一下实现思路。并未完善其功能。

1.后端

依赖

<!--websocket--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!--huttol--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.11</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>

 webSocket配置类

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

 WebSocket类

类似于controller接口,只不过这个接口,用来专门处理websoket相关的。

package com.example.websocketdemo.websocket;import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.websocketdemo.domain.User;
import com.example.websocketdemo.domain.UserMes;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;@Component
//定义websocket服务器端,它的功能主要是将目前的类定义成一个websocket服务器端。注解的值将被用于监听用户连接的终端访问URL地址
@ServerEndpoint("/websocket")
@Slf4j
public class WebSocket {//实例一个session,这个session是websocket的sessionprivate Session session;private User user; // 每个websocket连接对应的用户信息//存放websocket的集合(本次demo不会用到,聊天室的demo会用到)private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<>();// 用户服务器数据存储结构体private static List<UserMes> userMess = new ArrayList<>();public List<UserMes> getUserMess(){return userMess;}//前端请求时一个websocket时@OnOpenpublic void onOpen(Session session) {this.session = session;webSocketSet.add(this);log.info("【websocket消息】有新的连接, 总数:{}", webSocketSet.size());}//前端关闭时一个websocket时@OnClosepublic void onClose() {webSocketSet.remove(this);log.info("【websocket消息】连接断开, 总数:{}", webSocketSet.size());}//前端向后端发送消息@OnMessagepublic void onMessage(String message) {if(isUserStr(message)){this.user = userStrConvertUser(message);log.info("【websocket消息】客户端发来的连接请求:{}", message);return;}userMess.add(new UserMes(user.getName(),message));log.info("【websocket消息】收到客户端发来的消息:{}", message);}// 判断消息中是否包含用户信息的json字符串private boolean isUserStr(String mes){JSONObject response;try{response   = JSONUtil.parseObj(mes);if(response.containsKey("name") && response.containsKey("age")){return true;}return false;}catch (Exception e){return false;}}// 将包装用户信息的json字符串转化为用户对象private User userStrConvertUser(String mes){JSONObject res = JSONUtil.parseObj(mes);String name = res.getStr("name");Integer age = res.getInt("age");return new User(name,age);}//新增一个方法用于主动向客户端发送消息public void sendMessage(String message) {for (WebSocket webSocket: webSocketSet) {log.info("【websocket消息】广播消息, message={}", message);try {webSocket.session.getBasicRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}public List<User> getUserList(){ArrayList<User> users = new ArrayList<>();for (WebSocket webSocket : webSocketSet){users.add(webSocket.user);}return users;}//新增一个方法用于主动向客户端发送消息// 卧槽消息推送方法}

WebSocketTasks

利用定时器,实现服务端向客户端消息的推送。

websocket定时器,负责处理将客户端传入服务的消息,整合推送到相应的客户端。

package com.example.websocketdemo.tasks;import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.websocketdemo.domain.User;
import com.example.websocketdemo.domain.UserMes;
import com.example.websocketdemo.websocket.WebSocket;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.List;/*** websocket定时器* * @author: jzm* @date: 2024-03-05 20:02**/@Component
public class WebSocketTasks
{@Resourceprivate WebSocket webSocket;// 每隔10s定时推送当前用户在线人数@Scheduled(cron = "0/10 * * * * ?")public void sendOlineUserInfo() throws InterruptedException{WebSocket ws = webSocket;List<User> userList = ws.getUserList();JSONObject res = new JSONObject();res.set("size",userList.size());res.set("users",userList);webSocket.sendMessage(JSONUtil.toJsonStr(res));}@Scheduled(cron = "0/10 * * * * ?")public void sendUserList() throws InterruptedException{WebSocket ws = webSocket;List<UserMes> userMess = ws.getUserMess();ws.sendMessage(JSONUtil.toJsonStr(userMess));}}

 设计到的用户实体类 和其他配置类

/*** 用户* * @author: jzm* @date: 2024-03-06 08:11**/@Data
@AllArgsConstructor
public class User
{private String name;private Integer age;
}
/*** 用户消息* * @author: jzm* @date: 2024-03-06 08:34**/@Data
@AllArgsConstructor
public class UserMes
{private String username;private String message;
}

我是利用vue.js搭建的前端工程,是2个服务端口。会有跨域的影响。 

还有就是我服务端口是: 8089

@Configuration
public class WebMvcConfig implements WebMvcConfigurer
{@Overridepublic void addCorsMappings(CorsRegistry registry) {// 设置允许跨域的路径registry.addMapping("/**")// 设置允许跨域请求的域名.allowedOriginPatterns("*")// 是否允许cookie.allowCredentials(true)// 设置允许的请求方式.allowedMethods("*")// 设置允许的header属性.allowedHeaders("*")// 跨域允许时间.maxAge(3600);}}

2.前端

Index.vue

主要是这个Index.vue。用element-ui做ui。参考下面衔接,按照官方文档自扃安装以下。另外的我的vue版本是vue2.x的。

 参考: 组件 | Element

<template><div class="index"><div class="box" style="border: 1px solid black"><el-card class="box-card"><div slot="header" class="clearfix"><h2 style="text-align: center">聊天室首页</h2></div><div class="box-main"><divclass="box-main-line clearfix"v-for="(item, index) in userMess":key="index"><span class="avatar" :style="messageStyle(item)"><i class="el-icon-user-solid" style="font-size: 20px"></i><h5>{{ item.username }}</h5></span><span class="message" :style="messageStyle(item)">{{ item.message }} </span></div></div><br /><div class="box-input"><el-input placeholder="请输入内容" v-model="mes" @keyup.enter.native="sendMe"><template slot="prepend"><el-button type="info" round @click="sendMe">发送</el-button></template></el-input></div></el-card></div><!-- 一开始弹出表单 --><el-dialogtitle="请输入您的信息":visible.sync="isShowUserPage":before-close="checkUser"width="30%"style="padding: 0 10px"><el-form :model="user" status-icon label-width="100px"><el-form-item label="用户名" prop="pass"><el-input type="name" v-model="user.name" autocomplete="off"></el-input></el-form-item><el-form-item label="年龄" prop="age"><el-input v-model.number="user.age"></el-input></el-form-item></el-form><span slot="footer" class="dialog-footer"><el-button type="primary" @click="configUserPage">确 定</el-button></span></el-dialog><!-- 表格展示页 --><template><el-table :data="users" style="width: 100%"><el-table-column prop="name" label="用户名" width="180"> </el-table-column><el-table-column prop="age" label="年龄" width="180"> </el-table-column></el-table></template></div>
</template><script>
export default {name: "FrontIndex",data() {return {mes: "",websocket: null,isShowUserPage: false,user: {name: "",age: null,},users: [],// 用户信息列表userMess: [],}},mounted() {// TODOthis.configUserPage()},methods: {messageStyle(item) {return {float: item.username == this.user.name ? "right" : "left",textAlign: item.username == this.user.name ? "right" : "left",}},sendMe() {let websocket = this.websocketlet mes = this.mesif (mes == "") {this.$message.warning("不能发送空消息")return}websocket.send(this.mes)this.mes = ""},// 连接服务器connectServer() {this.websocket = new WebSocket("ws://localhost:8089/websocket")this.handWebSocketCallback()},// 处理websocket 连接回调函数handWebSocketCallback() {let websocket = this.websocketwebsocket.addEventListener("open", (e) => {this.$message.success("用户连接成功!")websocket.send(JSON.stringify(this.user))this.isShowUserPage = false})// 监听服务器消息websocket.addEventListener("message", (e) => {let mes = e.datalet obj = JSON.parse(mes)if (this.checkUsersMessage(mes)) {this.users = obj.users} else {this.userMess = obj}})},// 校验这个服务器消息是不是用户列表消息checkUsersMessage(mes) {let obj = JSON.parse(mes)if (obj.users != undefined) {return true}return false},// 确定、错误输入都是校验这个configUserPage() {let end = this.checkUser()if (end) {this.connectServer()}},checkUser() {let user = this.userif (user.name == "") {this.$message.error("用户名不能为空")this.isShowUserPage = truereturn false}if (user.age == null) {this.$message.error("年龄不能为空")this.isShowUserPage = truereturn false}return true},},
}
</script>
<style>
.clearfix:before,
.clearfix:after {display: table;content: "";
}
.clearfix:after {clear: both;
}.index {width: 600px;margin: 10px auto;
}
.box-main {height: 200px;border: 1px solid black;overflow-y: scroll;
}.box-input {width: 500px;height: 100px;margin: 10px auto;
}/* 隐藏滚动条,保留滚动功能 *//* 隐藏滚动条本身 */
.box-main::-webkit-scrollbar {width: 0;height: 0;
}/* 为了保留滚动功能,使用伪元素来模拟滚动条 */
.box-main::-webkit-scrollbar-track {box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
}.box-main::-webkit-scrollbar-thumb {background-color: #888;
}
.box-main-line {margin: 10xp 0 0 0;
}
.box-main-line .avatar {display: inline-block;width: 50px;height: 50px;border: 1px solid black;border-radius: 50%;text-align: center;
}
.box-main-line .message {display: inline-block;width: 88%;padding: 15px 0;margin: 0 0 0 10px;box-shadow: rgba(50, 50, 93, 0.25) 0px 50px 100px -20px, rgba(0, 0, 0, 0.3) 0px 30px 60px -30px,rgba(10, 37, 64, 0.35) 0px -2px 6px 0px inset;
}
</style>

3.测试

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用数据库实现增删改查

#include<myhead.h>//定义添加数据函数int do_add(sqlite3 *ppDb) {//1.准备sql语句,输入要添加的信息int add_numb; //工号char add_name[20]; //姓名char add_sex[10]; //性别double add_score; //工资printf("请输入要添加的工号:")…

恢复IDEA误删除的git提交,提交被删除,尝试恢复提交

​​​​​​ dgqDESKTOP-JRQ5NMD MINGW64 /f/IdeaProjects/workspace/spzx-parent ((8bb112e...)) $ git reflog 8bb112e (HEAD, origin/master, master) HEAD{0}: checkout: moving from master to 8bb112e5ac18dfe4bbd64adfd06363e46b609f21 8bb112e (HEAD, origin/master, …

微信小程序开发系列(二十一)·wxml语法·setData()修改数组类型数据(增加、修改、删除)

目录 1. 新增数组元素 方法一&#xff1a;push&#xff08;&#xff09; 方法二&#xff1a;concat() 方法三&#xff1a;ES6中的扩展运算符 ... 2. 修改数组元素 样式一&#xff1a;数字 样式二&#xff1a;元素 3. 删除数组元素 方法一&#xff1a;splice&#x…

vue2源码分析-vue入口文件global-api分析

文章背景 vue项目开发过程中,首先会有一个初始化的流程,以及我们会使用到很多全局的api,如 this.$set this.$delete this.$nextTick,以及初始化方法extend,initUse, initMixin , initExtend, initAssetRegisters 等等那它们是怎么实现,让我们一起来探究下吧 源码目录 global-…

Windows下 OracleXE_21 数据库的下载与安装

Oracle 数据库的下载与安装 数据库安装包下载数据库安装访问数据库进行测试Navicat连接数据库 1. 数据库安装包的下载 1.1 下载地址 Oracle Database Express Edition | Oracle 中国 1.2 点击“下载 Oracle Database XE”按钮&#xff0c;进去到下载页面&#xff08;选择对…

Stable diffusion零基础课程

该课程专为零基础学习者设计&#xff0c;旨在介绍和解释稳定扩散的基本概念。学员将通过简单易懂的方式了解扩散现象、数学模型及其应用&#xff0c;为日后更深入的科学研究和工程应用打下坚实基础。 课程大小&#xff1a;3.8G 课程下载&#xff1a;https://download.csdn.ne…

灵魂指针,教给(一)

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 一、内存和地址 1.1 内存 在介绍知识之前&#xff0c;先来想一个生活中的小栗子&#xff1a; 假如把你放在一个有100间屋子的酒店…

第三讲 汇编初步 课程随手记

一、寄存器 32位CPU通用寄存器如下图所示&#xff1a; 因为教材依照的是32位CPU寄存器&#xff0c;而我安装的是64位寄存器&#xff0c;所以找了一下64位的寄存器的资料 PS&#xff1a;一般来说&#xff0c;Intel处理器字节存储顺序为小端法存储&#xff0c;是指数据的高字节保…

基于Skywalking开发分布式监控(四)一个案例

上一篇我们简单介绍了基于SkyWalking自定义增强的基本架构&#xff0c;即通过把Trace数据导入数据加工模块进行加工&#xff0c;进行持久化&#xff0c;并赋能grafana展示。 现在我们给出一个例子&#xff0c;对于量化交易系统&#xff0c;市场交易订单提交&#xff0c;该订单…

关于springboot一个接口请求后,主动取消后,后端是否还在跑

1、最近在思考一个问题&#xff0c;如果一个springboot的请求的接口比较耗时&#xff0c;中途中断该请求后&#xff0c;则后端服务是否会终止该线程的处理&#xff0c;于是写了一个demo RequestMapping(value "/test", method RequestMethod.GET)public BasicResul…

云消息队列 Confluent 版正式上线!

作者&#xff1a;阿里云消息队列 前言 在 2023 年杭州云栖大会上&#xff0c;Confluent 成为阿里云技术合作伙伴&#xff0c;在此基础上&#xff0c;双方展开了深度合作&#xff0c;并在今天&#xff08;3月1日&#xff09;正式上线“云消息队列 Confluent 版”。 通过将 Co…

android基础学习

从上面的描述就可以知道&#xff0c;每一个Activity组件都有一个对应的ViewRoot对象、View对象以及WindowManager.LayoutParams对象。这三个对象的对应关系是由WindowManagerImpl类来维护的。具体来说&#xff0c;就是由WindowManagerImpl类的成员变量mRoots、mViews和mParams所…

【Apache Camel】基础知识

【Apache Camel】基础知识 Apache Camel是什么Apache Camel基本概念和术语CamelContextEndpointsRoutesRouteBuilderComponentsMessageExchangeProcessorsDomain Specific Language&#xff08;DSL&#xff09; Apache Camel 应用执行步骤Apache Camel 示意图参考 Apache Camel…

学习Java的第一天

一、Java简介 Java 是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 面向对象程序设计语言和 Java 平台的总称。由 James Gosling和同事们共同研发&#xff0c;并在 1995 年正式推出。 后来 Sun 公司被 Oracle &#xff08;甲骨文&#xff09;公司收购&#xff0c;Jav…

【AAAI2023】基于神经跨度的持续命名实体识别模型

论文标题&#xff1a;A Neural Span-Based Continual Named Entity Recognition Model 论文链接&#xff1a;https://arxiv.org/abs/2302.12200 代码&#xff1a;https://github.com/Qznan/SpanKL inproceedings{zhang2023spankl,title{A Neural Span-Based Continual Named En…

ElevenLabs用AI为Sora文生视频模型配音 ,景联文科技提供高质量真人音频数据集助力生成逼真音效

随着Open AI公司推出的Sora文生视频模型惊艳亮相互联网&#xff0c;AI语音克隆创企ElevenLabs又为Sora的演示视频生成了配音&#xff0c;所有的音效均由AI创造&#xff0c;与视频内容完美融合。 ElevenLabs的语音克隆技术能够从一分钟的音频样本中创建逼真的声音。为了实现这一…

RPC——远程过程调用

一、RPC介绍 1.1 概述 RPC&#xff08;Remote Procedure Call Protocol&#xff09; 远程过程调用协议。RPC是一种通过网络从远程计算机程序上请求服务&#xff0c;不需要了解底层网络技术的协议。RPC主要作用就是不同的服务间方法调用就像本地调用一样便捷。 1.2 RPC框架 …

QT----在编译器里能够连接云端数据库,使用windeployqt打包后运行程序,链接不上云端mysql数据库

问题描述 在编译器里能够连接云端数据库&#xff0c;使用windeployqt打包后运行程序&#xff0c;链接不上云端mysql数据库&#xff0c;困扰了好几天 打包发布手机上的app还是无法连接 问题解决 打包的时候没有将这个文件放入&#xff0c;我们复制放到exe的目录即可

redis原理深入解析之看完这篇还需要努力

数据结构 动态字符串SDS struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len; /*已保存的字节数 不含结束标识 header*/uint8_t alloc; /*申请总的字节数&#xff0c;不含结束标识 header*/unsigned char flags;/*不同sds头类型&#xff0c;控制sds头大小 header*/…

【AI视野·今日Robot 机器人论文速览 第八十二期】Tue, 5 Mar 2024

AI视野今日CS.Robotics 机器人学论文速览 Tue, 5 Mar 2024 Totally 63 papers &#x1f449;上期速览✈更多精彩请移步主页 Interesting: &#x1f4da;双臂机器人拧瓶盖, (from 伯克利) website: https://toruowo.github.io/bimanual-twist &#x1f4da;水下抓取器, (from …