微信小程序WebSocket实践

栏目: Html5 · 发布时间: 5年前

内容简介:微信基础库1.7.0之后的版本提供了新版的WebSocket API,考虑到兼容性问题,尝试整合新旧两种版本的API,进行简单的封装。从以下几个角度对微信小程序中所提供的WebSocket API封装支持1.7.0+,连接后会返回一个SocketTask对象,在该对象上监听该连接的各种事件与执行发送消息等操作。

微信基础库1.7.0之后的版本提供了新版的WebSocket API,考虑到兼容性问题,尝试整合新旧两种版本的API,进行简单的封装。

从以下几个角度对微信小程序中所提供的WebSocket API封装

  • API兼容性
  • 重连机制

小程序WebSocket API

旧版WebSocket API的使用

  • 创建 - wx.connectSocket
  • 发送消息 - sendSocketMessage
  • 监听事件 - onSocketOpen
let socketOpen = false
const socketMsgQueue = []
// 连接socket
wx.connectSocket({
  url: 'test.php'
})
// 监听打开事件
wx.onSocketOpen(function(res) {
  socketOpen = true
  for (let i = 0; i < socketMsgQueue.length; i++){
    sendSocketMessage(socketMsgQueue[i])
  }
  socketMsgQueue = []
})
// 发送消息
function sendSocketMessage(msg) {
  if (socketOpen) {
    wx.sendSocketMessage({
      data:msg
    })
  } else {
    socketMsgQueue.push(msg)
  }
}

新版WebSocket API的使用

支持1.7.0+,连接后会返回一个SocketTask对象,在该对象上监听该连接的各种事件与执行发送消息等操作。

let socketTask = wx.connectSocket({
  url: 'test.php'
})
// 监听打开事件
socketTask.onOpen(function(res) {
  socketOpen = true
  // 发送信息
  socketTask.send({msg: "hello world"})
})

并发数

  • 1.7.0 及以上版本,最多可以同时存在 5WebSocket 连接。
  • 1.7.0 以下版本,一个小程序同时只能有一个 WebSocket 连接,如果当前已存在一个 WebSocket 连接,会自动关闭该连接,并重新创建一个 WebSocket 连接。

模块封装

简单封装封装一个兼容新旧socketAPI的模块,仅考虑存在单个socket连接的情况

创建与事件监听

  • 新版: 在socket连接时会返回一个socketTask对象,监听事件是在该对象的基础上进行
  • 旧版: 直接使用wx放进行创建与监听方法

统一创建与添加监听函数

init() {
  let st = this.connect()
  this.listen(st)
  this.manualClose = false
}

创建连接

connect() {
  let st = wx.connectSocket(this.config.server)
  return st
}

添加事件监听函数

listen(st) {
  if (st !== undefined) {
    this.ws = st
    this.ws.onOpen(() => this.openHandle())
    this.ws.onError(() => this.errorHandle())
    this.ws.onClose(() => this.closeHandle())
    this.ws.onMessage(res => this.messageHandle(res))
  } else {
    wx.onSocketOpen(() => this.openHandle())
    wx.onSocketError(() => this.errorHandle())
    wx.onSocketClose(() => this.closeHandle())
    wx.onSocketMessage(res => this.messageHandle(res))
  }
}

重连机制

预设标记位

retryLock = false; // 避免多次同时重连
socketOpen = false; // 连接状态
manualClose = false; // 主动断开标记

在连接关闭监听函数中执行重连

closeHandle() {
  console.info('WebSocket closed')
  this.socketOpen = false
  this.retryLock = false
  // 不论是error还是close都会触发close事件,统一在这里进行重连
  // 初次连接失败不进行重连(失败不会进入到onOpen的监听事件中,那时未声明retryTimes变量)
  this.retryTimes !== undefined && this.reconnect()
}

判断重连锁与是否主动断开进行重连

reconnect() {
  if (this.retryLock) return
  this.retryLock = true
  // 若manualClose为true,表明不是主动断开
  if (!this.manualClose) {
    // 开始重连
    setTimeout(() => {
      this.retry()
    }, this.retryInterval)
  }
}

重连函数,包含重连次数的限制

retry() {
  if (
    this.socketOpen ||
    (this.retryTimes > 0 && this.retryCount <= this.retryTimes)
  ) {
    console.warn(`reconnect ending. reconnect ${this.retryTimes} times`)
    if (!this.socketOpen) {
      this.config.closeCallback()
    }
    return
  }
  this.retryTimes += 1
  console.warn(`[ ${this.retryTimes} ]th reconnect WebSocket...`)
  this.init()
}

消息队列

添加消息队列,当重连后自动发送缓存消息

openHandle() {
  this.retryTimes = 0
  this.socketOpen = true
  this.retryLock = false

  this.messageQueue.map(e => this.send(e))
  this.messageQueue = []
}

若发送时断开则先将消息缓存到消息队列中

send(value) {
  let data = this.msgWrapper(value)
  data = JSON.stringify(data)
  if (!this.socketOpen) {
    this.messageQueue.push(data)
  } else {
    if (this.ws) {
      this.ws.send({ data })
    } else {
      wx.sendSocketMessage({ data })
    }
  }
}

辅助函数

添加一些包裹消息格式的 工具 函数

messageIndex = 0;
helper = {
  isPlainObject: val =>
    Object.prototype.toString.call(val) === '[object Object]',
  nextId: () => {
    this.messageIndex += 1
    return this.messageIndex
  },
  id: () => Date.now() + '.' + this.helper.nextId()
};
msgWrapper(data) {
  let msg = data
  if (this.helper.isPlainObject(msg)) {
    if (msg.type) {
      return msg
    } else {
      return this.msgWrapper({ type: 'message', msg, id: this.helper.id() })
    }
  } else {
    return this.msgWrapper({ type: 'message', msg, id: this.helper.id() })
  }
}

完整代码

export default class WXWebSocket {
  messageQueue = []; // 消息队列
  retryLock = false; // 避免多次同时重连
  socketOpen = false;
  manualClose = false; // 主动断开标记
  constructor(config) {
    this.config = config || {}
    // 重连间隔
    this.retryInterval =
      this.config.retryInterval && this.config.retryInterval > 100
        ? this.config.retryInterval
        : 3000
    // 重连次数
    this.retryCount = this.config.retryCount || 5
    this.init()
  }
  init() {
    let st = this.connect()
    this.listen(st)
    this.manualClose = false
  }
  connect() {
    let st = wx.connectSocket(this.config.server)
    console.log('current socket: ', st)
    return st
  }
  listen(st) {
    // 添加监听事件
    if (st !== undefined) {
      // 若存在SocketTask,则要通过readyState判断状态
      // CONNECTING: 0
      // OPEN: 1
      // CLOSING: 2
      // CLOSE: 3
      this.ws = st
      this.ws.onOpen(() => this.openHandle())
      this.ws.onError(() => this.errorHandle())
      this.ws.onClose(() => this.closeHandle())
      this.ws.onMessage(res => this.messageHandle(res))
    } else {
      wx.onSocketOpen(() => this.openHandle())
      wx.onSocketError(() => this.errorHandle())
      wx.onSocketClose(() => this.closeHandle())
      wx.onSocketMessage(res => this.messageHandle(res))
    }
  }
  close() {
    this.manualClose = true
    if (this.ws) {
      this.ws.close()
    } else {
      wx.closeSocket()
    }
  }
  send(value) {
    console.log('send value: ', value)
    let data = this.msgWrapper(value)
    data = JSON.stringify(data)
    if (!this.socketOpen) {
      // add new message to queue
      this.messageQueue.push(data)
    } else {
      if (this.ws) {
        this.ws.send({ data })
      } else {
        wx.sendSocketMessage({ data })
      }
    }
  }
  openHandle() {
    console.info('WebSocket connected')
    this.retryTimes = 0
    this.socketOpen = true
    this.retryLock = false

    this.messageQueue.map(e => this.send(e))
    this.messageQueue = []
  }
  errorHandle() {
    console.error('WebSocket error')
    this.socketOpen = false
  }
  closeHandle() {
    console.info('WebSocket closed')
    this.socketOpen = false
    this.retryLock = false
    // 不论是error还是close都会触发close事件,统一在这里进行重连
    // 初次连接失败不进行重连(失败不会进入到onOpen的监听事件中,那时未声明retryTimes变量)
    this.retryTimes !== undefined && this.reconnect()
  }
  reconnect() {
    if (this.retryLock) return
    this.retryLock = true
    // 若manualClose为true,表明不是主动断开
    if (!this.manualClose) {
      // 开始重连
      setTimeout(() => {
        this.retry()
      }, this.retryInterval)
    }
  }
  retry() {
    if (
      this.socketOpen ||
      (this.retryTimes > 0 && this.retryCount <= this.retryTimes)
    ) {
      console.warn(`end reconnect. reconnect ${this.retryTimes} times`)
      if (!this.socketOpen) {
        this.config.closeCallback()
      }
      return
    }
    this.retryTimes += 1
    console.warn(`[ ${this.retryTimes} ]th reconnect WebSocket...`)
    this.init()
  }
  messageHandle(res) {
    this.config.responseCallback(res)
  }
  msgWrapper(data) {
    let msg = data
    if (this.helper.isPlainObject(msg)) {
      if (msg.type) {
        return msg
      } else {
        return this.msgWrapper({ type: 'message', msg, id: this.helper.id() })
      }
    } else {
      return this.msgWrapper({ type: 'message', msg, id: this.helper.id() })
    }
  }
  messageIndex = 0;
  helper = {
    isPlainObject: val =>
      Object.prototype.toString.call(val) === '[object Object]',
    nextId: () => {
      this.messageIndex += 1
      return this.messageIndex
    },
    id: () => Date.now() + '.' + this.helper.nextId()
  };
}

使用

创建连接

let socketTask = new WXWebSocket({
  server: this.wsServerOption,
  responseCallback: e => {
    let { data } = e
    let { msg } = JSON.parse(data)
    this.msgStack.push(msg)
  },
  closeCallback: () => {
    this.socketTask = null
  }
})
this.socketTask = socketTask

发送消息

sendWSMessage(msg) {
  this.msgStack.push(msg)
  this.socketTask && this.socketTask.send(msg)
},

关闭连接

closeWS() {
  if (!this.socketTask) return
  if (this.socketTask.socketOpen) {
    this.socketTask.close()
    this.socketTask = null
  }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

与孩子一起学编程

与孩子一起学编程

[美] 桑德Warren Sande、Carter Sande / 苏金国、姚曜 等 / 人民邮电出版社 / 2010-11 / 65.00元

一本老少咸宜的编程入门奇书!一册在手,你完全可以带着自己的孩子,跟随Sande父子组合在轻松的氛围中熟悉那些编程概念,如内存、循环、输入和输出、数据结构和图形用户界面等。这些知识一点儿也不高深,听起来备感亲切,书中言语幽默风趣而不失真义,让学习过程充满乐趣。细心的作者还配上了孩子们都喜欢的可爱漫画和经过运行测试的程序示例,教你用最易编写和最易理解的Python语言,写出你梦想中的游戏程序。 ......一起来看看 《与孩子一起学编程》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具