从0开始在vue中使用WebSocket,原来WebSocket如此简单
最近做的一个需求涉及到WebSocket,开始以为是个很高大上的东西,做下来才发现很好拿捏。再次证明很多东西不要怕,实际上没有那么唬人。
WebSocket是什么
websocket是一种在单个TCP连接上进行全双工通讯的协议。websocket使得客户端和服务器之间的数据交换变得更加简单,允许服务器主动向客户端推送数据。
在websocket API中,浏览器和服务器只需要完成一次握手,两者间就直接可以创建持续性的连接,并进行双向传输。
websocket最大的特点是除了客户端可以主动向服务器发送信息外,服务器页可以主动向客户端推送信息,是真正的双向通行,属于服务器推送技术的一种。
协议标识符是ws
(如果加密,则为wss
),服务器网址就是 URL,控制台查看地址如图:
WebSocket应用场景
- 网络电话
- 即时聊天
- 多人游戏
- 在线协同编辑文档
- 体育/游戏实况
- 实时地图位置
以网络电话为例,客户端需要知道播出后对方的接听状态、挂断状态,还要获取客户端没操作时对方的来电信息,这种情况下http协议无法满足由服务器向客户端推送,所以适合用websocket协议。
封装WebSocket
wobsocket.js
/*
* socket长连接和公共管理方案
* websocket和 VueX 还有 Redux建立起长连接机制 ,防止在路由切换时候的时候断开连接,需要把socket实例放入公共管理统一处理
* 此方案暴露出一个触发器 $soctket_emit api,方便任何组件调用 , 内部有一个订阅器$soctket_subscribe api ,与 VueX 中 mutations 和 Redux 中 reducers实时连接改变数据
*/
let socketUrl = ''
/**
* @param value
* @returns {string} 强数据类型校验
*/
function isType (value) {
return Object.prototype.toString.call(value).slice(8, -1)
}
/**
* @param event 当前事件
* 事件轮询器
*/
function eventPoll (event, outerConditon, time, callback) {
let timer
let currentCondition
timer = setInterval(() => {
if (currentCondition === outerConditon) {
clearInterval(timer)
callback && callback()
}
currentCondition = event()
}, time)
}
function isSocketContent () {
// 你的websocket url地址
socketUrl = 'ws://xxx:xxxx/'
// }
}
/**
* @constructor 构造函数
* commit 公共管理触发器
* action 处理返回订阅器返回数据
*/
function socket (commit, actions) {
if (isType(commit) !== 'Function') {
throw new Error('commit must be a function')
}
this.commit = commit
this.actions = actions || null
this.timer = null
this.errorResetNumber = 0 // 错误重连间隔
this.closeWs = false
this.errorFrom = 0 // socket断开来源
this.errorResetTimer = null // 错误重连轮询
this.errorDispatchOpen = true // 开启错误调度
this.heartSocketOpen = false
isSocketContent()
this.$soctket_init()
}
/**
* websocket ->初始化
* @param callback 初始化失败回调
* @param value 数据处理
*/
socket.prototype.$soctket_init = function (callback) {
const _this = this
if (_this.closeWs) {
throw new Error('socket is closed ,$socker_init is fail , all methods is invalid')
}
// const token = window.localStorage.getItem('token') || window.sessionStorage.getItem('token') || null
// if (!token) {
// throw new Error('token is underfined')
// }
const handerErrorMachine = () => {
if (_this.errorResetNumber === 4) {
_this.errorResetNumber = 0
_this.errorResetTimer = null
_this.errorFrom = 0
_this.errorDispatchOpen = false
_this.ws = null
console.log('socket连接失败')
return
}
_this.errorResetTimer = setTimeout(() => {
_this.$soctket_init()
_this.errorResetNumber++
}, _this.errorResetNumber * 2000)
}
const errorDispatch = (eventment) => {
let event = eventment
return function () {
if (_this.errorFrom === 0 && _this.errorDispatchOpen) {
_this.errorFrom = event
}
event === 1 ? console.log('web socket has failed from closeState ') : console.log('web socket has failed from errorState ')
if (_this.errorFrom === event && !_this.closeWs) {
_this.errorResetTimer && clearTimeout(_this.errorResetTimer)
handerErrorMachine()
}
}
}
if (this.timer) clearTimeout(this.timer)
_this.ws = new WebSocket(socketUrl)
_this.ws.onopen = function () {
callback && callback()
_this.errorResetNumber = 0
_this.errorResetTimer = null
_this.errorFrom = 0
_this.errorDispatchOpen = true
_this.$soctket_subscribe()
_this.$soctket_heartSoctket()
console.log('web socket has connected ')
}
_this.ws.onclose = errorDispatch(1)
_this.ws.onerror = errorDispatch(2)
}
/**
* 触发器->发布信息
* @param callback 状态处理
* @param value 数据处理
*/
socket.prototype.$soctket_emit = function (value, callback) {
const _this = this
const poll = function () {
return _this.ws.readyState
}
if (callback && isType(callback) !== 'Function') {
throw new Error('$socket_emit arugment[1] must be a function')
}
if (!_this.ws) {
throw new Error('$socket dispatch is fail please use $socket_open method')
}
console.log('look at this1,',_this.ws.readyState)
if (_this.ws.readyState == 1) { // 连接成功状态
console.log('look at this2,',value)
_this.ws.send(JSON.stringify(value))
_this.$soctket_heartSoctket()
callback && callback()
}
else if (_this.ws.readyState === 0) { // 连接中状态 ,轮询查询连接
console.log('look at this3,',value)
eventPoll(poll, 1, 500, () => {
_this.ws.send(JSON.stringify(value))
_this.$soctket_heartSoctket()
callback && callback()
})
}
else { // 失败重新连接
_this.$soctket_init(() => {
_this.$soctket_emit(value, callback)
})
}
}
/**
* 订阅器->接受广播
*/
socket.prototype.$soctket_subscribe = function () {
const _this = this
_this.ws.onmessage = function (res) {
if (_this.actions) {
if (isType(_this.actions) !== 'Function') {
throw new Error('actions')
} else {
_this.commit(..._this.actions(res.data))
}
} else {
_this.commit(JSON.parse(res.data).cmd,JSON.parse(res.data))
}
_this.$soctket_heartSoctket()
}
}
/**
* 心脏搏动机制->防止断开连接
*/
socket.prototype.$soctket_heartSoctket = function () {
if (this.timer) clearTimeout(this.timer)
console.log(this.timer)
this.timer = setTimeout(() => {
if (this.ws.readyState === 1 || this.ws.readyState === 0) {
this.ws.send(JSON.stringify({"cmd":"keepalive","timestamp":"<timestamp>"}))
this.$soctket_heartSoctket()
} else {
this.$soctket_init()
}
}, 59000)
}
/**
* 关闭socket连接
*/
socket.prototype.$soctket_close = function () {
if (this.timer) clearTimeout(this.timer)
if (this.errorResetTimer)clearTimeout(this.errorResetTimer)
this.closeWs = true
console.log('this is closing')
// this.ws.send(JSON.stringify(value))
this.ws.close()
}
/**
* 重启socket连接
*/
socket.prototype.$soctket_open = function () {
if (!this.closeWs) {
throw new Error('socket is connected')
}
this.timer = null
this.errorResetNumber = 0
this.closeWs = false
this.errorFrom = 0
this.errorResetTimer = null
this.errorDispatchOpen = true
this.heartSocketOpen = false
this.closeWs = false
this.$soctket_init()
}
export default socket
WebSocket结合状态管理器(Vuex)使用
header.vue
<div>
<div class="header">
<span @click="handlerClick" class="statusFlag">{{
statusFlag ? "【退出】" : "【登录】"
}}</span>
<span style="margin-left: 0">呼叫系统|</span
><span :class="statusFlag ? `online` : `outline`"
>● {{ statusFlag ? "在线" : "离线" }}</span
>
</div>
</div>
</template>
<script>
export default {
name: "Header",
data() {
return {
status: "离线",
statusFlag: false,
};
},
created() {
this.$store.dispatch("socketInit");
},
mounted() {
},
methods: {
callLogin(callback) {
const { ws } = this.$store.state.socket;
ws.$soctket_emit(
{
cmd: "seatlogin",
seatname: "0",
seatnum: "",
password: "8888",
timestamp: "<timestamp>",
},
() => {
console.log("登录成功");
}
);
},
callLogout() {
const { ws } = this.$store.state.socket;
const param = {
cmd: "seatlogout",
seatname: "0",
seatnum: "",
timestamp: "<timestamp>",
};
ws.$soctket_emit({
cmd: "seatlogout",
seatname: "0",
seatnum: "",
timestamp: "<timestamp>",
});
},
handlerClick() {
if (this.statusFlag) {
this.callLogout();
this.statusFlag = !this.statusFlag;
} else {
//当前退出 准备登入
this.callLogin();
this.statusFlag = !this.statusFlag;
}
},
},
},
};
</script>
<style lang="scss" scoped>
</style>
floatBtn.vue
<!-- // 0:空闲
// 1:摘机
// 2:拨号中
// 3:通话 4:振铃 -->
<div
class="info"
v-if="statusList.length ? statusList[channel].phonestate == 0 : false"
>
<img src="@/assets/image/call-1.png" alt="空闲" />
<p>空闲</p>
</div>
<div
class="info"
v-else-if="
statusList.length ? statusList[channel].phonestate == 1 : false
"
>
<img src="@/assets/image/call-2.png" alt="摘机" />
<p>摘机</p>
</div>
<div
class="info"
v-else-if="
statusList.length ? statusList[channel].phonestate == 2 : false
"
>
<img src="@/assets/image/call-3.png" alt="拨号中" />
<p>呼叫中...</p>
</div>
<div
class="info"
v-else-if="
statusList.length ? statusList[channel].phonestate == 3 : false
"
>
<img src="@/assets/image/call-4.png" alt="通话" />
<p>{{ one }}:{{ two }}:{{ three }}</p>
</div>
<div
class="info"
v-else-if="
statusList.length ? statusList[channel].phonestate == 4 : false
"
>
<img src="@/assets/image/call-5.png" alt="呼入" />
<p>呼入</p>
</div>
<div class="info" v-else>
<p>请稍等</p>
</div>
</template>
<script>
import { mapGetters } from "vuex";
import callInfo from "./callInfo.vue";
export default {
name: "floating-window",
components: {
callInfo,
},
computed: {
...mapGetters(["channelStatusList"]),
channelStatusList() {
return this.$store.state.socket.channelStatusList;
},
},
props: {
form: Object,
},
data() {
return {
statusList: [],
flag: null,
channel: "",
phonestate: "",
};
},
mounted() {
const { ws, channel } = this.$store.state.socket;
this.channel = channel;
ws.$soctket_emit(
{
cmd: "channelstatusopen",
seatname: "0",
seatnum: "",
timestamp: "<timestamp>",
},
() => {
console.log("打开坐席通道:", this.$store.state.socket.channel);
}
);
},
watch: {
channelStatusList(item1, item2) {
this.statusList = item1;
if (this.statusList[this.channel].phonestate == 0) {
this.end()
}
if (this.statusList[this.channel].phonestate == 3) {
this.timer();
}
if (this.statusList[this.channel].phonestate == 4) {
this.openCallInfo();
}
},
},
methods: {
handleClickMenuAction() {
this.$confirm("确定要挂断吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(() => {
this.$message({
message: "已挂断",
});
this.closed = false;
this.$emit("close-call", closed);
});
return;
},
openCallInfo() {
this.$emit("openCallInfo", this.statusList[this.channel].phonestate == 4,this.statusList[this.channel].dtmfa);
},
},
};
</script>
sokect.js
const sokect = {
state: {
ws: null,//websocket实例
callLogin:'',//是否登录坐席
channel:'',//通道号
channelStatusList:'',//所有通道的状态
},
mutations: {
contentSocket(state, { commit }) {
state.ws = new Socket(commit)
},
seatlogin(state,data){
state.callLogin = data.result
state.channel = data.channel
},
channelstatus(state,result){
state.channelStatusList = result.channelStatusList
console.log('channelstatus',state.channelStatusList)
},
seatlogout(state,data){
data.result == 'success' && (state.callLogin = 'closed')
}
},
actions: {
socketInit({ commit, state }) {
commit('contentSocket', { commit })
},
// seatlogin({ commit, state }) {
// commit('seatlogin', { commit })
// }
}
}
export default sokect
getters.js
ws:state => state.sokect.ws,
callLogin:state => state.sokect.callLogin,
channelStatusList:state => state.sokect.channelStatusList
}
export default getters
index.js
import Vuex from 'vuex'
import app from './modules/app'
import socket from './modules/sokect'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
socket,
},
getters
})
export default store
转载自:https://juejin.cn/post/7046266099473580045