Recently I made a requirement involving WebSocket, which I thought was a very elegant thing at first, but I found it was very easy to handle. Again, a lot of things don’t be afraid, it’s actually not that scary.

What is the WebSocket

Websocket is a protocol for full duplex communication over a single TCP connection. Websocket makes it easier to exchange data between the client and the server, allowing the server to actively push data to the client.

In the WebSocket API, the browser and the server only need to complete a handshake, the two can directly create a persistent connection, and two-way transmission.

The biggest feature of Websocket is that in addition to the client can actively send information to the server, the server page can actively push information to the client, which is a real two-way traffic and belongs to a server push technology.

The protocol identifier is WS (if encrypted, WSS), and the server URL is the URL. The console can view the address as shown in the figure below:

                                                  

WebSocket application scenarios

  • Internet phone
  • Instant messaging
  • Multiplayer games
  • Online collaborative document editing
  • Sports/games live
  • Real-time map location

Take the Internet phone as an example, the client needs to know the receiving status and hanging status of the other party after the broadcast, and also obtain the incoming call information of the other party when the client does not operate. In this case, the HTTP protocol cannot meet the requirements of the server to push to the client, so the Websocket protocol is suitable.

To encapsulate the 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
Copy the code

WebSocket is used in conjunction with the State Manager (Vuex)

header.vue

<div> <div class="header"> <span @click="handlerClick" class="statusFlag">{{ statusFlag ? <span style="margin-left: 0"> <span :class="statusFlag? 'online' : 'outline' ">● {{statusFlag? }}</span > </div> </div> </template> <script> export default {name: "Header", data() {return {status: "Offline ", 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(" login succeeded "); }); }, 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 {// Exit now ready to login this.calllogin (); this.statusFlag = ! this.statusFlag; }},},},}; </script> <style lang="scss" scoped> </style>Copy the code

floatBtn.vue

<! - / / 0: free / / 1: pick machine / / 2: dial in / / 3:4: call ringing - > < div class = "info" v - if = "statusList. Length? statusList[channel].phonestate == 0 : False "> < img SRC =" @ / assets/images/call - 1. PNG "Alt =" idle "/ > < p > free < / p > < / div > < div class =" info "v - else - if =" statusList.length ? statusList[channel].phonestate == 1 : False "> < img SRC =" @ / assets/images/call - 2. PNG "Alt =" off-hook "/ > < p > off-hook < / p > < / div > < div class =" info "v - else - if =" statusList.length ? StatusList [channel]. Phonestate == 2: false "> <img SRC ="@/assets/image/call-3.png" Alt =" Dialing" /> <p> Calling... </p> </div> <div class="info" v-else-if=" statusList.length ? statusList[channel].phonestate == 3 : False "> < img SRC =" @ / assets/images/call - 4. PNG "Alt =" call "/ > < p > {{one}}, {{two}}, {{three}} < / p > < / div > < div class="info" v-else-if=" statusList.length ? statusList[channel].phonestate == 4 : False "> < img SRC =" @ / assets/images/call - 5. PNG "Alt =" inbound "/ > < p > inbound < / p > < / div > < div class =" info "v - else > < p > please wait a moment < / 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 >,"}, () = > {the console. The log (" open seats channels: ", enclosing $store. State. The socket. The 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. }). Then (() => {this.$message({message: "Hang up ",}); 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>Copy the code

sokect.js

const sokect = { state: { ws: Websocket instance callLogin: ",// whether to log in to the agent channel: ",// channel number channelStatusList: ",// status of all channels}, 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 sokectCopy the code

getters.js

  ws:state => state.sokect.ws,
  callLogin:state => state.sokect.callLogin,
  channelStatusList:state => state.sokect.channelStatusList
}
export default getters

Copy the code

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

Copy the code