当前位置: 首页 > news >正文

使用WebSocket、SockJS、STOMP实现消息实时通讯功能

客户端

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head><title>websocket client</title><script src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.min.js"></script>  <script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.js"></script>  <script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>  <script type="text/javascript">var stompClient = null;   //加载完浏览器后  调用connect(),打开双通道  $(function(){     //打开双通道  connect()  })    //强制关闭浏览器  调用websocket.close(),进行正常关闭  window.onunload = function() {  disconnect()  }  function connect(){  // 当前登录的用户idvar userId="4a85f897fc48360be1f8e6abeec40c16";  //连接SockJS的endpoint名称为"webSocket"  var socket = new SockJS('http://xxx.xxx.xxx.xxx:8080/webSocket');//使用STMOP子协议的WebSocket客户端  stompClient = Stomp.over(socket);// socket 链接传递 Authorization:token 信息var headers={"Authorization": "token"}//   var testMsg = JSON.stringify({'message':'Hello WebSocket!','userId':userId});//连接WebSocket服务端     stompClient.connect(headers,function(frame){      console.log('Connected:' + frame);  //通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息  stompClient.subscribe('/user/'+userId+'/single/ip',function(response){  console.log("点对点消息");console.log(response);var message=JSON.parse(response.body);                                   console.log(message);   showResponse(message);				});  stompClient.subscribe('/topic',function(response){  console.log("订阅消息");console.log(response);var message=JSON.parse(response.body);                                   console.log(message);                    }); // 客户端给服务端发送消息stompClient.send("/app/testSendMsg",{},testMsg);			});  }  //关闭双通道  function disconnect(){  if(stompClient != null) {  stompClient.disconnect();  }  console.log("Disconnected");  }  function showResponse(message){  var response = $("#response");  var msg = JSON.parse(message.responseMessage);  response.append("<p>只有userID为"+msg.userId+"的人才能收到</p>");  }  </script>
</head>
<body><pre id="response"></pre>
</body>
</html>

服务端

pom

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency> 

初始化config

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;/*** webSocket 初始化类*/
@Configuration
// @EnableWebSocketMessageBroker 注解用于开启使用 STOMP 协议来传输基于代理(MessageBroker)的消息,这时候控制器(controller)
// 开始支持@MessageMapping,就像是使用 @requestMapping 一样。
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {@Lazy@Autowiredprivate WebsocketUserInterceptor websocketUserInterceptor;@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {//注册一个名为 /oauth/ws 的 Stomp 节点(endpoint),并指定使用 SockJS 协议。registry.addEndpoint("/webSocket").setAllowedOrigins("*").withSockJS();}@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {//点对点应配置一个/user消息代理,广播式应配置一个/topic消息代理registry.enableSimpleBroker("/topic","/user");//客户端向服务端发起请求时,需要以/app为前缀。registry.setApplicationDestinationPrefixes("/app");//点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/registry.setUserDestinationPrefix("/user");}/*** 采用自定义拦截器,获取connect时候传递的参数* @param registration*/@Overridepublic void configureClientInboundChannel(ChannelRegistration registration) {registration.interceptors(websocketUserInterceptor);}
}

拦截器

@Slf4j
@Component
public class WebsocketUserInterceptor implements ChannelInterceptor {@Autowiredprivate WebSocketServer webSocketServ;@Overridepublic Message<?> preSend(Message<?> message, MessageChannel channel) {StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);if (StompCommand.CONNECT.equals(accessor.getCommand())) {log.info("【webSocket】 --- 连接success");Object raw = message.getHeaders().get(SimpMessageHeaderAccessor.NATIVE_HEADERS);if (raw instanceof Map) {Object tokenObj = ((Map) raw).get(JwtTokenUtil.AUTH_HEADER_KEY);if (tokenObj instanceof LinkedList) {String token = ((LinkedList) tokenObj).get(0).toString();//设置当前访问器的认证用户String userId = this.getUserIdFromToken(token);accessor.setUser(new WebsocketUserVO(userId));webSocketServ.pushOnlineUser(userId);log.info("【webSocket】 --- userId:{} 上线了,在线数量:{}",userId,webSocketServ.getOnlineUserSize());}}}return message;}/*** 发送消息调用后立即调用 一般用于监听上下线*/@Overridepublic void postSend(Message<?> message, MessageChannel channel, boolean sent) {try {StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);//消息头访问器if (accessor.getCommand() == null){ return;}// 避免非stomp消息类型,例如心跳检测switch(accessor.getCommand()){case DISCONNECT://点击断开连接,这里会执行两次,第二次执行的时候,message.getHeaders.size()=5,第一次是6。直接关闭浏览器,只会执行一次,size是5。log.info("【webSocket】 --- 断开连接");MessageHeaders headers = message.getHeaders();if(ObjectUtil.isNotNull(headers)) {Object header = headers.get(SimpMessageHeaderAccessor.USER_HEADER);log.info("【webSocket】 --- header:{}", header.toString());if (ObjectUtil.isNotNull(header)) {WebsocketUserVO vo = (WebsocketUserVO) header;webSocketServ.removeOnlineUser(vo.getName());log.info("【webSocket】 断开连接 --- userId:{} 下线了,在线数量:{}", vo.getName(), webSocketServ.getOnlineUserSize());}break;}case MESSAGE:break;case SEND:break;}} catch (Exception e) {log.info("【webSocket】 断开连接 - 异常:{}", e.getMessage());}}// 解析token获取userIdprivate String getUserIdFromToken(String token){token = token.replace(JwtTokenUtil.TOKEN_PREFIX,"");LoginUser user = JSON.parseObject(JWT.decode(token).getSubject(),LoginUser.class);String userId = user.getUserId();log.info("【webSocket】解析token获取userId:{}",userId);return userId;}
}

WebSocketServer

@Component
public class WebSocketServer {public static String WEB_SOCKET_KEY="webSocket:register:";@Autowiredprivate StringRedisTemplate stringRedisTemplate;// 用户上线public void pushOnlineUser(String id) {stringRedisTemplate.opsForValue().set(WEB_SOCKET_KEY+id,id);}// 用户下线public void removeOnlineUser(String id) {stringRedisTemplate.delete(WEB_SOCKET_KEY+id);}// 获取在线用户数量public Integer getOnlineUserSize() {return this.getOnlineUserList().size();}// 获取在线用户集合public List<String> getOnlineUserList() {Set<String> keys = stringRedisTemplate.keys(WEB_SOCKET_KEY + "*");return stringRedisTemplate.opsForValue().multiGet(keys);}
}

实体bean

public class WebsocketUserVO implements Principal {private  String name;public WebsocketUserVO(String name) {this.name = name;}@Overridepublic String getName() {return name;}
}
/*** 服务器向浏览器发送消息用这个类*/
public class Server2ClientMessage {private String responseMessage;public Server2ClientMessage(String responseMessage) {this.responseMessage = responseMessage;}public String getResponseMessage() {return responseMessage;}public void setResponseMessage(String responseMessage) {this.responseMessage = responseMessage;}
}
/*** 浏览器向服务器发送消息用这个类*/
public class Client2ServerMessage {private String userId;private String message;public String getUserId() {return userId;}public void setUserId(String userId) {this.userId = userId;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}
}

测试controller

@Controller
public class SockertController {@Autowiredprivate SimpMessagingTemplate simpMessagingTemplate;@MessageMapping("/testSendMsg")public BaseResponse testSendMsg(Client2ServerMessage socketBean) {System.out.println("收到客户端消息:"+ JSON.toJSON(socketBean));System.out.println("开始发送服务器端消息");Server2ClientMessage message = new Server2ClientMessage("{\"userId\":\""+socketBean.getUserId()+"\"}");// 点对点消息simpMessagingTemplate.convertAndSendToUser(socketBean.getUserId(),"/single/ip",message);// 广播消息simpMessagingTemplate.convertAndSend("/topic",message);System.out.println("结束发送服务器端消息");return BaseResponse.success("操作成功");}
}

测试效果

在这里插入图片描述

nginx配置协议升级

proxy_http_version 1.1
proxy_set_header Upgrade $http_upgrade
proxy_set_header Connection 'upgrade'

相关文章:

使用WebSocket、SockJS、STOMP实现消息实时通讯功能

客户端 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head><title>websocket client</title><script src"http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.min.js"></script>…...

C++回顾(十一)—— 动态类型识别和抽象类

11.1 动态类识别 11.1.1 自定义类型 C中的多态根据实际的对象类型调用对应的函数 &#xff08;1&#xff09;可以在基类中定义虚函数返回具体的类型信息 &#xff08;2&#xff09;所有的派生类都必须实现类型相关的虚函数 &#xff08;3&#xff09;每个类中的类型虚函数都需…...

雷电模拟器安卓7以上+Charles抓包APP最新教程

一、工具准备&#xff1a; 证书安装工具全局代理工具下载&#xff1a; https://download.csdn.net/download/weixin_51111267/87536481 二、Charles设置 &#xff08;一&#xff09;电脑上证书安装 &#xff08;二&#xff09;安卓模拟器上系统证书安装&#xff08;RooT权限打…...

vsvode 配置sftp,连接远程linux全过程

在本地安装sftp插件&#xff0c;配置参数https://blog.csdn.net/u011119817/article/details/106630599在linux机台安装vscode-service服务https://zhuanlan.zhihu.com/p/294933020连接超时&#xff0c;将配置文件添加超时时间遇到的错误处理&#xff1a;(272条消息) 【vscode插…...

C++类转换为蓝图、打印日志、蓝图关卡、删除C++文件

蓝图宏 UCLASS(Blueprintable)//c脚本可转换为蓝图 UPROPERTY(BlueprintReadWrite)//蓝图中可创建set&#xff0c;get节点 UFUNCTION(BlueprintCallable)//可创建函数节点 UPROPERTY(BlueprintReadWrite,Category”My Variables”)//节点进行分类打印日志 UE_LOG(LogTemp, Lo…...

elasticsearch高级篇:核心概念和实现原理

1.elasticsearch核心概念1.1 索引(index)一个索引就是一个拥有几分相似特征的文档的集合。比如说&#xff0c;你可以有一个客户数据的索引&#xff0c;另一个产品目录的索引&#xff0c;还有一个订单数据的索引。一个索引由一个名字来标识&#xff08;必须全部是小写字母&#…...

部署安装Nginx服务实例

其他服务&#xff1a; 搭建zabbix4.0监控服务实例 普罗米修斯监控mysql数据库实战 Linux安装MySQL数据库步骤 一. Nginx概念介绍 1.介绍Nginx程序 Nginx (engine x) 是一款开源且高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。主要特点是占用…...

云原生架构设计原则及典型技术

云原生是面向云应用设计的一种思想理念&#xff0c;充分发挥云效能的最佳实践路径&#xff0c;帮助企业构建弹性可靠、松耦合、易管理可观测的应用系统&#xff0c;提升交付效率&#xff0c;降低运维复杂度。代表技术包括不可变基础设施、服务网格、声明式 API 及 Serverless 等…...

【Linux】-- 工具介绍 vim_gcc/g++_gdb

目录 Linux中的软件管理工具 – yum 在Linux下安装软件的方式 认识yum 查找软件包 安装 卸载 lrzsz.x86_64 rz sz Linux中的编辑器 – vim vim的基本概念 vim各模式切换 vim命令模式命令 vim底行模式命令 gcc / g gcc / g的作用 gcc / g语法 预处理 编译 汇…...

JAVA SE: IO流

一、Java流式输入输出原理Java对于输入输出是以流(Stream)的方式进行的&#xff0c;JDK提供各种各样的“流”类&#xff0c;以获取不同类型的数据。可以理解为将管道插入到文件中&#xff0c;然后从管道获取数据。这个管道外边还可以套管道&#xff0c;外边的管道对数据进行处理…...

打破原来软件开发模式的无代码开发平台

前言传统的系统开发是需要大量的时间和成本的&#xff0c;如今无代码开发平台的出现就改变了这种状况。那么你知道什么是无代码开发平台?无代码开发对企业来说有什么特殊的优势么?什么是无代码平台无代码平台指的是&#xff1a;使用者无需懂代码或手写代码&#xff0c;只需通…...

06-redux中的hook

知识点06-redux的hook 在函数组件中要和redux连接&#xff0c;分为两个步骤 前提状态机已经主备就绪 注入store到根组件 在函数组件中&#xff0c;使用Provider包裹根组件&#xff0c;并将store注入这一步&#xff0c;依旧是不能少的 import store from "./redux/store…...

watch监听不到数组对象的变化

watch监听不到数组对象的变化一、利用索引直接改变arr的值二、修改数组的长度arr.length三、添加和修改对象属性和值Vue不能监听到数组和对象值的变化其实和双向绑定的原理有关。Vue双向绑定原理是利用js中的Object.defineproperty重定义对象的GET和SET方法&#xff0c;而同时这…...

言语理解与表达之语句表达

考点一语句填空提问方式&#xff1a;填入划横线处最恰当的一句是&#xff08; &#xff09;1.横线在结尾&#xff1a;总结前文提出对策2.横线在开头&#xff1a;需概括文段的中心内容3.横线在中间&#xff1a;注意与上下文联系把握好主题词&#xff0c;保证文段话题一致实例1和…...

2023年全国最新食品安全管理员精选真题及答案14

百分百题库提供食品安全管理员考试试题、食品安全员考试预测题、食品安全管理员考试真题、食品安全员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 131.食品生产企业在一年内&#xff08;&#xff09;次因违反《中华人民共…...

【MySQL】约束

文章目录1. 约束2. 非空约束 NOT NULL3. 唯一性约束 UNIQUE4. 主键约束 PRIMARY KEY5. 自增约束 AUTO_INCREMENT6. 外键约束FOREIGN KEY7. 默认值约束 DEFAULT8. 小结1. 约束 为了保证数据的完整性&#xff0c;SQL规范以约束的方式对表数据进行额外的条件限制。从以下四个方面…...

C语言学习(三)

#include <stdio.h> int main(void){int a; scanf("%d",&a); printf("%d",a); return 0&#xff1b;} 正在上传…重新上传取消正在上传…重新上传取消&符号作用是把键盘中输入的值给变量a,使用scanf()时输入数值&#xff0c;需要按一下enter…...

TOUGH系列软件建模及在地下水、CO2地质封存、水文地球化学、地热等多相多组分系统多过程耦合

TOUGH2系列软件传统地下水模拟软件Feflow和Modflow不同&#xff0c;TOUGH2系列软件采用模块化设计和有限积分差网格剖分方法&#xff0c;通过配合不同EOS模块&#xff0c;软件可以处理各种复杂地质条件下&#xff0c;诸如地热能开发&#xff0c;非饱和带水气运移、油气运移&…...

k8s学习之路 | k8s 工作负载 ReplicaSet

文章目录1. ReplicaSet 基础概念1.1 RS 是什么&#xff1f;1.2 RS 工作原理1.3 什么时候使用 RS1.4 RS 示例1.5 非模板 Pod 的获得1.6 编写 RS1.7 使用 RS1.8 RS 替代方案2. ReplicaSet 与 ReplicationController2.1 关于 RS、RC2.2 两者的选择器区别2.3 总结1. ReplicaSet 基础…...

python实现半色调技术图像转换

半色调技术 半色调技术是一种将灰度图像转换为黑白图像的技术。它是通过将灰度图像的像素值映射到黑白像素值上来实现的。 比如说&#xff0c;在一块只能显示纯黑或纯白的屏幕上&#xff0c;如何将一张灰度图显示出灰度的效果&#xff0c;这时就可以用半色调技术实现。 如下…...

c++面试技巧-基础篇

1.面试官&#xff1a;简述C语言的优缺点。 应聘者&#xff1a;C语言的优缺点如下。 • C语言的优点&#xff1a;C语言既保留了C语言的有效性、灵活性、便于移植等全部精华和特点&#xff0c;又添加了面向对象编程的支持&#xff0c;具有强大的编程功能&#xff0c;可方便地构…...

三八妇女节即将到来,跨境电商如何玩转节日营销?

随着国际妇女节的到来&#xff0c;跨境电商商家们都开始了为自己的店铺进行节日营销的准备。商家做节日营销的目的都是一样的&#xff0c;无非都是增加曝光率&#xff0c;拉动客流&#xff0c;增加营业额。但是每一个节日营销的形式是可以不一样的。今年有什么营销玩法呢&#…...

【Java学习笔记】10.条件语句 - if...else及switch case 语句

前言 本章介绍Java的条件语句 - if…else和switch case 语句。 Java 条件语句 - if…else Java 中的条件语句允许程序根据条件的不同执行不同的代码块。 一个 if 语句包含一个布尔表达式和一条或多条语句。 语法 if 语句的语法如下&#xff1a; if(布尔表达式) {//如果布…...

解析STM32启动过程

相对于ARM上一代的主流ARM7/ARM9内核架构&#xff0c;新一代Cortex内核架构的启动方式有了比较大的变化。ARM7/ARM9内核的控制器在复位后&#xff0c;CPU会从存储空间的绝对地址0x000000取出第一条指令执行复位中断服务程序的方式启动&#xff0c;即固定了复位后的起始地址为0x…...

微信小程序开发自学笔记 —— 八、小程序基础库的更新迭代

小程序基础库的更新迭代 小程序基础库 小程序的运行环境是分成渲染层和逻辑层的&#xff0c;在渲染层可以用各类组件组建界面的元素&#xff0c;在逻辑层可以用各类API来处理各种逻辑&#xff0c;组件、API其实都是小程序基础库进行包装提供的&#xff0c;基础库的职责还要处…...

Mysql迁移Postgresql

目录原理环境准备操作系统(Centos7)Mysql客户端安装Psql客户端安装数据库用户空字符串处理成null导表脚本dbmysql2pgmysqlcopy测试在mysql中建表导表测试查看pg中的表原理 Mysql抽取&#xff1a;mysql命令重定向到操作系统文件&#xff0c;处理成csv文件&#xff1b; PG装载&a…...

关于信息安全认证CISP、PTE对比分析

CISP 注册信息安全专业人员 CISP-PTE 注册渗透测试工程师&#xff08;以下简称PTE&#xff09; 1 、发证机构 CISP与PTE的发证机构都是中国信息安全测评中心&#xff0c;政府背景给认证做背书&#xff0c;学员信息都在中国政府可控的机构手中&#xff1b; 如果想在政府、国…...

游戏场景编辑器和骨骼动画相关软件

游戏场景编辑器 一.Tiled(2D) Tiled 是帮助你开发游戏内容的 2D 地图编辑器。它的主要功能是可以编辑各种形式的瓦片地图&#xff0c;还支持通过用空图片这种强大的方式来标记额外信息给游戏使用。Tiled 关注的是总体灵活性&#xff0c;同时尽量保持直观性。 Tiled Map 不但…...

vue3常用的API

目录 1.ref函数 2.reactive函数 3.reactive对比ref 4.computed函数 5.watch函数 6.toRef 7..provide && inject 1.ref函数 作用: 定义一个响应式的数据 语法: const xxx ref(initValue) 创建一个包含响应式数据的引用对象&#xff08;reference对象&#xff…...

Qt中使用

LIB库路径&#xff0c;include 头文件&#xff0c;运行的时候记得吧dll库带上&#xff0c;这基本就完成了。准备工作&#xff1a;Qt可以是傻瓜式的安装就行&#xff0c;GE的驱动里面有exe&#xff0c;直接点击安装即可&#xff0c;完了记得到安装路径把“.h”“.liib”和“.dll…...

网站编辑转行做文案/软件培训

前言 很多朋友问我的博客是什么软件写的&#xff0c;嘻嘻&#xff0c;其潜台词是我的博客页面还挺好看的呢&#xff01;&#xff01;&#xff01;心里美滋滋的&#xff0c;这里就把方法共享出来吧 定制样式而已 我的博客主要是对H1的样式做了一点改变&#xff0c;给H1加上了背景…...

中企动力科技股份有限公司销售/互联网优化

1. Class类的使用 1.1 class的获取有三种方式 F fnew F();//方法一 任何一个类都有一个隐含的静态成员变量classClass c1F.class;//方法二 已经知道该类的对象通过getClass方法Class c2f.getClass();System.out.println(c1c2); //true//方法三 Class.forName()方…...

wordpress 幻灯片/新浪博客

我们大家都知道,JS有个很经典的浮点运算精度丢失问题,今天我们就来聊一聊这个问题产生的原因,以及该如何去解决它呢? 先来看下面的代码,0.10.2的结果不等于0.3,这是不是超出了我们之前的认知呢?毕竟0.10.20.3可是我们小学就已经学会了的东西,到这里怎么就不一样了呢? 0.1 …...

电子政务门户网站建设的教训/怎么注册一个自己的网址

paip. 混合编程的实现resin4 (自带Quercus &#xff09; 配置 php 环境#---混合编程的类型1.代码inline 方式2.使用库/api 解析方式.#----配置resin 支持phpresin4默认自动支持php..也能手动配置了.web.xml加php的servlet解析..参考Quercus让你的PHP开心在Servlet容器奔跑#---…...

怎么查有做网站的公司有哪些/seo 知乎

zebra http://www.zebra.org/ quagga http://www.quagga.net/...

丽水网站开发/廊坊关键词排名优化

B&#xff0e;在打印预览状态下单击“打印”命令C&#xff0e;将插入点置于该页&#xff0c;单击“文件”菜单中“打印”命令&#xff0c;在“页面范围”中选“当前页”D&#xff0e;在工具栏中单击“打印”命令E&#xff0e;单击“文件”菜单中“打印”命令&#xff0c;在“页…...