技术标签: SpringSecurity STOMP SpringBoot WebSocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
WebSocket是一个消息架构,不强制使用任何特定的消息协议,它依赖于应用层解释消息的含义;
与处在应用层的HTTP不同,WebSocket处在TCP上非常薄的一层,会将字节流转换为文本/二进制消息,因此,对于实际应用来说,WebSocket的通信形式层级过低,因此,可以在 WebSocket 之上使用STOMP协议,来为浏览器和server间的通信增加适当的消息语义。
1、直接使用WebSocket(SockJS)就很类似于使用TCP套接字
来编写web应用,因为没有高层协议,就需要我们定义应用间所发送消息的语义,还需要确保连接的两端都能遵循这些语义。
2、同HTTP在TCP套接字
上添加请求-响应模型层
一样,STOMP在WebSocket之上提供了一个基于帧的线路格式层
,用来定义消息语义。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 前端库 -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>sockjs-client</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>stomp-websocket</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.3.1</version>
</dependency>
也可以直接前端页面引入相关JS文件
jquery的js
sockjs-client的js
stomp-websocket的js
Spring提供了基于WebSocket
的STOMP
支持,STOMP是一个简单的可互操作的协议,通常用于中间服务器与客户端之间进行异步消息传递
。
@Configuration
@EnableWebSocketMessageBroker // 开启WebSocket消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
/**
* 设置消息代理的前缀 - '/topic'
* 被设置的前缀的消息会被转发到消息代理
* 消息代理再将消息广播给当前连接的客户端 - 群发
*/
registry.enableSimpleBroker("/topic");
/**
* 配置目标前缀,这里只配置一个,即/app
* 配置了的前缀为/app可以通过@MessageMapping注解的方法处理
* 其他的destination如/topic、/queue将被直接交给broker处理
*/
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
/**
* 设置前缀为/chat,可以通过这个/chat建立连接
* withSockJs支持解决WbeSocket兼容问题
*/
registry.addEndpoint("/chat").withSockJS();
}
}
@Data
public class Message {
/** 昵称 */
private String name;
/** 消息内容 */
private String content;
}
这里的Controller
主要是用来处理消息的,前面配置文件中,我们配置了/app
目标前缀,前缀为/app
的会进入到我们下面所说的Controller
层里标注@MessageMapping
的方法里被处理。
@Controller
public class TestController {
@MessageMapping("/hello") // 接收/app/hello路径发来的消息
@SendTo("/topic/greetings") // 转发到/topic/greetings
public Message greeting(Message message){
return message;
}
}
除了@SendTo
注解可以将处理过的消息转发到broker,再由broker进行消息广播外。Spring提供了一个SimpMessagingTemplate
类来让开发者更加灵活地发送消消息。
@Autowired
private SimpMessagingTemplate template;
@MessageMapping("/hello") // 接收/app/hello路径发来的消息
public void greeting(Message message){
template.convertAndSend("/topic/greetings",message);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket群聊</title>
</head>
<body>
<!-- 用户名区域 -->
<div>
<label for="name">请输入用户名:</label>
<input type="text" id="name" placeholder="用户名">
</div>
<!-- 连接区域 -->
<div>
<button id="connect" type="button">连接</button>
<button id="disconnect" type="button" disabled="disabled">断开连接</button>
</div>
<!-- 发送消息区域 -->
<div id="chat" style="display:none">
<div>
<label for="content">请输入聊天内容:</label>
<input type="text" id="content" placeholder="聊天内容">
</div>
<button id="send" type="button">发送</button>
</div>
<!-- 聊天区域 -->
<div id="greetings">
<div id="conversation" style="display: none">群聊中...</div>
</div>
<!-- JS引用区域 -->
<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
<!-- 自定义JS -->
<script src="/js/app.js"></script>
</body>
</html>
// STOMP客户端
var stompClient = null;
/**
* 是否已经连接,对页面显示进行处理
* @param connected 是否已经连接
*/
function setConnected(connected) {
$("#connect").prop("disabled",connected);
$("#disconnect").prop("disabled",!connected);
if(connected){
$("#conversation").show();
$("#chat").show();
}else{
$("#conversation").hide();
$("#chat").hide();
}
$("#greetings").html("");
}
/**
* 建立WebSocket连接
*/
function connect() {
// 如果名字为空,则不让连接
if(!$("#name").val()){
return;
}
// 通过SockJs建立连接对象
var socket = new SockJS('/chat');
// 也可以通过WebSocket建立连接
// var socket = new WebSocket("/chat");
// 获取STOMP子协议的客户端对象
stompClient = Stomp.over(socket);
// 向服务器发起websocket连接并发送CONNECT镇
stompClient.connect({
},function (frame) {
// 表示连接成功,
setConnected(true);
// 订阅服务端发送的消息
stompClient.subscribe('/topic/greetings',function (greeting) {
// 显示消息
showGreeting(JSON.parse(greeting.body));
});
});
}
/**
* 断开连接
*/
function disconnect() {
if (stompClient != null){
stompClient.disconnect();
}
setConnected(false);
}
/**
* 发送消息
*/
function sendMessage() {
// 发送消息
stompClient.send("/app/hello",{
},
JSON.stringify({
'name':$("#name").val(),'content':$("#content").val()}));
}
/**
* 控制显示消息
* @param message 消息对象
*/
function showGreeting(message) {
$("#greetings").append("<div>" + message.name + ":" + message.content + "</div>");
}
$(function () {
$("#connect").click(function () {
connect();
});
$("#disconnect").click(function () {
disconnect();
});
$("#send").click(function () {
sendMessage();
// 清空聊天框
$("#content").val("");
})
});
运行项目,用两个不同的浏览器打开,分别发送消息。
对app.js
中的STOMP的API解释。
/**
* 通过SockJS建立WebSocket连接对象
* 参数就是我们配置文件中registerStompEndpoints方法里配置的前缀
*/
var socket = new SockJS('/chat');
// 同样,可以用下面这行代码代替,但是就没有SockJS提供的兼容支持
var socket = new WebSocket("/chat");
// 获取STOMP子协议的客户端对象
var stompClient = Stomp.over(socket);
/**
* headers:客户端的认证信息
* connectCallback:连接成功时(服务器响应 CONNECTED 帧)的回调方法
* errorCallback:连接失败时(服务器响应 CONNECTED 帧)的回调方法
* 如果不需要认证,使用{}替代即可
* 失败回调方法可以省略
*/
stompClient.connect(headers, connectCallback, errorCallback);
其中headers
认证信息大概长这个样子:
var headers = {
login: 'mylogin',
passcode: 'mypasscode',
// additional header
'client-id': 'my-client-id'
};
断开连接是异步操作的。
/**
* disconnectCallback:回调方法
* 回调方法在操作完成时调用,可以省略
*/
stompClient.disconnect(disconnectCallback);
STOMP 1.1 版本,默认开启了心跳检测机制,可通过client对象的heartbeat进行配置,默认是10000ms
stompClient.heartbeat.outgoing=20000;
stompClient.heartbeat.incoming=0;
/**
* destinationUrl:服务端Controller中@MessageMapping中匹配的URL
* headers:发送信息的header,JavaScript对象,可选参数,省略可用{}代替
* body:发送信息的body,字符串,可选参数
*/
stompClient.send(destinationUrl, headers, body);
body消息可以使用JSON数据,使用JSON.stringify转换即可
stompClient.send("/app/hello",{
},
JSON.stringify({
'name':$("#name").val(),'content':$("#content").val()}));
STOMP 客户端支持在发送消息时用事务进行处理
// 该方法返回一个包含了事务 id、commit()、abort()的JavaScript 对象
var tx = stompClient.begin();
// 在headers对象中加入事务id,若没有添加,则会直接发送消息,不会以事务进行处理
stompClient.send("/app/hello",{
transaction: tx.id},
JSON.stringify({
'name':$("#name").val(),'content':$("#content").val()}));
// 提交事务
tx.commit();
/**
* destinationUrl:服务端@SendTo匹配的URL
* callback:为每次收到服务器推送的消息时的回调方法,该方法包含参数message(即收到的消息)
* headers:附加的headers,JavaScript对象,可选参数
* headers方法返回一个包含了id属性的JavaScript对象,可作为unsubscribe()方法的参数
*/
var subscription = varstompClient.subscribe(destinationUrl, callback, headers);
subscription.unsubscribe();
了解更多,可以百度,或是参考STOMP 客户端 API 整理
为了更能表现出点对点的用户概念,所以这里引入SpringSecurity
,然后通过内存里的账号登录。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("lcy")
.password("$2a$10$XcigeMfToGQ2bqRToFtUi.sG1V.HhrJV6RBjji1yncXReSNNIPl1K") // 123
.roles("admin")
.and()
.withUser("jyqc")
.password("$2a$10$XcigeMfToGQ2bqRToFtUi.sG1V.HhrJV6RBjji1yncXReSNNIPl1K")
.roles("user")
.and()
.withUser("xbyx")
.password("$2a$10$XcigeMfToGQ2bqRToFtUi.sG1V.HhrJV6RBjji1yncXReSNNIPl1K")
.roles("admin");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated() // 登录就可以访问
.and()
.formLogin().permitAll(); // 登录相关url都可以访问
}
}
@Configuration
@EnableWebSocketMessageBroker // 开启WebSocket消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic","/queue");
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat").withSockJS();
}
}
新增如下代码
@Autowired
private SimpMessagingTemplate template;
@MessageMapping("/chat")
public void chat(Principal principal, Chat chat){
String from = principal.getName();
chat.setFrom(from);
// convertAndSendToUser内部会做处理
// 发送的最终路径是/user/用户名/queue/chat
template.convertAndSendToUser(chat.getTo(),"/queue/chat",chat);
}
convertAndSendToUser
源码查看,可以发现里面对url
进行修改,this.destinationPrefix
默认是/user
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket单聊</title>
</head>
<body>
<div id="chat">
<div id="chatsContent">
</div>
<div>
聊天内容:
<input type="text" id="content" placeholder="请输入聊天内容"><br>
目标用户:
<input type="text" id="to" placeholder="请输入目标用户"><br>
<button id="send" type="button">发送</button>
</div>
</div>
<!-- JS引用区域 -->
<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
<script src="/js/chat.js"></script>
</body>
</html>
// STOMP客户端
var stompClient = null;
function connect() {
var socket = new SockJS("/chat");
stompClient = Stomp.over(socket);
stompClient.connect({
},function (frame) {
// 因为后端的convertAndSendToUser处理了url,所以也就是为什么相比之前的群里多了个/user的原因
stompClient.subscribe('/user/queue/chat',function (chat) {
showGreeting(JSON.parse(chat.body));
});
});
}
function sendMsg() {
stompClient.send("/app/chat",{
},JSON.stringify({
'content':$("#content").val(),'to':$("#to").val()}));
}
function showGreeting(message) {
$("#chatsContent")
.append("<div>" + message.from + ":" + message.content + "</div>");
}
$(function () {
connect();
$("#send").click(function () {
sendMsg();
$("#content").val("");
})
});
开启三个不同的浏览器测试,点对点模式没有问题。
引言 最近在面试中,除了基础 & 算法 & 项目之外,经常被问到或被要求介绍和描述下自己所知道的几种分类或聚类算法(当然,这完全不代表你将来的面试中会遇到此类问题,只是因为我的简历上写了句:熟悉常见的聚类 & 分类算法而已),而我向来恨对一个东西只知其皮毛而不得深入,故写一个有关数据挖掘十大算法的系列文章以作为自己备试之用,甚至以备将来常常回顾思考。行文杂乱,但侥幸若能对读者起到一
关于Keil debug 出现cannot access target shutting down debug session 错误提示
在学习ROS Tutorials时,$ roscd beginner_tutorials出现错误,roscd: No such package/stack 'beginner_tutorials'解决方案有二:第一种方法:输入beginner_tutorials的绝对路径,在我的电脑中如下:cd ~/catkin_ws/src/beginner_tutorials第二
简介:IDLE是Python软件包自带的一个集成开发环境,可以方便地创建、运行、调试Python程序。本文包括IDEL安装、使用配置、和运行调试教程。一、IDLE安装IDLE是Python(版本2.x或3.x)安装过程的用户自定义安装选项,IDLE是跟Python一起安装的,不过要确保安装时选中了“tcl/tk”组件,准确地说,应该是不要取消该组件,因为默认该组件是处于选中状态的。安装完成后需根据...
中职计算机应用基础大纲 (4页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦!9.9 积分《计算机应用基础》教学方法的探讨微博 字号 电邮 收藏 打印 · 大 · 中 · 小 相关文章:· 思想政治理论课教学中讲授教学与案例... · 课堂教学三阶段之教学反思研究 · 高校教学方法研究述评 · 教学学术:高校提升教学质量的突破口 · 贯通学习理论和教学实...
随着现代科技和计算机技术的不断发展,人们在与机器的信息交流中,需要一种更加方便、自然的交互方式,实现人机之间的语音交互,让机器听懂人话是人们梦寐以求的事情。语音识别技术的发展,使得这一理想得以实现,把语音识别技术与机器人控制技术相结合,正成为目前研究的热点,不但具有较好的理论意义,而且有较大的实用价值。 语音识别技术应用于机器人系统大多是针对特定的环境,设计出语音命令来进行控制的。只需要
Smarty模板技术之foreach遍历数组实例<?php$arr = array(600, 851, 7412);$smarty->assign('testarrg', $arr);?>用Smarty中的foreach方法来遍历并输出这个数组<dl><dt>foreach中item属性用法</dt>{foreach from=$testarrg item=test}<dd>{$test}</dd>{/f
Datawhale干货作者:太子长琴,算法工程师,Datawhale成员文本分类是自然语言处理(NLP)最基础核心的任务,或者换句话说,几乎所有NLP任务都是「分类」任务,或者涉及到「...
这样在adpater里面设置是有效果的,设置子布局中TextView文本框的字体颜色:
李:俞老师,您好!很高兴今天能采访您。我们都知道词汇是语言的基础,在外语学习和外语考试中,词汇也是重要的突破口,尤其是在GRE考试中,词汇是重头戏。所以外语学习者非常关心单词记忆法的问题。英语单词记忆方法多种多样,您认为在运用这些方法时要注意什么?俞:对一般的英语学习者来说,如果希望达到比较高的英语水平,就不仅仅是一个背单词的问题,还涉及到听、说和阅读的能力。词汇量的多少对阅读能力有影响,但它只...
1.功能描述:该请求用于识别一张图片,即对于输入的一张图片(可正常解码,且长宽比较合适),输出植物识别结果。近期植物识别进行了能力升级——模型升级,Top1准确率绝对值提升11.76%,精度保持业界领先!具体如下图所示:2.平台接入植物识别接入网址:https://console.bce.baidu.com/ai/?fromai=1#/ai/imagerecognition/ove...
先推两个将monitor命令比较好的网址:http://www.ibm.com/developerworks/cn/linux/l-cn-qemu-monitor/http://smilejay.com/2012/12/virsh-use-qemu-monitor-command/维基下面的qemu进不去 不知道是不是被和谐了 下面推一个类似的https://lxr.mis