vue项目的搭建在这里就不说了,网上有很多教程。大家自行百度。前端使用stomp+websocket有很多的实现版本。这里只讲@stomp/stompjs方式实现。
导入
使用@stomp/stompjs需要执行安装命令:
import Stomp from '@stomp/stompjs';
然后在组件中引入即可,这里需要注意的是,可能由于版本不一致,会导致引入的方式不一致。但是主要是以下两种方式中的一种
import { Client, Stomp} from '@stomp/stompjs';
或者
import Stomp from '@stomp/stompjs';
连接服务器
连接服务器需要用到connect()方法。该方法有一个基本的成功时的回调函数
var connect_success_callback = function() {
// called back after the client is connected and
authenticated to the STOMP server
};
connect()
方法接受一个可选的参数(error_callback
),当客户端不能连接上服务端时,这个回调函数error_callback
会被调用,该函数的参数为对应的错误对象。
var error_callback = function(error) {
// display the error's message header:
alert(error.headers.message);
};
// 创建Stomp客户端实例
let stompClient = Stomp.client('ws://localhost:7000/websocket-demo/stmpwebsocket');
// 连接WebSocket
stompClient.connect(frame => {
console.log('Connected: ' + frame);
//这里表示已经连接成功
}, error => {
console.error('STOMP error:', error);
});
connect()方法可以传入两个参数,用来做用户认证:用户的登录和密码凭证。在大多数情况下,connect()
方法可接受不同数量的参数来提供简单的API:
stompClient.connect(login, passcode, connectCallback);
stompClient.connect(login, passcode, connectCallback, errorCallback);
stompClient.connect(login, passcode, connectCallback, errorCallback, host);
login
和passcode
是strings,connectCallback
和errorCallback
则是functions。(有些brokers(代理)还需要传递一个host
(String类型)参数。)
有时候我们的连接没有专门的用户名和密码,都是在原有的已经认证的基础上进行连接,例如token,所以我们需要将token传给后端:这样我们需要一个http类似的请求头实现方式。stomp也支持hear的参数
stompClient.connect(headers, connectCallback);
stompClient.connect(headers, connectCallback, errorCallback);
header
是map
形式,connectCallback
和errorCallback
为functions。
需要注意:如果你使用上述这种方式,那就需要自行在headers
添加login
、passcode
(甚至host
):
var headers = {
login: 'username',
passcode: 'mypasscode',
token: 'my-client-id'
};
stompClient.connect(headers, connectCallback);
断开连接
断开连接使用的时disconnect()方法,该方法接受一个回调函数
stompClient.disconnect(function() {
alert("See you next time!");
};
心跳保活
如果STOMP broker(代理)接收STOMP 1.1版本的帧,heart-beating
是默认启用的。
heart-beating也就是频率,incoming是接收频率,outgoing是发送频率。通过改变incoming和outgoing可以更改客户端的heart-beating(默认为10000ms)。heart-beating是利用window.setInterval()去规律地发送heart-beats或者检查服务端的heart-beats。
stompClient.heartbeat.outgoing = 20000;
// client will send heartbeats every 20000ms
stompClient.heartbeat.incoming = 0;
// client does not want to receive heartbeats
// from the server
消息发送
消息发送使用send()方法。这个方法必须有一个参数,用来描述对应的STOMP的目的地。另外可以有两个可选的参数:headers
,object
类型包含额外的信息头部;body
,一个String类型的参数。
stompClient.send(describetion, headers,messageBody)
消息订阅和接收
stomp的数据订阅和接收是同时的,订阅方法需要传入一个数据处理回调函数,函数有且只有一个参数,参数中包含了服务器发送的数据。且该方法会返回一个Promise对象,用于取消数据订阅。
//获取订阅结果用于取消订阅
let pushSubscriptionPromise = stompClient.subscribe('subscribePath', message => {
// 处理接收到的消息
console.log('收到消息', message.body);
});
取消订阅
数据的取消订阅使用订阅方法返回的对象调用unSubscribe()即可
pushUserSubscriptionPromise.unsubscribe()
消息确认ack
在收到服务端的数据之后,我们可能需要给后端一个ack'消息,用于告诉后端我们已经收到了消息,这个机制在传输一些很重要的信息是有必要的。
//获取订阅结果用于取消订阅
let pushSubscriptionPromise = stompClient.subscribe('subscribePath', message => {
// 处理接收到的消息
console.log('收到消息', message.body);
/ 发送ACK确认消息
stompClient.ack(message);
});
stompClient.ack(message)方法用于发送一个ACK确认,表示我们已经收到并处理了ID为message.headers['message-id']的消息。如果你需要显式指定消息ID,可以这样做:
stompClient.ack(message.headers['message-id']);
以上就是使用@stomp/stompjs。
以下是一个完整的组件代码。可直接复制到一个已经搭建好的vue项目上测试。请忽略极其难看的ui页面。
<template>
<div class="f_c float_l">
<div class="m_10 float_l">
<el-input class='input_common' v-model="websocketPath" placeholder="后端websocket地址:http://ip:port/context/websocket-point" ></el-input>
<el-input class='input_common' v-model="userId" placeholder="请输入用户名" ></el-input>
<el-button v-if="!connected" @click=" connectWebsocket" >连接</el-button>
<el-button v-else @click="disConnectWebsocket" >断开</el-button>
</div>
<el-divider />
<div class="f_c" v-if="connected">
<div>
<el-input class='input_common' v-model="messageUrl" placeholder="请输入消息地址:/sendMessage"></el-input>
<el-button @click="sendMessage" v-if="connected">发送</el-button>
</div>
<el-input class='input_common mt_10' v-model="message" type="textarea" :rows="2" placeholder="请输入需要发送的消息"></el-input>
</div>
<el-divider />
<div class="f_c m_10 float_l" v-if="connected">
<div class="float_l">
<el-input class='input_common' v-model="subscribePath" placeholder="请输入监听地址:/subscribe/id"></el-input>
<el-button @click="subscribeTopic" v-if="!subscribed">广播订阅</el-button>
<el-button @click="unSubscribeTopic" v-else>取消订阅</el-button>
</div>
<div class="m_10 f_c">
<span class="float_l">收到消息</span>
<span style="border: aqua; width: 500px; height: 100px">{{receiveMessage}}</span>
</div>
</div>
<div class="f_c m_10 float_l" v-if="connected">
<div class="float_l">
<el-input class='input_common' v-model="userSubscribePath" placeholder="请输入监听地址:/subscribe/id"></el-input>
<el-button @click="subscribeUserTopic" v-if="!userSubscribed">用户订阅</el-button>
<el-button @click="unUserSubscribeTopic" v-else>取消订阅</el-button>
</div>
<div class="m_10 f_c">
<span class="float_l">收到消息</span>
<span style="border: aqua; width: 500px; height: 100px">{{receiveUserMessage}}</span>
</div>
</div>
</div>
</template>
<script>
import { Client, Stomp} from '@stomp/stompjs';
export default {
name: "index",
data(){
return {
websocketPath:'localhost:7000/websocket-demo/stmpwebsocket',
userId:'123456789',
subscribePath:'/topic/targetSubscribe',
receiveMessage:'',
userSubscribePath:'/user/queue/targetUser',
receiveUserMessage:'',
messageUrl:'/message/stomp/springUserMessage/987654321',
message:'',
connected: false,
subscribed: false,
userSubscribed:false,
stompClient:null,
pushSubscriptionPromise: null,
pushUserSubscriptionPromise: null
}
},
methods:{
connectWebsocket(){
if(!this.websocketPath){
this.$message.error("请先填写websocket地址信息")
return
}
if(!this.userId){
this.$message.error("请先填写用户信息")
return
}
// 创建Stomp客户端实例
this.stompClient = Stomp.client('ws://' + this.websocketPath);
//请求头
let headers = {
'userId': this.userId
}
// 连接WebSocket
this.stompClient.connect(headers, frame => {
console.log('Connected: ' + frame);
this.connected = true
}, error => {
console.error('STOMP error:', error);
});
},
sendMessage(){
if(!this.message || !this.messageUrl){
this.$message.error("请先填写websocket地址和消息内容信息")
return
}
let msg = { message: this.message };
console.log("数据发送:", this.messageUrl, msg)
this.stompClient.send(this.messageUrl, {'aaaaaa': 'aaaaaaaaa'}, JSON.stringify(msg));
},
onceSubscribe(){
this.stompClient.subscribe('/message/subscribe/1232131321', function(message) {
console.log('onceSubscribe-Message: ' + message.body);
})
},
subscribeTopic(){
if(!this.subscribePath){
this.$message.error("请先填写订阅地址")
return
}
// 订阅某个路径
let number = 1
this.subscribed = true
//获取订阅结果用于取消订阅
this.pushSubscriptionPromise = this.stompClient.subscribe(this.subscribePath, message => {
// 处理接收到的消息
console.log('收到消息', message);
let nowMessage = '第' + number + '次收到订阅的消息:' + JSON.stringify(JSON.parse(message.body)) + '\r\n';
this.receiveMessage += nowMessage;
number ++;
});
},
subscribeUserTopic(){
if(!this.userSubscribePath){
this.$message.error("请先填写用户的订阅地址")
return
}
// 订阅某个路径
let number = 1
this.userSubscribed = true
//获取订阅结果用于取消订阅
this.pushUserSubscriptionPromise = this.stompClient.subscribe(this.userSubscribePath, message => {
// 处理接收到的消息
console.log('收到消息', message);
let nowMessage = '第' + number + '次收到订阅的消息:' + JSON.stringify(JSON.parse(message.body)) + '\r\n';
this.receiveUserMessage += nowMessage;
number ++;
// 发送ACK确认消息
this. stompClient.ack(message);
});
},
unSubscribeTopic(){
// 订阅某个路径
this.pushSubscriptionPromise.unsubscribe();
this.subscribed = false;
this.receiveMessage = ''
},
unUserSubscribeTopic(){
// 订阅某个路径
this.pushUserSubscriptionPromise.unsubscribe();
this.userSubscribed = false;
this.receiveUserMessage = ''
},
disConnectWebsocket(){
if (this.stompClient != null) {
// 关闭连接
this.stompClient.disconnect()
this.stompClient = null
this.connected = false
this.subscribed = false
this.userSubscribed = false
this.receiveMessage = ''
this.receiveUserMessage = ''
}
},
beforeDestroy(){
if(this.stompClient != null){
this.stompClient.disconnect()
this.stompClient = null
this.connected = false
this.subscribed = false
this.userSubscribed = false
this.receiveMessage = ''
this.receiveUserMessage = ''
}
}
}
}
</script>
<style scoped>
</style>