利用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.rhkb.cn/news/273094.html

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

相关文章

Spring MVC 全局异常处理器

如果不加以异常处理&#xff0c;错误信息肯定会抛在浏览器页面上&#xff0c;这样很不友好&#xff0c;所以必须进行异常处理。 1.异常处理思路 系统的dao、service、controller出现都通过throws Exception向上抛出&#xff0c;最后由springmvc前端控制器交由异常处理器进行异…

Tensorflow2.0+部署(tensorflow/serving)过程备忘记录Windows+Linux

Tensorflow2.0部署&#xff08;tensorflow/serving&#xff09;过程备忘记录 部署思路&#xff1a;采用Tensorflow自带的serving进模型部署&#xff0c;采用容器docker 1.首先安装docker 下载地址&#xff08;下载windows版本&#xff09;&#xff1a;https://desktop.docke…

Jmeter之Ramp-up Period(in seconds)

1、Ramp-up Period概念 &#xff08;in seconds&#xff09;–并发用户启动周期&#xff0c;告知JMeter 要在多长时间内启动全部Vuser用户。 2、为什么需要有“ramp-up period”&#xff0c;立即启动所有的并发用户数不是更好&#xff1f; 对于绝大多数的网址或应用&#xf…

【Prometheus】DataModel

数据模型 DataModel 指标 Metric metric 包含 metric name 和 metric label 格式&#xff1a; <metric name>{<label name><label value>, ...}例如&#xff1a;服务器 HTTP 接口 /messages 的总请求数 api_http_requests_total{method"POST",…

Jmeter(三) - 从入门到精通 - 测试计划(Test Plan)的元件(详解教程)

1.简介 上一篇中已经教你如何通过JMeter来创建一个测试计划&#xff08;Test Plan&#xff09;&#xff0c;那么这一篇我们就将JMeter启动起来&#xff0c;创建一个测试计划&#xff08;Test plan&#xff09;&#xff0c;然后给大家介绍一下测试计划&#xff08;Test Plan&am…

二,几何相交---4,BO算法---(1)接近性和可分离性

提了三个观点 1&#xff0c;如果一条直线&#xff08;比如竖直&#xff09;可以分开两个线段&#xff0c;则这两个线段不相交 2&#xff0c;只需要观察与隔离线相交的几个线段 3&#xff0c;从左向右扫描线只需要观察每个线段的两个端点和一些可能的相交点。

力扣18:三数之和

15. 三数之和 - 力扣&#xff08;LeetCode&#xff09; 题意&#xff1a;给你一个包含 n 个整数的数组 nums&#xff0c;判断 nums 中是否存在三个元素 a&#xff0c;b&#xff0c;c &#xff0c;使得 a b c 0 &#xff1f;请你找出所有满足条件且不重复的三元组。 注意&a…

目标检测5:采用yolov8, RK3568上推理实时视频流

上一个效果图&#xff0c;海康球机对着电脑屏幕拍&#xff0c;清晰度不好。 RK3568接取RTSP视频流&#xff0c;通过解码&#xff0c;推理&#xff0c;编码&#xff0c;最终并把结果推出RTSP视频流。 RK3568 推理 数据集采用coco的80个种类集&#xff0c;通过从yovo8.pt&#x…

2024年AI辅助研发趋势:探索未来研发工作的AI应用

引言&#xff1a; 随着科技的不断发展&#xff0c;人工智能&#xff08;AI&#xff09;技术已经深入到了各个行业&#xff0c;其中包括研发领域。在2024年&#xff0c;AI辅助研发将继续成为关注的焦点&#xff0c;因为它为研发工作带来了许多新的机遇和挑战。本文将探讨2024年…

【HTML】HTML基础7.3(自定义列表)

目录 标签 效果 代码 注意 标签 <dl> <dt>自定义标题</dt><dd>内容1</dd><dd>内容2</dd><dd>内容3</dd> 。。。。。。 </dl> 效果 代码 <dl><dt>蜘蛛侠系列</dt><dd>蜘蛛侠1</dd…

搭建nacos集群,并通过nginx实现负载均衡

nacos、eureka、consul、zookeeper等都是常用的微服务注册中心&#xff0c;这篇文章详细介绍一下在Ubuntu操作系统上搭建一个nacos的集群&#xff0c;以及通过nginx的反向代理功能实现nacos的负载均衡。 目录 一、安装nacos 1、安装nacos 2、修改nacos配置文件 3、创建naco…

css3中nth-child属性作用及用法剖析

hello宝子们...我们是艾斯视觉擅长ui设计和前端开发10年经验!希望我的分享能帮助到您!如需帮助可以评论关注私信我们一起探讨!致敬感谢感恩! 标题&#xff1a;CSS3中nth-child属性作用及用法剖析 摘要&#xff1a;CSS3中的nth-child选择器允许我们根据元素位置来定位特定的元素…

android开发环境搭建

android开发环境搭建 Android 开发环境搭建1.JDK安装与配置1.1 Jdk官方下载1.2 JDK安装1.3 环境变量配置1.4 新建JAVA_HOME1.5 修改Path变量1.6 新建classpath1.7 验证环境是否配置完成 2.开发工具二选一1.如何创建一个工程2.工程的目录结构的了解3.与开发的相关的常规视图4.我…

【QA-SYSTEMS】CANTATA-解决Jenkins中build Cantata报错

【更多软件使用问题请点击亿道电子官方网站查询】 1、 文档目标 解决Jenkins中build Cantata测试项目报找不到license server的错误。 2、 问题场景 在Jenkins中build Cantata测试项目&#xff0c;报错“Failed to figure out the license server correctly”。 3、软硬件环…

MT笔试题

前言 某团硬件工程师的笔试题&#xff0c;个人感觉题目的价值还是很高的&#xff0c;分为选择题和编程题&#xff0c;选择题考的是嵌入式基础知识&#xff0c;编程题是两道算法题&#xff0c;一道为简单难度&#xff0c;一道为中等难度 目录 前言选择题编程题 选择题 C语言中变…

Linux 理解进程

目录 一、基本概念 二、描述进程-PCB 1、task_struct-PCB的一种 2、task_ struct内容分类 三、组织进程 四、查看进程 1、ps指令 2、top命令 3、/proc文件系统 4、在/proc文件中查看指定进程 5、进程的工作目录 五、通过系统调用获取进程标示符 1、getpid()/get…

WPF 窗口添加投影效果Effect

BlurRadius&#xff1a;阴影半径 Color&#xff1a;颜色 Direction&#xff1a;投影方向 ShadowDepth&#xff1a;投影的深度 <Window.Effect><DropShadowEffect BlurRadius"10" Color"#FF858484" Direction"300" ShadowDepth&quo…

VB语言回忆录——到了是该放弃VB语言的时候了么

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 VB语言回忆录——到了是该放弃VB语言的时候了么 前言初次接触编程开始学习VB开始发挥作用版本变迁有感而发 前言 4年前&#xff08;2020年&#xff09;&#xff0c;微软 NET…

【AI辅助研发】-趋势:大势已来,行业变革

【AI辅助研发】-趋势&#xff1a;大势已来&#xff0c;行业变革 引言 在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;技术已逐渐渗透到各行各业&#xff0c;其中软件研发行业更是受益匪浅。AI辅助研发已成为大势所趋&#xff0c;不仅提高了软件开发的效…

Python接口自动化测试:断言封装详解

前言 在进行API接口测试时&#xff0c;断言起着至关重要的作用。断言是用于验证预期结果与实际结果是否一致的过程。在Python中&#xff0c;我们可以利用一些库来实现断言功能。 1. 安装必要的库 在Python中&#xff0c;我们主要会使用两个库&#xff1a;requests和jsonpath…