h5和小程序socket長連接和公共管理方案(vue+redux+websocket)

我們都知道在vue和react這種單頁面組件化項目中,建立socket連接會遇到,重複連接,切換組件連接中斷等問題,而且如果想要在任何頁面接受到來自socket傳遞的信息,所以在建立socket連接時候就要考慮是否要把連接實例化放在公共state裏邊統一管理,這樣可以方便在任何組件中調用socket方法。這裏會介紹socket與Vuex和redux進行連接實時接受信息改變數據的方案。

websocket與公共管理邏輯圖

在這裏插入圖片描述

本方案的大體思路就是如上圖所示,現在頁面初始化的時候根據需要向vuex或者redux發起dispatch觸發初始化的方法,初始化的時候觸發websocket,js構造函數或者類的實例,並且要把改變公共狀態方法的commit作爲參數傳遞給socket實例 , 而真正建立起socket連接的方法實在webosocket實例中進行的,websocket實例會暴露出兩個方法,一個subscribe用來監聽服務端傳遞的信息來改變管理狀態,當然這裏的觸發是根據調用commit 函數來觸發的,另一個是又任意組件調用的emit方法 ,來把信息傳遞給服務端,從而實現了雙向通信,並把通信回執內容放在公共狀態管理,避免切換組件信息丟失,重新連接,丟失連接等情況發生。下面會拿vuex例子具體講一下流程

websocket與vue及vuex案例

在這裏插入圖片描述
以上就是文件的格式(這裏簡化了), websocket.js就是socket方法集裏邊又訂閱器,發佈器,失敗調度,心跳機制 , vuex下邊的socket,js就是一個vuex 在redux中就是一個reducer,
socket.vue就是要用到socket連接的組件,廢話不說,下面一一解釋

首先我們來看socket初始化

 if (!socket.ws) {
            //在socket.vue文件中初始化socket連接
     this.$store.dispatch('socketInit')
 }

這是隻是單獨觸發了一個dispatch 在調用了一個socketInit方法,然後我們來看vuex中socket.js中的socketInit方法

import Socket from '../websocket' //socket 方法類
import socketAction from '../../config/socket' //這個是對服務端的數據處理的中間件函數,這裏可以忽略

export default {
    state: {
        ws: null, // websorket實例
    
    },
    mutations: {
        subscribe_socket (state,{data}){
            //這裏的data爲socket連接後端返回來的數據
        },
        contentSocket (state, { commit }) {
            state.ws = new Socket(commit, socketAction)
        }
    },
    actions: {
        // 創建實例
        socketInit ({commit, state}) {
            commit('contentSocket', { commit }) //把commit作爲參數
        }
    }
}

在vuex的異步函數actions調用了初始化的方法,然後把觸發contentSocket 發法來創建實例,並綁定在state上的ws上,這裏一定要把commit 來作爲參數,一邊socket實例能觸發方法改變state,
我們知道了socket實例如何綁定和commit傳遞的了 ,下面我們看看websocket.js文件怎麼運作的了

function socket (commit, actions) {
    if (isType(commit) !== 'Function') {
        throw new Error('commit must be a function')
    }  
    this.commit = commit //觸發vuex中mutations的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函數是一個構造函數用來做初始化操作, isSocketContent()是用來獲取token等操作大家不必在意, 這裏觸發了一個soctketinit()soctket_init()方法,接下來我們看一下soctket_init()方法

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 + '?token=' + token) //這裏才進行了真正的socket連接

    _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)
}

這裏纔是真正的socket連接 和一些錯誤處理方式 , 這裏把socket連接和構造函數中的ws綁定在一起,以及一個連接失敗的調度機制 , 裏邊有一個之前一直提到的方法,.soctketsubscribe().soctket_subscribe() 沒錯就是它,監聽後端傳來信息的方法,類似於發佈訂閱模式的訂閱者,值得提出的一點是.soctket_heartSoctket() 是一個心臟搏動機制,我們知道如果socket連接長時間沒有通話會自動斷開連接,所以這裏有一個心臟搏動機制。接下來我們看一下,soctket_subscribe 方法

subscribe訂閱器

/**
* 訂閱器->接受廣播
*/

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(res.data)
            
        }    
        _this.$soctket_heartSoctket()
    }
}

我們纔看到原來之前vuex傳進來的 commit 在這裏發揮了作用,也就是觸發mutations 來改變state裏邊 的數據 ,來重新渲染試圖 ,接下來我們看一下emit觸發器

emit觸發器

 /**
* 觸發器->發佈信息
* @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')
    }
    if (_this.ws.readyState === 1) { // 連接成功狀態
        _this.ws.send(value)
        _this.$soctket_heartSoctket()
        callback && callback()
    }
    else if (_this.ws.readyState === 0) { // 連接中狀態 ,輪詢查詢連接
        eventPoll(poll, 1, 500, () => {
            _this.ws.send(value)                                                             
            _this.$soctket_heartSoctket()
            callback && callback()
        })
    }
    else { // 失敗重新連接
        _this.$soctket_init(() => {
            _this.$soctket_emit(value, callback)
        })
    }
}

這個就是之前提到的emit 觸發器 用來在vue中調用, 來向服務端發起數據通信,就實現了雙向的數據通信, 裏邊有一個輪詢器 來輪詢eventPoll ,websocket 的狀態是否是已經連通的狀態
,那麼在Vue文件中是怎麼調用emit的呢 ,很簡單就是調用vuex中之前綁定的state裏邊的wx

 const { ws } = this.$store.state.socket
   ws.$soctket_emit(JSON.stringify({
                data: 'hello , world'
            }), () => {
               console.log('發送成功')
            })

就是這麼簡單觸發的。以上整個機制都已經講解了一邊,那麼還有心跳機制,給大家介紹一下

heart心跳機制

/**
* 心臟搏動機制->防止斷開連接
*/

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('heart , socket')
            this.$soctket_heartSoctket()
        } else {
            this.$soctket_init()
        }
    }, 59000)

就是不斷向服務端發起消息,來防止斷開連接。
還有兩個方法來控制ws的連接和關閉

/**
* 開啓,關閉 socket
*/
/**
* 關閉socket連接
*/
socket.prototype.$soctket_close = function () {
    if (this.timer) clearTimeout(this.timer)
    if (this.errorResetTimer)clearTimeout(this.errorResetTimer)
    this.closeWs = true
    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()
}

小程序的socket連接 ,

小程序的socket連接和h5 的差不多一個體系,也是用公共管理進行連接 , 不過commit的傳遞方式和h5有點出入,這裏就不解釋了,這套體系在項目中還是比較穩定的**,喜歡的朋友歡迎來到gitHub上下載源碼

你可以在github上找到源碼[here][1].

鏈接: [link]https://github.com/laoxiedabaojian/websocket-vue-react-.git
https://github.com/laoxiedabaojian/websocket-vue-react-

謝謝大家~~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章