一、概述

随着互联网的发展,传统的HTTP协议已经很难满足W©b应用日益复杂的需求了。近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据。

传统的HTTP协议是无状态的,每次请求(Request)都要由客户端(如浏览器)主动发起,服务端进行处理后返回Response结果。传统Web模式中客户端是主动方,服务端是被动方,服务端很难主动向客户端发送数据,对于信息变化不频繁的Wb应用来说传统模式是足够应付的。对于涉及实时信息交互的Wb应用,如带有即时通信、实时数据、订阅推送等功能的应用,上述模式带来了很大的不便。在WebSocket规范被提出之前,开发人员若要实现这些实时性较强的功能,经常会使用折中的解决方法:轮询(Polling)和Comet技术。后者本质上其实也是一种轮询,但有所改进。轮询是最原始实现实时Web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动。明显地,这种方法会导致过多不必要的请求,浪费流量和服务器资源。

Comet技术可以分为长轮询和流技术。长轮询改进了上述的轮询技术,减少了无用的请求。它为某些数据设定过期时间,当数据过期后才会向服务端发送请求,这种机制适合数据改动不是特别频繁的情况。流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新连接状态以保持HTTP长连接存活,据此,服务端就可以通过这条长连接主动将数据发送给客户端。

轮询和Comet技术都基于请求-应答模式,都不算是真正意义上的实时技术。它们的每一次请求、应答,在相同的头部信息上都浪费了一定流量,并且复杂度也较大。

伴随着HTML5推出的WebSocket,真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。WebSocket的工作流程是这样的:浏览器通过JavaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过TCP连接传输数据。因为WebSocket连接本质上是TCP连接,所以不需要每次传输都带上重复的头部数据,它的数据传输量比轮询和Comet技术小很多。

微信小程序的WebSocket功能提供了如下几个接口:

接口 功能和用途
wx.connectSocket() 创建WebSocket连接
wx.onSocketOpen() 监听WebSocket打开
wx.onSocketError() 监听WebSocket错误
wx.sendSocketMessage() 发送WebSocket消息
wx.onSocketMessage() 接收WebSocket消息
wx.closeSocket() 关闭WebSocket连接
wx.onSocketClose() 监听WebSocket关闭

二、WebSocket接口

2.1、SocketTask wx.connectSocket(Object object)

创建一个 WebSocket 连接。

  • 参数:Object object
属性 类型 默认值 必填 说明
url string 开发者服务器 wss 接口地址
header Object HTTP Header,Header 中不能设置 Referer
protocols Array.<string> 子协议数组
tcpNoDelay boolean false 建立 TCP 连接的时候的 TCP_NODELAY 设置
perMessageDeflate boolean false 是否开启压缩扩展
timeout number 超时时间,单位为毫秒
forceCellularNetwork boolean false 强制使用蜂窝网络发送请求
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)
  • 返回值:SocketTask
    WebSocket 任务
  • 并发数:
    • 1.7.0 及以上版本,最多可以同时存在 5 个 WebSocket 连接。
    • 1.7.0 以下版本,一个小程序同时只能有一个 WebSocket 连接,如果当前已存在一个 WebSocket 连接,会自动关闭该连接,并重新创建一个 WebSocket 连接。
wx.connectSocket({
  url: 'wss://example.qq.com',
  header:{
    'content-type': 'application/json'
  }
})

2.2、wx.onSocketOpen(function listener)

监听 WebSocket 连接打开事件。

  • 参数:function listener
    WebSocket 连接打开事件的监听函数

Object res:

属性 类型 说明
header object 连接成功的 HTTP 响应 Header

2.3、wx.onSocketError(function listener)

监听 WebSocket 错误事件。

  • 参数:function listener
    WebSocket 错误事件的监听函数

Object res

属性 类型 说明
errMsg string 错误信息

2.4、wx.sendSocketMessage(Object object)

通过 WebSocket 连接发送数据。需要先 wx.connectSocket,并在 wx.onSocketOpen 回调之后才能发送。

  • 参数:Object object
属性 类型 默认值 必填 说明
data string/ArrayBuffer 需要发送的内容
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)
let socketOpen = false
let socketMsgQueue = []
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)
  }
}

2.5、wx.onSocketMessage(function listener)

监听 WebSocket 接收到服务器的消息事件。

  • 参数:unction listener
    WebSocket 接收到服务器的消息事件的监听函数

Object res:

属性 类型 说明
data string/ArrayBuffer 服务器返回的消息

2.6、wx.closeSocket(Object object)

关闭 WebSocket 连接。

  • 参数:Object object
属性 类型 默认值 必填 说明
code number 1000(表示正常关闭连接) 一个数字值表示关闭连接的状态号,表示连接被关闭的原因。
reason string 一个可读的字符串,表示连接被关闭的原因。这个字符串必须是不长于 123 字节的 UTF-8 文本(不是字符)。
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)
wx.connectSocket({
  url: 'test.php'
})

//注意这里有时序问题,
//如果 wx.connectSocket 还没回调 wx.onSocketOpen,而先调用 wx.closeSocket,那么就做不到关闭 WebSocket 的目的。
//必须在 WebSocket 打开期间调用 wx.closeSocket 才能关闭。
wx.onSocketOpen(function() {
  wx.closeSocket()
})

wx.onSocketClose(function(res) {
  console.log('WebSocket 已关闭!')
})

2.7、wx.onSocketClose(function listener)

监听 WebSocket 连接关闭事件。

  • 参数:function listener
    WebSocket 连接关闭事件的监听函数

Object res:

属性 类型 说明
code number 一个数字值表示关闭连接的状态号,表示连接被关闭的原因。
reason string 一个可读的字符串,表示连接被关闭的原因。

2.8、案例

<view class="zong">
  <view class="shang">
    <view wx:for="{{messagelist}}" wx:for-index="idx" wx:for-item="itemName">
      <view class="selfMessage" wx:if="{{itemName.type == 'self'}}">
        <view class="nameInfo">{{itemName.name + " " + itemName.time}}</view>
        <view class="detailMessage">{{itemName.message}}</view>
      </view>
      <view class="otherMessage" wx:else>
        <view class="nameInfo">{{itemName.name + " " + itemName.time}}</view>
        <view class="detailMessage">{{itemName.message}}</view>
      </view>
    </view>
  </view>
  <view class="xia">
    <form report-submit bindsubmit="fasong">
      <viwe class="inputArea">
        <input type="text" name="inputValue" placeholder="{{placeholderText}}" class="message"/>
      </viwe>
      <button size="default" type="primary" form-type="submit" class="sendButton">发送</button>
    </form>
  </view>
</view>
Page({
  data: {
    placeholderText: "连接后端服务器中...",
    messagelist: [],
    socketOpen: false,
  },
  onLoad: function(options) {
    console.log("将要连接后端服务器。")
    wx.connectSocket({
      url: 'ws://127.0.0.1:8080/ws'
    });
    wx.onSocketOpen((result) => {
      console.log("连接后台服务器成功。")
      this.setData({
        placeholderText: "连接成功,请输入您的昵称。",
        socketOpen: true
      })
    });
    wx.onSocketMessage((res) => {
      console.log("收到后端服务器内容:" + res.data);
      var data = res.data;
      var dataArray = data.split("_");
      var newMessage = {
        type: dataArray[0],
        name: dataArray[1],
        time: dataArray[2],
        message: dataArray[3]
      };
      var newArray = this.data.messagelist.concat(newMessage);
      this.setData({
        messagelist: newArray,
        placeholderText: "请输入聊天信息"
      });
    });
  },
  onUnload: function() {
    wx.closeSocket();
  },
  fasong: function(e) {
    if(e.detail.value.inputValue != "") {
      if(this.data.socketOpen) {
        wx.sendSocketMessage({
          data: e.detail.value.inputValue
        })
      }
    }
  }
})

三、SocketTask

WebSocket 任务,可通过 wx.connectSocket() 接口创建返回。

3.1、SocketTask.close(Object object)

关闭 WebSocket 连接

  • 参数:Object object
属性 类型 默认值 必填 说明
code number 1000(表示正常关闭连接) 一个数字值表示关闭连接的状态号,表示连接被关闭的原因。
reason string 一个可读的字符串,表示连接被关闭的原因。这个字符串必须是不长于 123 字节的 UTF-8 文本(不是字符)。
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)

3.2、SocketTask.onClose(function listener)

监听 WebSocket 连接关闭事件

  • 参数:function listener
    WebSocket 连接关闭事件的监听函数

Object res:

属性 类型 说明
code number 一个数字值表示关闭连接的状态号,表示连接被关闭的原因。
reason string 一个可读的字符串,表示连接被关闭的原因。

3.3、SocketTask.onError(function listener)

监听 WebSocket 错误事件

  • 参数:function listener
    WebSocket 错误事件的监听函数

Object res:

属性 类型 说明
errMsg string 错误信息

3.4、SocketTask.onMessage(function listener)

监听 WebSocket 接收到服务器的消息事件

  • 参数:function listener
    WebSocket 接收到服务器的消息事件的监听函数

Object res:

属性 类型 说明
data string/ArrayBuffer 服务器返回的消息

3.5、SocketTask.onOpen(function listener)

监听 WebSocket 连接打开事件

  • 参数:function listener
    WebSocket 连接打开事件的监听函数

Object res:

属性 类型 说明
header object 连接成功的 HTTP 响应 Header
profile Object 网络请求过程中一些调试信息

其中profile结构如下:

结构属性 类型 说明
fetchStart number 组件准备好使用 SOCKET 建立请求的时间,这发生在检查本地缓存之前
domainLookUpStart number DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
domainLookUpEnd number DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等
connectStart number 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等。注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间
connectEnd number 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等。注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间。注意这里握手结束,包括安全连接建立完成、SOCKS 授权通过
rtt number 单次连接的耗时,包括 connect ,tls
handshakeCost number 握手耗时
cost number 上层请求到返回的耗时

3.6、SocketTask.send(Object object)

通过 WebSocket 连接发送数据

  • 参数:Object object
属性 类型 默认值 必填 说明
data string/ArrayBuffer 需要发送的内容
success function 接口调用成功的回调函数
fail function 接口调用失败的回调函数
complete function 接口调用结束的回调函数(调用成功、失败都会执行)

3.7、示例

** 1. 发送消息**

socketTask.send({
  data: JSON.stringify({ message: 'Hello' }),
  success() {
    console.log('发送成功')
  },
  fail(err) {
    console.error('发送失败', err)
  }
})

2. 接受消息

socketTask.onMessage((res) => {
  console.log('收到消息:', res.data)
  // 处理消息
  const data = JSON.parse(res.data)
})

3. 监听连接打开

socketTask.onOpen(() => {
  console.log('WebSocket连接已打开')
  console.log('当前状态:', socketTask.readyState)
})

4. 监听连接关闭

socketTask.onClose((res) => {
  console.log('连接已关闭', res)
  // res.code: 关闭码
  // res.reason: 关闭原因
})

5. 监听错误

socketTask.onError((err) => {
  console.error('WebSocket错误:', err)
})

6. 关闭连接

// 可选参数
socketTask.close({
  code: 1000,        // 关闭码,默认1000
  reason: '正常关闭',  // 关闭原因
  success() {
    console.log('关闭成功')
  },
  fail(err) {
    console.error('关闭失败', err)
  }
})

完整使用示例:

// 创建连接
const createWebSocket = () => {
  const socketTask = wx.connectSocket({
    url: 'wss://example.com/ws',
    header: { 'Content-Type': 'application/json' }
  })
  
  // 监听连接打开
  socketTask.onOpen(() => {
    console.log('连接已建立')
    
    // 发送心跳
    setInterval(() => {
      if (socketTask.readyState === 1) {
        socketTask.send({ data: 'ping' })
      }
    }, 30000)
  })
  
  // 监听消息
  socketTask.onMessage((res) => {
    try {
      const data = JSON.parse(res.data)
      handleMessage(data)
    } catch (e) {
      console.log('接收文本消息:', res.data)
    }
  })
  
  // 监听错误
  socketTask.onError((err) => {
    console.error('连接错误:', err)
    // 重连逻辑
    setTimeout(createWebSocket, 3000)
  })
  
  // 监听关闭
  socketTask.onClose((res) => {
    console.log('连接关闭:', res.code, res.reason)
    if (res.code !== 1000) {
      // 非正常关闭,尝试重连
      setTimeout(createWebSocket, 3000)
    }
  })
  
  return socketTask
}

// 使用
let socket = createWebSocket()

// 页面卸载时关闭连接
onUnload() {
  if (socket && socket.readyState === 1) {
    socket.close({ code: 1000, reason: '页面卸载' })
  }
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐