基于Spring-boot-websocket的聊天应用开发总结

目录

1.概述

1.1 Websocket

1.2 STOMP

1.3 源码

2.Springboot集成WS

2.1 添加依赖

2.2 ws配置

2.2.1 WebSocketMessageBrokerConfigurer

2.2.2 ChatController

2.2.3 ChatInRoomController

2.2.4 ChatToUserController

2.3 前端聊天配置

2.3.1 index.html和main.js

2.3.2 chatInRoom.html和chatInRoom.js

2.3.3 chatToUser.html和chatToUser.js

2.4 测试

2.4.1 基础的发布订阅测试

2.4.2 群聊测试

2.4.3 私聊测试

3 参考总结


最近在研究通过spring-boot-websocket开发简单的聊天应用,以下对这几天做一下总结。

关于WebRTC原理我主要是通过《WebRTC音视频实时互动技术原理、实战与源码分析》这本书了解底层的框架和实现思路,电子版资料可以私聊我。

1.概述

1.1 Websocket

WebSocket 连接允许客户端服务器进行全双向通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于轮询方式的不停建立连接显然效率要大大提高。

如果仅使用WebSocket完成群聊、私聊功能时需要自己管理session信息,但通过STOMP协议时,Spring已经封装好,开发者只需要关注自己的主题、订阅关系即可。

1.2 STOMP

STOMP即“面向消息的简单文本协议”,提供了能够协作的报文格式,以至于 STOMP 客户端可以与任何 STOMP 消息代理(Brokers)进行通信,从而为多语言,多平台和 Brokers 集群提供简单且普遍的消息协作。

STOMP 协议可以建立在WebSocket 之上,也可以建立在其他应用层协议之上。通过 Websocket建立 STOMP 连接,也就是说在 Websocket 连接的基础上再建立 STOMP 连接。最终实现如上图所示,这一点可以在代码中有一个良好的体现。

主要包含如下几个协议事务:

  • CONNECT:启动与服务器的流或 TCP 连接
  • SEND:发送消息
  • SUBSCRIBE:订阅主题
  • UNSUBSCRIBE:取消订阅
  • BEGIN:启动事务
  • COMMIT:提交事务
  • ABORT:回滚事务
  • ACK:确认来自订阅的消息的消费
  • NACK:告诉服务器客户端没有消费该消息
  • DISCONNECT:断开连接

1.3 源码

git地址:https://github.com/BAStriver/spring-boot-websocket-chat-app

下载路径:https://download.csdn.net/download/BAStriver/88711460

2.Springboot集成WS

2.1 添加依赖

<dependencies><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.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-messaging</artifactId><version>6.0.7</version></dependency>
</dependencies>

2.2 ws配置

2.2.1 WebSocketMessageBrokerConfigurer

这里主要是配置STOMP协议端点、消息代理。

并且设置了前端发布消息的前缀为/app,和消息代理的前缀/topic(@SendTo中为/topic/*)。

// register STOMP endpoints
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/ws") // this is the endpoint which should be set in SockJS client.setAllowedOriginPatterns("*") // allow cross-domain request.withSockJS(); // use SockJS protocol
}// register message broker
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {// while sending messages in front end, the path should add the prefix as /appregistry.setApplicationDestinationPrefixes("/app");// enable and set the prefixes of broker paths, like /topic/public// without this prefix, it will block those sent messagesregistry.enableSimpleBroker("/topic", "/user");// while sending messages to user in front end, the path should add the prefix as /user// default is /userregistry.setUserDestinationPrefix("/user");
}

2.2.2 ChatController

以下是基础的控制器,通过sendMessage()发布消息,通过addUser()把订阅者加入到session管理,并最终返回到订阅路径/topic/public。 

@MessageMapping("/chat.sendMessage")
@SendTo("/topic/public")
public ChatMessage sendMessage(@Payload ChatMessage chatMessage
) {return chatMessage;
}@MessageMapping("/chat.addUser")
@SendTo("/topic/public")
public ChatMessage addUser(@Payload ChatMessage chatMessage,SimpMessageHeaderAccessor headerAccessor
) {// Add username in web socket sessionheaderAccessor.getSessionAttributes().put("username", chatMessage.getSender());return chatMessage;
}

经过上面的方法可以实现发布订阅模式。

值得注意的是,如果没有配置@SendTo,则消息会默认返回到@MessageMapping的路径给订阅者。

2.2.3 ChatInRoomController

这个主要是实现群聊。

@MessageMapping("/chat/{roomId}")
@SendTo("/topic/chat/{roomId}") // if not add @SendTo, then by default will send to the path /topic/chat/{roomId}
public ChatMessage sendMessage(@DestinationVariable String roomId, ChatMessage message) {log.info("roomId: {}", roomId);return message;
}// if need the {roomId} in @SendTo,
// then should add {roomId} in @MessageMapping and sent roomId from front end.
// otherwise, it could not resolve placeholder 'roomId' in value "/topic/chat/{roomId} of @SendTo
@MessageMapping("/chat.addUserToRoom/{roomId}")
@SendTo("/topic/chat/{roomId}")
public ChatMessage addUser(@Payload ChatMessage chatMessage,SimpMessageHeaderAccessor headerAccessor
) {// Add username in web socket sessionheaderAccessor.getSessionAttributes().put("username", chatMessage.getSender());return chatMessage;
}

值得注意的是,如果@SendTo需要{roomId}这个参数,那么在@MessageMapping()中也需要传入{roomId}。

2.2.4 ChatToUserController

这个主要实现单独发布消息到指定的订阅者。

@MessageMapping("/chatToUser/{userId}")
@SendTo(value = "/topic/chatToUser/{userId}")
public ChatMessage sendMessage(@DestinationVariable String userId, ChatMessage message,SimpMessageHeaderAccessor headerAccessor) {log.info("send to the userId: {}", userId);log.info("message: {}", message);//        Set<StompAuthenticatedUser> collect = simpUserRegistry.getUsers().stream()
//                .map(simpUser -> StompAuthenticatedUser.class.cast(simpUser.getPrincipal()))
//                .collect(Collectors.toSet());
//        collect.forEach(user -> {
//            if(user.getNickName().equals(userId)) {
//                simpMessagingTemplate.convertAndSendToUser(userId, "/chatToUser/"+userId, message);
//            }
//        });return message;
}@MessageMapping("/chat.helloUser/{userId}")
@SendTo("/user/chat/{userId}")
public ChatMessage helloUser(@DestinationVariable String userId,@Payload ChatMessage chatMessage,SimpMessageHeaderAccessor headerAccessor
) {// Add username in web socket sessionheaderAccessor.getSessionAttributes().put("username", chatMessage.getSender());headerAccessor.getSessionAttributes().put("userid", userId);// use the tool to send the message to public topic directly, without @MessageMapping// simpMessagingTemplate.convertAndSend("/user/chat/" + userId, chatMessage);return chatMessage;
}//@MessageMapping("/chat.sendMessage")
@GetMapping("/testSendMessage")
public void testSendMessage(ChatMessage message) {// use the tool to send the message to public topic directly, without @MessageMappingsimpMessagingTemplate.convertAndSend("/topic/public", message);
}

值得注意的是,这里的@MesssageMapping()不要和前面的重复了。

同样的,也可以通过如下的代码实现发布消息。

simpMessagingTemplate.convertAndSend("/user/chat/" + userId, chatMessage);

其实这个部分和#2.2.3同理,不同的是私聊其实可以用@SendToUser。 

2.3 前端聊天配置

SockJS 是一个浏览器的 JavaScript库,它提供了一个类似于网络的对象,SockJS 提供了一个连贯的,跨浏览器的JavaScriptAPI,它在浏览器和 Web 服务器之间创建了一个低延迟、全双工、跨域通信通道。SockJS 的一大好处在于提供了浏览器兼容性。即优先使用原生WebSocket,如果浏览器不支持 WebSocket,会自动降为轮询的方式。如果你使用 Java 做服务端,同时又恰好使用 Spring Framework 作为框架,那么推荐使用SockJS。

2.3.1 index.html和main.js

对应#2.2.2的前端页面和脚本。

这里初始化一个sockjs实例,其中的/ws指定了#2.2.1的STOMP端点。

function connect(event) {username = document.querySelector('#name').value.trim();if(username) {usernamePage.classList.add('hidden');chatPage.classList.remove('hidden');const header = {"User-ID": new Date().getTime().toString(),"User-Name": username};var socket = new SockJS('/ws'); // set the STOMP endpointstompClient = Stomp.over(socket);stompClient.connect(header, onConnected, onError);}event.preventDefault();
}

当客户端和服务Connected之后,开始订阅/topic/public的消息以及设置send()的消息发布路径。

function onConnected() {// Subscribe to the Public TopicstompClient.subscribe('/topic/public', onMessageReceived);// Tell your username to the serverstompClient.send("/app/chat.addUser", // prefix with /app{},JSON.stringify({sender: username, type: 'JOIN'}))connectingElement.classList.add('hidden');
}function sendMessage(event) {var messageContent = messageInput.value.trim();if(messageContent && stompClient) {var chatMessage = {sender: username,content: messageInput.value,type: 'CHAT'};stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));messageInput.value = '';}event.preventDefault();
}

值得注意的是,这里send()的时候要记得加/app作为前缀。

2.3.2 chatInRoom.html和chatInRoom.js

对应#2.2.3的前端页面和脚本。

初始化sockjs实例和上面的一样,但要注意的是Connected之后的订阅和发布路径加上了{room}作为聊天室的id。

function onConnected() {// Subscribe the message of the {room}stompClient.subscribe('/topic/chat/'+room, onMessageReceived);// Tell your username to the serverstompClient.send("/app/chat.addUserToRoom/"+room, // prefix with /app{},JSON.stringify({sender: username, type: 'JOIN'}))connectingElement.classList.add('hidden');
}function sendMessage(event) {var messageContent = messageInput.value.trim();if(messageContent && stompClient) {var chatMessage = {sender: username,content: messageInput.value,type: 'CHAT'};stompClient.send("/app/chat/"+room, {}, JSON.stringify(chatMessage));messageInput.value = '';}event.preventDefault();
}

2.3.3 chatToUser.html和chatToUser.js

对应#2.2.3的前端页面和脚本。

初始化sockjs实例和上面的一样,但要注意的是Connected之后的订阅和发布路径加上了{username}和{userid}作为私聊对象id。

function onConnected() {console.log('username: ', username);console.log('userid: ', userid);// Subscribe the message with {userid}stompClient.subscribe('/user/chat/' + username, onMessageReceived);stompClient.subscribe('/topic/chatToUser/' + username, onMessageReceived);// Tell your username to the serverstompClient.send("/app/chat.helloUser/" + username, // prefix with /app{},JSON.stringify({sender: username, type: 'JOIN'}))connectingElement.classList.add('hidden');
}function sendMessage(event) {var messageContent = messageInput.value.trim();if (messageContent && stompClient) {var chatMessage = {sender: username,content: messageInput.value,type: 'CHAT'};stompClient.send("/app/chatToUser/" + userid, {}, JSON.stringify(chatMessage));messageInput.value = '';}event.preventDefault();
}

2.4 测试

2.4.1 基础的发布订阅测试

这里测试的#2.3.1的部分。

首先是登录界面,进入:http://localhost:8080/index.html

打开两个index页面,然后输入username之后实现聊天。

第一个index.html登入BAS用户,第二个页面登入BAS55。

2.4.2 群聊测试

这里测试的#2.3.2的部分。

首先是登录界面,进入:http://localhost:8080/chatInRoom.html

第一个index.html登入BAS用户(Room: 12345),

第二个页面登入BAS55(Room: 12345),

第三个页面登入BAS10(Room: 123),BAS10单独在一个房间

2.4.3 私聊测试

这里测试的#2.3.3的部分。

首先是登录界面,进入:http://localhost:8080/chatToUser.html

第一个index.html登入BAS用户(Chat To: BAS5),

第二个页面登入BAS55(Chat To: BAS),

第三个页面登入BAS10(Chat To: BAS9)。

3 参考总结

以下是开发过程中参考并且觉得挺有帮助的资料:

SpringBoot——整合WebSocket(STOMP协议) - 简书

Spring Boot系列 WebSocket集成简单消息代理_websocketmessagebrokerconfigurer-CSDN博客

WebSocket的那些事(4-Spring中的STOMP支持详解)_simpuserregistry 为空-CSDN博客

注:

1.关于@MessageMapping()的使用可以参考:Spring Boot中的@MessageMapping注解:原理及使用-CSDN博客

2.关于AbstractWebSocketHandler的使用可以参考:WebSocket基本概念及在Spring Boot中的使用 - 知乎

3.关于@SendTo()和@SendToUser()的区别和使用可以参考:在Spring WebSocket中使用@SendTo和@SendToUser进行消息路由 - 实时互动网

Spring-messaging (STOMP) @SendTo 与 @SendToUser的区别-CSDN博客

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

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

相关文章

mnn-llm: 大语言模型端侧CPU推理优化

在大语言模型(LLM)端侧部署上&#xff0c;基于 MNN 实现的 mnn-llm 项目已经展现出业界领先的性能&#xff0c;特别是在 ARM 架构的 CPU 上。目前利用 mnn-llm 的推理能力&#xff0c;qwen-1.8b在mnn-llm的驱动下能够在移动端达到端侧实时会话的能力&#xff0c;能够在较低内存…

MySQL之视图外连接、内连接和子查询的使用

一、视图 1.1 含义 虚拟表&#xff0c;和普通表一样使用 1.2 操作 创建视图 create view 视图名 as 修改视图 方式一&#xff1a; create or replace view 视图名 as 【查看视图相关字段】 方式二&#xff1a; alter view 视图名 as 【查看的SQL语句】 查看视图 方式一&…

Nginx快速入门:Nginx实现高可用|结合keepalived实现主备节点(九)

0. 引言 在生产中我们要尽可能避免单点故障&#xff0c;nginx也不例外&#xff0c;因此搭建主备节点必不可少&#xff0c;今天我们来学习下如何利用keepalived实现主备 1. keepalived简介 keepalived 是一个LINUX系统中开源的负载均衡和故障转移软件&#xff0c;它主要用于高…

2024年阿里云、腾讯云、华为云、LightNode、硅云服务器如何选?怎么买最划算?[最新价格表]

很多小伙伴都有一颗上云的心&#xff0c;包括我自己 有事没事的折腾一下自己的小破站&#xff0c;也挺有意思的&#xff01; 那么&#xff0c;云服务器哪家好&#xff1f;优惠力度哪家大&#xff1f;活动入口哪里进&#xff1f;云服务器如何配置&#xff1f;如何选型&#xf…

K8S集群部署解决工作节点couldn‘t get current server API group list问题

最近在自己电脑上装了VMWare Player&#xff0c;在上面装了两个Ubuntu虚拟机&#xff0c;为了方便学习云原生技术&#xff0c;决定在上面装一个2个节点&#xff08;一个控制面&#xff0c;一个工作节点&#xff09;的K8S集群。 参考这篇文章&#xff1a; Ubuntu 22.04 搭建K8…

kubectl的插件安装工具krew

最近得知一个kubectl插件安装工具&#xff0c;叫做krew。 官网地址是&#xff1a;Krew – kubectl plugin manager 安装krew 按照官网的做法&#xff0c;一直安装失败&#xff0c;于是拆解步骤&#xff0c;一步一步下载离线安装。 1、下载krew.yaml 地址&#xff1a;https:…

Proteus 各版本安装指南

Proteus下载链接 https://pan.baidu.com/s/1vHgg8jK9KSHdxSU9SDy4vQ?pwd0531 1.鼠标右击【Proteus8.15(64bit&#xff09;】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到Proteus8.15(64bit&#xff09; 】。 2.打开解压后的文件夹&#…

适用于生物行业的生信云平台

随着基因检测技术的不断发展&#xff0c;生物信息云平台在基因检测行业的应用越来越广泛。生物信息云平台是一种基于云计算的技术&#xff0c;可以将基因检测数据存储在云端&#xff0c;并通过数据分析、挖掘等技术手段&#xff0c;对基因数据进行处理、分析和解读。 这种技术的…

Linux安装nginx(带http ssl)

nginx安装 nginx文件 以及gcc pcre zlib openssl 网盘下载 1.安装gcc yum -y install gcc gcc-c 2.安装pcre rpm -ivh pcre-8.32-17.el7.x86_64.rpm --force --nodeps rpm -ivh pcre-devel-8.32-17.el7.x86_64.rpm --force --nodeps 3.安装zlib tar -zxvf zlib-1.2.11.ta…

metaSPAdes,megahit,IDBA-UB:宏基因组装软件安装与使用

metaSPAdes,megahit,IDBA-UB是目前比较主流的宏基因组组装软件 metaSPAdes安装 GitHub - ablab/spades: SPAdes Genome Assembler #3.15.5的预编译版貌似有问题&#xff0c;使用源码安装试试 wget http://cab.spbu.ru/files/release3.15.5/SPAdes-3.15.5.tar.gz tar -xzf SP…

C++ 给父类带参构造函数的赋值

在类的使用中&#xff0c;默认的构造函数不带任何参数&#xff0c;但是也会因为需要而使用带参数的构造函数。 在带参的构造函数中&#xff0c;是如何继承的呢&#xff0c;这里我们通过使用基类&#xff0c;子类&#xff0c;孙类的两重继承来观察&#xff0c;如何给带参构造函数…

完美版视频网站模板 – 苹果CMS v10大橙子vfed主题

源码下载&#xff1a; https://download.csdn.net/download/m0_66047725/88700504 这次提供的大橙子 vfed 模板 已经完美&#xff0c;只去除了授权验证和正版主题神秘后门&#xff0c;不影响任何功能体验性。主题优化&#xff1a;全站响应式自带主题设置面板自带联盟资源库大全…

【C/C++】开源串口库 CSerialPort 应用

文章目录 1、简述2、效果图2.1、命令行&#xff08;不带GUI&#xff09;2.2、GUI&#xff08;这里用的Qt&#xff09; 3、串口硬件知识普及4、核心实现4.1、Qt的pro文件4.2、main文件4.3、SSerialPort类4.3.1、头文件4.3.2、源文件 4.4、Linux下的CMakeLists.txt 1、简述 本文…

设计模式之单例模式的懒饿汉

懒汉式 说白了就是你不叫我我不动&#xff0c;你叫我我才动。 类初始化模式&#xff0c;也叫延迟占位模式。在单例类的内部由一个私有静态内部类来持有这个单例类的实例。因为在 JVM 中&#xff0c;对类的加载和类初始化&#xff0c;由虚拟机保证线程安全。 public class Singl…

cesium键盘控制模型

效果&#xff1a; 由于对添加模型和更新位置api进行二次了封装&#xff0c;下面提供思路 1.添加模型 const person reactive({modelTimer: null,position: {lon: 104.07274,lat: 30.57899,alt: 1200,heading: 0,pitch: 0,roll: 0,}, }); window.swpcesium.addEntity.addMo…

SSH 无密登录配置

1)配置 ssh (1)基本语法 ssh 另一台电脑的 IP 地址 (2)ssh 连接时出现 Host key verification failed 的解决方法 [yuxuan@yuxuan102 ~]$ ssh yuxuan103 ➢ 如果出现如下内容 Are you sure you want to continue connecting (yes/no)? ➢ 输入 yes,并回车 (3)退回到 …

简单 Web Server 程序的设计与实现 (2024)

1.题目描述 Web 服务是 Internet 最方便与受用户欢迎的服务类型&#xff0c;它的影响力也远远超出了专业技术范畴&#xff0c; 已广泛应用于电子商务、远程教育、远程医疗与信息服务等领域&#xff0c;并且有继续扩大的趋势。目前很多 的 Internet 应用都是基于 Web 技术的&…

Linux驱动学习—中断

1、中断基础概念 1.1 什么是中断 CPU在正常运行期间&#xff0c;由外部或者内部引起的时间&#xff0c;让CPU停下当前正在运行的程序&#xff0c;转而去执行触发他的中断所对应的程序&#xff0c;这就是中断。 响应中断的过程&#xff1a; <1>中断请求 <2>中断…

三、C语言中的分支与循环—for循环 (6)

本章分支结构的学习内容如下&#xff1a; 三、C语言中的分支与循环—if语句 (1) 三、C语言中的分支与循环—关系操作符 (2) 三、C语言中的分支与循环—条件操作符 与逻辑操作符(3) 三、C语言中的分支与循环—switch语句&#xff08;4&#xff09;分支结构 完 本章循环结构的…

JavaScript基本语法

文章目录 1. JavaScript 是什么1.1 JavaScript 和 HTML 和 CSS 之间的关系1.2 JavaScript 运行过程1.3 JavaScript 的组成 2. JavaScript 的书写形式2.1 行内式2.2 内嵌式2.3 外部式 3. 变量的使用3.1 静态变量和动态变量 4. 基本数据类型4.1 undefined 未定义数据类型4.2 null…