基于WebSocket实现简易即时通讯功能

代码实现

pom.xml

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.15.0</version>
</dependency>

配置信息

部分内容非必须,按自身需求处理即可

  • WebSocketConfig
package com.example.im.config;import com.example.im.infra.handle.ImRejectExecutionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;import javax.annotation.Resource;/*** @author PC*/
@Configuration
@EnableWebSocket
public class WebSocketConfig {@Resourceprivate WebSocketProperties webSocketProperties;@Beanpublic ServerEndpointExporter serverEndpoint() {return new ServerEndpointExporter();}/**** 配置线程池* @return 线程池*/@Beanpublic TaskExecutor taskExecutor() {WebSocketProperties.ExecutorProperties executorProperties = webSocketProperties.getExecutorProperties();ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 设置核心线程数executor.setCorePoolSize(executorProperties.getCorePoolSize());// 设置最大线程数executor.setMaxPoolSize(executorProperties.getMaxPoolSize());// 设置队列容量executor.setQueueCapacity(executorProperties.getQueueCapacity());// 设置线程活跃时间(秒)executor.setKeepAliveSeconds(executorProperties.getKeepAliveSeconds());// 设置默认线程名称executor.setThreadNamePrefix("im-");// 设置拒绝策略executor.setRejectedExecutionHandler(new ImRejectExecutionHandler());// 等待所有任务结束后再关闭线程池executor.setWaitForTasksToCompleteOnShutdown(true);return executor;}
}
  • WebSocketProperties
package com.example.im.config;import com.example.im.infra.constant.ImConstants;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;/*** @author PC*/
@Configuration
@ConfigurationProperties(prefix = "cus.ws")
public class WebSocketProperties {/*** 接收人是否排除自身,默认排除*/private Boolean receiverExcludesHimselfFlag = true;/*** 消息是否排除接收人信息,默认不排除*/private Boolean excludeReceiverInfoFlag = false;/*** 线程池信息*/private ExecutorProperties executorProperties = new ExecutorProperties();/*** 发送消息给指定人的分隔符,默认为@*/private String receiverSeparator = ImConstants.Symbol.AT;public Boolean getReceiverExcludesHimselfFlag() {return receiverExcludesHimselfFlag;}public void setReceiverExcludesHimselfFlag(Boolean receiverExcludesHimselfFlag) {this.receiverExcludesHimselfFlag = receiverExcludesHimselfFlag;}public Boolean getExcludeReceiverInfoFlag() {return excludeReceiverInfoFlag;}public void setExcludeReceiverInfoFlag(Boolean excludeReceiverInfoFlag) {this.excludeReceiverInfoFlag = excludeReceiverInfoFlag;}public String getReceiverSeparator() {return receiverSeparator;}public void setReceiverSeparator(String receiverSeparator) {this.receiverSeparator = receiverSeparator;}public ExecutorProperties getExecutorProperties() {return executorProperties;}public void setExecutorProperties(ExecutorProperties executorProperties) {this.executorProperties = executorProperties;}/*** 线程池信息*/public static class ExecutorProperties {/*** 核心线程数*/private int corePoolSize = 10;/*** 最大线程数*/private int maxPoolSize = 20;/*** 队列容量*/private int queueCapacity = 50;/*** 线程活跃时间(秒)*/private int keepAliveSeconds = 60;public int getCorePoolSize() {return corePoolSize;}public void setCorePoolSize(int corePoolSize) {this.corePoolSize = corePoolSize;}public int getMaxPoolSize() {return maxPoolSize;}public void setMaxPoolSize(int maxPoolSize) {this.maxPoolSize = maxPoolSize;}public int getQueueCapacity() {return queueCapacity;}public void setQueueCapacity(int queueCapacity) {this.queueCapacity = queueCapacity;}public int getKeepAliveSeconds() {return keepAliveSeconds;}public void setKeepAliveSeconds(int keepAliveSeconds) {this.keepAliveSeconds = keepAliveSeconds;}}
}

application.yml

server:port: 18080
cus:ws:exclude-receiver-info-flag: truereceiver-excludes-himself-flag: true

ws端口

  • WebSocketEndpoint

注意:若按常规注入方式(非static修饰),在项目启动时setWebSocketMessageService是有值的,但是发送消息时WebSocketMessageService会变为null,需要用static修饰。

其原因为Spring的bean管理是单例的,但是WebSocket是多对象的,当新用户进入系统时,会创建一个新的WebSocketEndpoint对象,但是不会再注入WebSocketMessageService,这样就会导致其为null。若想解决该问题,可以使用static修饰WebSocketMessageService,static修饰的对象属于类,而非实例,其在类加载时即可进行初始化。

package com.example.im.endpoint;import com.example.im.app.service.WebSocketMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.Optional;
import java.util.concurrent.ConcurrentHashMap;/*** @author PC*/
@Component
@ServerEndpoint("/ws")
public class WebSocketEndpoint {private final static Logger logger = LoggerFactory.getLogger(WebSocketEndpoint.class);public static final ConcurrentHashMap<String, WebSocketEndpoint> WEB_SOCKET_ENDPOINT_MAP = new ConcurrentHashMap<>();private Session session;private static WebSocketMessageService webSocketMessageService;@Autowiredpublic void setWebSocketMessageService(WebSocketMessageService webSocketMessageService) {WebSocketEndpoint.webSocketMessageService = webSocketMessageService;}/*** 打开ws连接** @param session 会话*/@OnOpenpublic void onOpen(Session session) {//连接成功logger.info("The connection is successful:" + getUserName(session));this.session = session;WEB_SOCKET_ENDPOINT_MAP.put(getUserName(session), this);}/*** 断开ws连接** @param session 会话*/@OnClosepublic void onClose(Session session) {WEB_SOCKET_ENDPOINT_MAP.remove(getUserName(session));//断开连接logger.info("Disconnect:" + getUserName(session));}/*** 接收到的消息** @param message 消息内容*/@OnMessagepublic void onMessage(String message, Session session) {//接收消息String sendUserName = getUserName(session);logger.info(sendUserName + " send message: " + message);webSocketMessageService.sendMessage(sendUserName, message);}private String getUserName(Session session) {return Optional.ofNullable(session.getRequestParameterMap().get("userName")).orElse(new ArrayList<>()).stream().findFirst().orElse("anonymous_users");}public Session getSession() {return session;}public void setSession(Session session) {this.session = session;}
}

实现类

WebSocketMessageServiceImpl

package com.example.im.app.service.impl;import com.example.im.app.service.WebSocketMessageService;
import com.example.im.config.WebSocketProperties;
import com.example.im.endpoint.WebSocketEndpoint;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Service;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;/*** @author PC*/
@Service
public class WebSocketMessageServiceImpl implements WebSocketMessageService {private final static Logger logger = LoggerFactory.getLogger(WebSocketMessageServiceImpl.class);private WebSocketProperties webSocketProperties;@Autowiredpublic void setWebSocketProperties(WebSocketProperties webSocketProperties) {this.webSocketProperties = webSocketProperties;}private TaskExecutor taskExecutor;@Autowiredpublic void setTaskExecutor(TaskExecutor taskExecutor) {this.taskExecutor = taskExecutor;}@Overridepublic void sendMessage(String sendUserName, String message) {//包含@发给指定人,否则发给全部人if (StringUtils.contains(message, webSocketProperties.getReceiverSeparator())) {this.sendToUser(sendUserName, message);} else {this.sendToAll(sendUserName, message);}}private void sendToUser(String sendUserName, String message) {getReceiverName(sendUserName, message).forEach(receiverName -> taskExecutor.execute(() -> {try {if (WebSocketEndpoint.WEB_SOCKET_ENDPOINT_MAP.containsKey(receiverName)) {WebSocketEndpoint.WEB_SOCKET_ENDPOINT_MAP.get(receiverName).getSession().getBasicRemote().sendText(generatorMessage(message));}} catch (IOException ioException) {logger.error("send error:" + ioException);}}));}private void sendToAll(String sendUserName, String message) {for (Map.Entry<String, WebSocketEndpoint> webSocketEndpointEntry : WebSocketEndpoint.WEB_SOCKET_ENDPOINT_MAP.entrySet()) {taskExecutor.execute(() -> {if (webSocketProperties.getReceiverExcludesHimselfFlag() && StringUtils.equals(sendUserName, webSocketEndpointEntry.getKey())) {return;}try {webSocketEndpointEntry.getValue().getSession().getBasicRemote().sendText(generatorMessage(message));} catch (IOException ioException) {logger.error("send error:" + ioException);}});}}private List<String> getReceiverName(String sendUserName, String message) {if (!StringUtils.contains(message, webSocketProperties.getReceiverSeparator())) {return new ArrayList<>();}String[] names = StringUtils.split(message, webSocketProperties.getReceiverSeparator());return Stream.of(names).skip(1).filter(receiver ->!(webSocketProperties.getReceiverExcludesHimselfFlag() && StringUtils.equals(sendUserName, receiver))).collect(Collectors.toList());}/*** 根据配置处理发送的信息** @param message 原消息* @return 被处理后的消息*/private String generatorMessage(String message) {return BooleanUtils.isTrue(webSocketProperties.getExcludeReceiverInfoFlag()) ?StringUtils.substringBefore(message, webSocketProperties.getReceiverSeparator()) : message;}
}

测试

Postman访问WebSocket

点击new,新建WebSocket连接

创建ws连接

连接格式:ws://ip:port/endpoint

例如,本次实例demo的ws连接如下,userName为自定义参数,测试使用,非必须,根据自身需求调整即可

ws://127.0.0.1:18080/ws?userName=test1

点击Connect进行连接

为了方便测试,再创建三个ws连接,也进行Connect

ws://127.0.0.1:18080/ws?userName=test2

ws://127.0.0.1:18080/ws?userName=test3

ws://127.0.0.1:18080/ws?userName=test4

测试

连接后,在test1所在页面发送消息

  • 首先测试@用户的情况

test2、test3可接收消息,test4无消息

  • 而后测试发送给所有人的情况

test2、test3、test4均接收到消息

参考资料

[1].即时通讯demo

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

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

相关文章

2024最新分别用sklearn和NumPy设计k-近邻法对鸢尾花数据集进行分类(包含详细注解与可视化结果)

本文章代码实现以下功能&#xff1a; 利用sklearn设计实现k-近邻法。 利用NumPy设计实现k-近邻法。 将设计的k-近邻法对鸢尾花数据集进行分类&#xff0c;通过准确率来验证所设计算法的正确性&#xff0c;并将分类结果可视化。 评估k取不同值时算法的精度&#xff0c;并通过…

HTML CSS 基础

HTML & CSS 基础 HTML一、HTML简介1、网页1.1 什么是网页1.2 什么是HTML1.3 网页的形成1.4总结 2、web标准2.1 为什么需要web标准2.2 Web 标准的构成 二、HTML 标签1、HTML 语法规范1.1基本语法概述1.2 标签关系 2、 HTML 基本结构标签2.1 第一个 HTML 网页2.2 基本结构标签…

uniapp 游戏 - 使用 uniapp 实现的扫雷游戏

0. 思路 1. 效果图 2. 游戏规则 扫雷的规则很简单。盘面上有许多方格,方格中随机分布着一些雷。你的目标是避开雷,打开其他所有格子。一个非雷格中的数字表示其相邻 8 格子中的雷数,你可以利用这个信息推导出安全格和雷的位置。你可以用右键在你认为是雷的地方插旗(称为标…

中华春节符号·世界品牌——粤港澳企(实)业协会商会经济合作座谈会成功举办

日前&#xff0c;一场旨在推动粤港澳三地经济深度合作的盛会——《粤港澳企&#xff08;实&#xff09;业协会商会经济合作座谈会》在广州市天河区时代TIT广场2栋801车陂社区文化中心隆重举行。此次活动由泰康之家粤园与广东经贸文化促进会联合主办&#xff0c;吸引了全球华人企…

Dubbo SpringBoot应用创建和K8S部署

推荐阅读&#xff1a;Dubbo 快速入门-CSDN博客 创建基于Spring Boot的微服务应用 以下文档将引导您从头创建一个基于 Spring Boot 的 Dubbo 应用&#xff0c;并为应用配置 Triple 通信协议、服务发现等微服务基础能力。 快速创建应用 以下是我们为您提前准备好的示例项目&am…

AI大模型开发架构设计(12)——以真实场景案例驱动深度剖析 AIGC 新时代 IT 人的能力模型

文章目录 以真实场景案例驱动深度剖析 AIGC 新时代 IT 人的能力模型1 AI 工具以及大模型会给我们带来哪些实际收益?业务研发流程环节为什么 LLM 大模型不是万能的?LLM 大模型带来实际收益 2 新时代IT人的能力模型会发生哪些变化?古典互联网架构师能力模型IT人能力模型变化 以…

这都能封!开发者行为导致Google账号关联?

从去年10月开始&#xff0c;在AI加持下&#xff0c;Google Play不断更新和迭代审查机制&#xff0c;Google Play在最近一年的时间真是杀疯了&#xff0c;封号的声音响彻整个行业&#xff0c;尤其是一些敏感品类行业。账号关联&#xff0c;恶意软件&#xff0c;欺骗行为&#xf…

小红书新ID保持项目StoryMaker,面部特征、服装、发型和身体特征都能保持一致!(已开源)

继之前和大家介绍的小红书在ID保持以及风格转换方面相关的优秀工作&#xff0c;感兴趣的小伙伴可以点击以下链接阅读~ 近期&#xff0c;小红书又新开源了一款文生图身份保持项目&#xff1a;StoryMaker&#xff0c;是一种个性化解决方案&#xff0c;它不仅保留了面部的一致性&…

贪吃蛇游戏(代码篇)

我们并不是为了满足别人的期待而活着。 前言 这是我自己做的第五个小项目---贪吃蛇游戏&#xff08;代码篇&#xff09;。后期我会继续制作其他小项目并开源至博客上。 上一小项目是贪吃蛇游戏&#xff08;必备知识篇&#xff09;&#xff0c;没看过的同学可以去看看&#xf…

Angular Count-To 项目教程

Angular Count-To 项目教程 angular-count-to Angular directive to animate counting to a number 项目地址: https://gitcode.com/gh_mirrors/an/angular-count-to 1. 项目介绍 Angular Count-To 是一个用于 AngularJS 的动画计数器指令。该指令可以在指定的时间内从…

Lfsr32

首先分析 Lfsr5 首先要理解什么是抽头点&#xff08;tap&#xff09;&#xff0c;注意到图中有两个触发器的输入为前级输出与q[0]的异或&#xff0c;这些位置被称为 tap position.通过观察上图&#xff0c;所谓抽头点指的就是第5个&#xff0c;第3个寄存器的输入经过了异或逻辑…

利用C++封装鼠标轨迹算法为DLL:游戏行为检测的利器

在现代软件开发中&#xff0c;鼠标轨迹模拟技术因其在自动化测试、游戏脚本编写等领域的广泛应用而备受青睐。本文将介绍如何使用C语言将鼠标轨迹算法封装为DLL&#xff08;动态链接库&#xff09;&#xff0c;以便在多种编程环境中实现高效调用&#xff0c;同时探讨其在游戏行…

cudnn8编译caffe过程(保姆级图文全过程,涵盖各种报错及解决办法)

众所周知,caffe是个较老的框架,而且只支持到cudnn7,但是笔者在复现ds-slam过程中又必须编译caffe,我的cuda版本是11.4,最低只支持到8.2.4,故没办法,只能编译了 在此记录过程、报错及解决办法如下; 首先安装依赖: sudo apt-get install git sudo apt-get install lib…

【IEEE独立出版 | 厦门大学主办】第四届人工智能、机器人和通信国际会议(ICAIRC 2024)

【IEEE独立出版 | 厦门大学主办】 第四届人工智能、机器人和通信国际会议&#xff08;ICAIRC 2024&#xff09; 2024 4th International Conference on Artificial Intelligence, Robotics, and Communication 2024年12月27-29日 | 中国厦门 >>往届均已成功见刊检索…

【Kubernetes① 基础】一、容器基础

目录 一、进程二、隔离与限制三、容器镜像总结参考书籍 一、进程 容器技术的兴起源于PaaS技术(平台即服务)的普及&#xff1b;Docker公司发布的Docker项目具有里程碑式的意义&#xff1b;Docker项目通过“容器镜像”解决了应用打包这个根本性难题(CloudFoundry)。 容器本身的价…

【QAMISRA】解决导入commands.json时报错问题

1、 文档目标 解决导入commands.json时报错“Could not obtain system-wide includes and defines”的问题。 2、 问题场景 客户导入commands.json时报错“Could not obtain system-wide includes and defines”。 3、软硬件环境 1、软件版本&#xff1a; QA-MISRA23.04 2、…

【电路笔记】-运算放大器多谐振荡器

运算放大器多谐振荡器 文章目录 运算放大器多谐振荡器1、概述2、施密特触发器3、运算放大器稳态多谐振荡器4、运算放大器单稳态多谐振荡器5、运算放大器双稳态多谐振荡器6、总结1、概述 本文将重点介绍通常称为多谐振荡器的配置,特别是基于运算放大器的电路。 事实上,多谐振…

AWS账号与邮箱的关系解析

在当今数字化时代&#xff0c;云计算服务的普及使得越来越多的企业和个人用户开始使用亚马逊网络服务&#xff08;AWS&#xff09;。作为全球领先的云服务平台&#xff0c;AWS为用户提供了丰富的计算、存储和数据库服务。然而&#xff0c;对于许多新用户来说&#xff0c;关于AW…

VLOG视频制作解决方案,开发者可自行定制包装模板

无论是旅行见闻、美食探店&#xff0c;还是日常琐事、创意挑战&#xff0c;每一个镜头背后都蕴含着创作者无限的热情和创意。然而&#xff0c;面对纷繁复杂的视频编辑工具&#xff0c;美摄科技凭借其前沿的视频制作技术和创新的解决方案&#xff0c;为每一位视频创作者提供了开…

LLaMA-Factory 让大模型微调变得更简单!!

背景 如果只需要构建一份任务相关的数据&#xff0c;就可以轻松通过网页界面的形式进行 Fine-tuning 微调操作&#xff0c; 那么必将大大减轻微调工作量。 今年的 ACL 2024见证了北航和北大合作的突破—论文《LLAMAFACTORY: 统一高效微调超百种语言模型》。他们打造的 LLaMA-…