🗒️WebSocket

原生的

const socket = new WebSocket(ws_url)

// 监听事件
socket.onopen = () => { }
socket.onclose = () => { }
socket.onerror = () => { }
socket.onmessage = (event) => {
    let data = JSON.parse(event.data)  // 接收数据 event.data
}

// 调用方法
socket.close()

SocketIO

使用,代码示意:

// https://github.com/socketio/socket.io-client/blob/master/docs/API.md
// https://github.com/socketio/engine.io-client#methods
import io from 'socket.io-client'

class SocketIO {
    
    socket;

    constructor(){
	this.socket = io(
	    ws_origin,            // url 默认是 window.location
	    {  
		path: ws_path,    // String. 默认是 '/socket.io'
		// query: {},     // Object. 查询参数
		// parser:;       // 使用的解析器。默认是 socket.io 自带的 Parser。详见 socket.io-parser
		// reconnection: true,           // Boolean. 是否自动重新连接。默认 true
		// reconnectionAttempts: 1,      // Number. 尝试重新连接的次数。默认 Infinity
		// reconnectionDelay: 1000,      // Number. 尝试重新连接之前的等待多长。默认 1000。但它受随机因子影响,比如默认的初始延时会介于 500-1500 ms
		// reconnectionDelayMax: 5000,   // Number. 重新连接之间等待的最长时间。默认 5000。每次尝试都将重新连接延迟增加2倍以及随机化
		// randomizationFactor: 0.5,     // Number. 默认 0.5。 0 <= randomizationFactor <= 1
		// timeout: 20000,               // Number. 连接超时, 默认 20000。  如果多长时间之内连接还没建成功,则认为失败,会触发 connect_error 和 connect_timeout 事件。进而触发重连机制
		autoConnect: autoConnect,            // Boolean. 默认是 true。如果设置成 false,则必须手动调用 manager.open,在你觉得合适的时候
		transports: ['websocket', 'polling']    // Array. 要尝试的传输列表(按序)。默认是 ['polling', 'websocket']。
	})
		
	// 监听事件
	// this.socket.on('connect', () => {})
	// this.socket.on('connect_error', (error) => {})
	// this.socket.on('connect_timeout', (error) => {})
	// this.socket.on('reconnect', (attemptNumber) => {})
	// this.socket.on('disconnect', (reason) => {})
	// this.socket.on('error', (error) => {})
	// this.socket.on('ping', () => {
	//    console.warn('套接字: ping')
	// })
	// this.socket.on('pong', (latency) => {
	//    console.warn('套接字: pong')
	// })
	
	// 监听服务器推送的消息(自定义类型)
	socket.on('REPLY', (res) => {
	    let data = JSON.parse(res) // 再传给对应的函数
	})
	socket.on('END', (res) => {
	    let data = JSON.parse(res) // 再传给对应的函数
	})
	socket.on('DRAWBACK', (res) => {
	    let data = JSON.parse(res) // 再传给对应的函数
	})
    }
}

对外 API,代码示意:

class SocketIO {

    // 一. 连接和断开
    connect(){
        // 若用户登录了,再连接
        socket.connect()
    }
    close(msg){
        socket.close(msg)  // 手动断开
    }

    // 二. 发送消息
    send(msg){
        socket.send('client send ' + msg)
    }

    // 三. 接收消息:自定义的三类
    captureReply(callback, path){
        this.#capture(CB_REPLY_LIST, callback, path)
    }
    captureEnd(callback, path){
        this.#capture(CB_END_LIST, callback, path)
    }
    captureDrawBack(callback, path){
        this.#capture(CB_DRAWBACK_LIST, callback, path)
    }

    #capture(obj, callback, path){
        if(path){
            obj[path] = callback
        }else{
            obj['*'] = callback
        }
    }
}

centrifuge-js

使用上:若用户登录了,则开始连接:先 new,再 subscribe 再 connect。代码示意:

import Centrifuge from 'centrifuge'  // https://github.com/centrifugal/centrifuge-js

class SocketIO {

    centrifuge;
    subscription;

    constructor(){
        // 关于 ping 的间隔,默认是 25000 即 25s
        //     这里重置为 5s,让其在 10s 之内有心跳,因为服务器 proxy 代理有超时时间 10s
        //     设置成 2s,以适应移动端不稳定的网络状态(网络不稳定时,能及时收到短信提醒/页面重刷)
        this.centrifuge = new Centrifuge(ws_url, { 
            pingInterval: 5000                              
        })

        // 用 user_id 生成 connection token
        const jsw_token = this.#getJWT(user_id, 'secret')  
        this.centrifuge.setToken(jsw_token)

        // this.centrifuge.on('connect', (context) => {})
        // this.centrifuge.on('disconnect', (context) => {})

        // 订阅并连接
        this.#subscribeAndConnect()
    }

    #subscribeAndConnect(){
    	this.subscription = centrifuge.subscribe(uid, (msg) => {
            // 收到消息 msg
            let res = msg.data
            const data = JSON.parse(res.content)
            const type = res.msg_type   // 自定义的:end drawback reply
			
            // 具体的业务逻辑:把 data 传给特定的函数(因为不止一个页面要接收后台的消息)
            if(CB_LIST[type]){
		let cur_path = location.pathname
		if(CB_LIST[type][cur_path]){
		    call_cb(CB_LIST[type][cur_path], data)  // cb(data)
		}
		call_cb(CB_LIST[type]['*'], data)           // cb(data)
	    }
	})
	// .on('join', (msg) => {})
    	// .on('leave', (msg) => {})
	// .on('unsubscribe', (context) => {})
	// .on('subscribe', (context) => {})
	// .on('error', (msg) => {})
	// .on('publish', (msg) => {})

        this.centrifuge.connect()
    }
}

对外提供的接口,有三类,代码示意如下。

// 代码示意
class SocketIO {

    // 一. 连接和关闭
    connect(){
        this.centrifuge.connect()
    }
    close(){
        this.centrifuge.disconnect() // 也会触发 subscription 的 unsubscribe 
    }

    // 二. 当页面是可见状态、不可见状态时
    revisible(){
        this.connect()
        this.#subscriptChannel()
    }
    rehide(){
        // subscription.unsubscribe()
        // subscription.removeAllListeners()

        // 单纯取消订阅,会导致发短信有延迟,故改为直接关闭
        this.close()
    }

    // 三. 接收服务器传来的消息
    captureReply(callback, path){
        this.#capture(callback, path, 'REPLY')   // 医生的新回复
    }
    captureEnd(callback, path){
        this.#capture(callback, path, 'END')     // 订单正常关闭
    }
    captureDrawBack(callback, path){
        this.capture(callback, path, 'DRAWBACK') // 退款
    }
}
class SocketIO {

    // 私有方法:把接收方的回调函数都存起来
    #capture(callback, path, type){
        if(!CB_LIST[type]){
            CB_LIST[type] = {}
        }

        if(path){
            CB_LIST[type][path] = callback
        }else{
            CB_LIST[type]['*'] = callback
        }
    }
}

Last updated