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

news2024/9/30 5:34:46

其实原理非常简单,就是客户端用户通过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
{

    @Bean
    public 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的session
    private 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时
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this);
        log.info("【websocket消息】有新的连接, 总数:{}", webSocketSet.size());
    }


    //前端关闭时一个websocket时
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        log.info("【websocket消息】连接断开, 总数:{}", webSocketSet.size());
    }


    //前端向后端发送消息
    @OnMessage
    public 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
{
    @Resource
    private 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
{



    @Override
    public 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">
					<div
						class="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-dialog
			title="请输入您的信息"
			: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() {
		// TODO
		this.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.websocket
			let mes = this.mes
			if (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.websocket
			websocket.addEventListener("open", (e) => {
				this.$message.success("用户连接成功!")
				websocket.send(JSON.stringify(this.user))
				this.isShowUserPage = false
			})

			// 监听服务器消息
			websocket.addEventListener("message", (e) => {
				let mes = e.data
				let 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.user
			if (user.name == "") {
				this.$message.error("用户名不能为空")
				this.isShowUserPage = true
				return false
			}
			if (user.age == null) {
				this.$message.error("年龄不能为空")
				this.isShowUserPage = true
				return 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.coloradmin.cn/o/1506142.html

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

相关文章

云原生(一)、linux快速上手

Linux是一种开源的Unix-like操作系统内核。它是由Linus Torvalds于1991年首次发布&#xff0c;其后经过全球的自由软件社区的持续开发和改进。Linux内核是操作系统的核心部分&#xff0c;但通常与GNU项目合作&#xff0c;以形成完整的操作系统&#xff0c;被称为Linux发行版&am…

每日OJ题_牛客WY28 跳石板(动态规划)

目录 牛客WY28 跳石板 解析代码 牛客WY28 跳石板 跳石板_牛客题霸_牛客网 解析代码 #include <iostream> #include <vector> #include <climits> #include <cmath> using namespace std;void get_div_num(int n, vector<int>& arr) {for…

基于java+springboot+vue实现的农产品智慧物流系统(文末源码+Lw)23-239

课题意义 现如今&#xff0c;信息种类变得越来越多&#xff0c;信息的容量也变得越来越大&#xff0c;这就是信息时代的标志。近些年&#xff0c;计算机科学发展得也越来越快&#xff0c;而且软件开发技术也越来越成熟&#xff0c;因此&#xff0c;在生活中的各个领域&#x…

WEBUI中的完美像素模式(Pixel Perfect)到底是什么意思

在webui的controlnet中&#xff0c;有个选项&#xff0c;叫做“完美像素模式”&#xff0c;英文为“pixel perfect mode”&#xff0c;有很多朋友在使用的时候不知道这个神奇的选项是否应该勾选上&#xff0c;所以有时候排查问题的时候&#xff0c;会反复的选择和去掉勾选&…

机器学习的基础学习笔记

黑马的学习视频 大家常说的人工智能、机器学习、深度学习其实是包含关系&#xff0c;深度学习是机器学习的一种特殊方法&#xff0c;而机器学习又是人工智能的一个子领域。 其中机器学习是使计算机系统能够通过学习经验和数据来改进性能。机器学习算法能够从数据中发现模式&am…

Igraph入门指南 5

2、graph_from系列&#xff0c;将其他R数据结构转换成图 2-1 邻接矩阵转图&#xff1a;graph_from_adjacency_matrix 可以接受Matrix包创建的稀疏矩阵作为参数 邻接矩阵中行的顺序被保留&#xff0c;并作为图中顶点的顺序。 本函数几个重要的参数&#xff1a; weighted&am…

美团 Java 开发笔试热经

Voiceover&#xff1a; 见者有缘&#xff0c;缘来好运。欢迎大家来到我的博客【CS_GUIDER】&#xff1a;&#xff08;建议收藏至浏览器书签&#xff09; https://wlei224.gitee.io &#xff08;建议访问这个&#xff0c;速度极快&#xff09; https://wl2o2o.github.io &#x…

Kafka 面试题及答案整理,最新面试题

Kafka中的Producer API是如何工作的&#xff1f; Kafka中的Producer API允许应用程序发布一流的数据到一个或多个Kafka主题。它的工作原理包括&#xff1a; 1、创建Producer实例&#xff1a; 通过配置Producer的各种属性&#xff08;如服务器地址、序列化方式等&#xff09;来…

数据结构/C++:AVL树

数据结构/C&#xff1a;AVL树 概念实现插入左单旋右单旋左右双旋右左双旋 总代码展示 概念 AVL树是一种自平衡二叉搜索树&#xff08;BST&#xff09;&#xff0c;被命名为Adelson-Velskii和Landis树&#xff0c;以它们的发明者们的名字命名。AVL树通过在插入和删除操作后进行…

嘉立创画PCB板子入门喔

PCB层叠结构 单层板&#xff0c;双层板&#xff08;就是在单层板的基础上过孔到背面&#xff09;&#xff0c;四层板&#xff0c;六层板&#xff08;四层&#xff0c;六层就是在双层板子基础上通过通孔&#xff0c;盲孔&#xff0c;埋空完成&#xff09;一般以偶数去打板。 为什…

2024年掌握人工智能的顶级课程

[AI 课程推荐] 谷歌、微软、哈佛大学, DeepLearning.AI都发布了免费的人工智能和ChatGPT的课程。 以下是 2024 年掌握人工智能的顶级课程: GOOGLE - 生成式人工智能学习路径微软- 为每个人提供生成式人工智能微软 - 人工智能初学者入门哈佛 - CS50 的 Python 人工智能简介Deep…

MATLAB报错:尝试将 SCRIPT imread 作为函数执行

报错&#xff1a; 在执行matlab脚本的时候出现报错。 >> imread 尝试将 SCRIPT imread 作为函数执行: S:\temp_file\matlab\DigitalImageProcessing\imread.m出错 imread (line 2) I imread(S:\temp_file\matlab\DigitalImageProcessing\blueman.png);分析以及解决方…

Java并发编程总结(一)

一、进程与线程 1、进程与线程 1.1、进程 程序一般认为是静态存储的&#xff0c;而进程则是活动的&#xff0c;真正执行的、动态加载到内存的&#xff0c;被CPU执行的。 程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加…

解决 JavaScript heap out of memory 报错

前台运行项目时候提示内存溢出 解决 先执行&#xff1a; export NODE_OPTIONS"--max-old-space-size4096" 再运行&#xff1b; nom run serve

算法---双指针练习-4(盛水最多的容器)

题目 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;盛水最多的容器 2. 讲解算法原理 算法的主要思路是使用双指针的方法&#xff0c;通过不断调整指针的位置来计算面积&#xff0c;并更新最大面积。具体步骤如下&#xff1a; 初始化左指针x为数组…

Chrome浏览器好用的几个扩展程序

Chrome好用的扩展程序 背景目的介绍JsonHandle例子未完待续。。。。。。 背景 偶然在往上看到Chrome有很多好用的扩展程序&#xff0c;比较好用&#xff0c;因此记录下比较实用的扩展程序。 目的 记录Chrome浏览器好用的插件。 介绍 JsonHandle下载以及无法扩展插件的解决…

C#实现线性查找算法

C#实现线性查找算法 以下是使用 C# 实现线性查找算法的示例代码&#xff1a; using System;class Program {static int LinearSearch(int[] array, int target){for (int i 0; i < array.Length; i){if (array[i] target){return i; // 如果找到目标&#xff0c;返回其索…

《时间贫困》

作者&#xff1a;【英】凯茜霍姆斯 深陷困境&#xff1a;时间贫困且精疲力竭 我们生活在生产力至上的文化中&#xff0c;忙碌已经成了一种身份的象征&#xff0c;也是个人价值的一种体现。然而&#xff0c;基于我个人的经历和研究&#xff0c;我发现这种忙碌的生活状态并不能…

【Web开发】深度学习HTML(超详细,一篇就够了)

&#x1f493; 博客主页&#xff1a;从零开始的-CodeNinja之路 ⏩ 收录文章&#xff1a;【Web开发】深度学习html(超详细,一篇就够了) &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 HTML1. HTML基础1.1 什么是HTML1.2 认识HTML标签1.3 HTML文件基本…

计算机网络(五)

网络层 网络层的主要目的是实现网络互连&#xff0c;进而实现数据包在各网络之间的传输。 要实现网络层&#xff0c;主要解决三个问题&#xff1a; ①网络层向运输层提供怎样的服务&#xff1f;&#xff08;“可靠传输“、”不可靠传输“&#xff09; ②网络层寻址 ③路由选择…