首先,项目中如果需要长时间且不定时不间断地进行客户端与服务器端交互的时候,使用 WebSocket 连接是不二之选,比如交易所项目

那么创建一个 WebSocket 需要什么功能,怎么才能是一个健壮的 ws 体系呢?

首先我们需要几个基础功能:

1. onopen(ws 连接成功回调)

2. onmessage (ws 返回数据回调)

3. onclose (ws 关闭回调)

4. onerror (ws 报错回调)

当我们的 ws 有这些方法之后,我们就需要加几个方法用来和服务器端交互

1. subscibe (订阅)

2. unSubscibe (取消订阅)

3. request (向服务器发送数据)

有了这些方法之后我们就需要考虑一些异常点

1. reConnect (ws 重连,当 ws 发生异常并断开连接之后,重新连接 ws)

2. reSubscribe (重新订阅,当 ws 重新连接之后,将之前订阅的事件重新订阅)

3. Heartbeat (ws 心跳,我们需要时刻检测服务器端是否还活着)

4. pollingRollback (轮询备用回调,当 ws 断开连接之后,数据还是需要持续更新)

既然知道了这些,那我们需要创建一些方法来实现了,具体实现的逻辑就不讲了,大家可以直接看代码。


首先我们创建一个 socket 文件夹,像这样创建三个文件,这样我们方便维护

index.js

/*** websocket*/
import Heartbeat from './heartbeat'
import PollingRollback from './pollingRollback'export default class Socket {constructor(url) {this.ws = nullthis.url = urlthis.subscriptionMap = {}this.pollingRollback = nullthis.createPollingCallback() // 创建轮询this.start()}start() {if (!this.url) return console.error('url is required')this.ws = new WebSocket(this.url + "?lang=" + window.localStorage.lang);this.ws.addEventListener("open", this.onOpen);this.ws.addEventListener("message", this.onMessage);this.ws.addEventListener("close", this.onClose);this.ws.addEventListener("error", this.onError);}request(payload) { // 单纯地给服务器发送数据if (this.isConnected()) {this.ws.send(JSON.stringify({ ...payload, event: 'req' }));}}subscribe({ payload, rollback, callback }, isReSubscribe) {if (!isReSubscribe && this.subscriptionMap[payload.id]) returnthis.subscriptionMap[payload.id] = { payload, rollback, callback }this.pollingRollback.set(payload.id, rollback)if (this.isConnected()) {this.ws.send(JSON.stringify({ ...payload, event: 'sub' }));}}unSubscribe(id) {if (!id) returnif (this.isConnected()) {if (this.subscriptionMap[id]) {const payload = this.subscriptionMap[id].payloadthis.ws.send(JSON.stringify({ ...payload, event: 'cancel' }));this.pollingRollback.remove(id)delete this.subscriptionMap[id];}}}isConnected() {return this.ws && this.ws.readyState === WebSocket.OPEN}onOpen = () => {clearInterval(this.reConnectTimer)this.createHeartbeat() // 创建 socket 心脏this.reSubscribe() // 重新订阅已有的subthis.pollingRollback.close() // ws 连接之后,关闭轮询}onMessage = (result) => {const data = result.dataif (/ping|pong/i.test(data)) returnconst normalizedData = JSON.parse(data || "{}");this.handleCallback(normalizedData)}handleCallback = (data) => {const id = data.id;if (!id) return;if (this.subscriptionMap[id]) {this.subscriptionMap[id]["callback"] && this.subscriptionMap[id]["callback"](data);}}onClose = () => {console.warn(`【Websocket is closed】`)this.ws.removeEventListener("open", this.onOpen);this.ws.removeEventListener("message", this.onMessage);this.ws.removeEventListener("close", this.onClose);this.ws.removeEventListener("error", this.onError);this.ws = null;}onError = (error) => {if (error && error.message) {console.error(`【Websocket error】 ${error.message}`)}this.ws.close()this.reConnect()}reConnect() { // 开启重连this.pollingRollback.open() // ws连接之前,开启轮询if (this.reConnectTimer) returnthis.reConnectTimer = setInterval(() => {this.start()}, 3000)}reSubscribe() {Object.values(this.subscriptionMap).forEach(subscription => this.subscribe(subscription, true))}createHeartbeat() {this.heartbeat = new Heartbeat(this.ws)this.heartbeat.addEventListener('die', () => {this.ws.close()this.ws.reConnect()})}createPollingCallback() {this.pollingRollback = new PollingRollback()}
}

heartbeat.js

/*** 心跳*/
const INTERVAL = 5000 // ping 的间隔
const TIMEOUT = INTERVAL * 2 // 超时时间(只能是INTERVAL的整数倍数,超过这个时间会触发心跳死亡事件) 默认为 ping 两次没有响应则超时
const DIE_EVENT = new CustomEvent("die") // 心跳死亡事件 => 超时时触发export default class Heartbeat extends EventTarget {constructor(ws, interval, timeout) {super()if (!ws) returnthis.ws = wsthis.interval = interval || INTERVALthis.timeout = timeout || TIMEOUTthis.counter = 0this.ws.addEventListener("message", this.onMessage)this.ws.addEventListener("close", this.onClose)this.start()}ping() {this.counter += 1if (this.counter > (this.timeout / this.interval)) { // ping 没有响应 pongthis.dispatchEvent(DIE_EVENT)return}if (this.ws && this.ws.readyState === WebSocket.OPEN) {const data = JSON.stringify({ ping: new Date().getTime() })this.ws.send(data);}}pong(data) {this.ws.send(data.replace("ping", "pong"));}onMessage = (result) => {const data = result.data;if (/pong/i.test(data)) { // 服务器响应重新计数return this.counter = 0}if (/ping/.test(data)) { // 服务器 ping 我们return this.pong(data)}}onClose = () => {this.ws.removeEventListener("message", this.onMessage);this.ws.removeEventListener("close", this.onClose);this.ws = null;clearInterval(this.keepAliveTimer)}start() {this.keepAliveTimer = setInterval(this.ping, this.interval)}
}

pollingRollback.js

/*** 轮询*/export default class PollingRollback {constructor(interval) {this.rollbackMap = {}this.rollbackTimer = nullthis.interval = interval || 3000}set(id, rollback) {this.rollbackMap[id] = rollback}remove(id) {delete this.rollbackMap[id]}open() {this.rollbackTimer = setInterval(() => {Object.values(this.rollbackMap).forEach(rollback => rollback && rollback())}, this.interval)}close() {clearInterval(this.rollbackTimer)}
}

这样一来,一个完整的 Socket 就封装完成了,那我们该如何使用呢?

我们可以创建一个 ws.js 文件,把我们需要的方法再次封装一个类
至于有人问为什么要在原有基础上再封装一个类,因为暴露的方法必须简介明了的原则吧,也可以直接使用原始类,只是两人开发的话,另外一个人不容易上手吧!

ws.js

import Socket from './socket'class CommonWs {constructor(url) {this.ws = nullthis.url = url}connect() {this.ws = new Socket(this.url)}request(payload) {if (!this.ws) this.connect()this.ws.request(payload)}subscribe(payload, rollback, callback) {if (!this.ws) this.connect()this.ws.subscribe({ payload, rollback, callback })}unSubscribe(id) {if (!this.ws) this.connect()this.ws.unSubscribe(id)}close() {if (!this.ws) returnthis.ws.close()}isConnected() {return this.ws && this.ws.isConnected()}
}// ws
export const ws = new CommonWs(<wsUrl>)// ws2
export const ws2 = new CommonWs(<ws2Url>)

ok,到这一步你就可以直接创建 ws 来使用订阅,取消订阅,关闭ws连接等的方法了,至于 Socket 背后的那些事我们并不需要知道,只要保证我们的代码没有 bug 就可以了。

这里我补充一下 subscibe 参数的含义

* payload 订阅需要的内容,比如我们要订阅钱包余额信息,后台需要我们发送 id,那我们就传 { id: 'balance' } 之类的

* rollback ws 连接发生异常断开的期间,页面的数据还是需要刷新,那么我们就是用 rollback 函数来轮询更新数据

* callback 顾名思义,ws 返回数据后会调用该对应方法

js 实现封装 WebSocket 连接相关推荐

  1. 【微信小程序控制硬件⑧ 】微信小程序以 websocket 连接阿里云IOT物联网平台mqtt服务器,封装起来使用就是这么简单!(附带Demo)

    [微信小程序控制硬件第1篇 ] 全网首发,借助 emq 消息服务器带你如何搭建微信小程序的mqtt服务器,轻松控制智能硬件! [微信小程序控制硬件第2篇 ] 开始微信小程序之旅,导入小程序Mqtt客户 ...

  2. vue全局安装jquery,vue使用bootstrap框架,vue中封装websocket通讯,vue引入element-ui 组件库,引入highcharts图表插件

    vue安装jquery: 1.使用vue-cli创建好vue项目后,在项目文件夹下,使用命令npm install jquery --save-dev 引入jquery. 2.修改项目文件 build ...

  3. websocket连接mqtt实现发布及订阅主题

    2019独角兽企业重金招聘Python工程师标准>>> 环境:linux(ubuntu.Centos7),websocket,mosquitto-1.4.10,libwebsocke ...

  4. 微信小程序websocket连接服务器(接收信息)

    app.js App({onLaunch: function () {var that = this;// 登录wx.login({success: res => {// 发送 res.code ...

  5. websocket 连接本地端口_Web应用架构WebSocket 协议介绍

    由HyBi工作组开发的WebSocket有线协议(RFC 6455)由两个高级组件组成:用于协商连接参数的开放HTTP握手和二进制消息帧机制,以实现低开销.基于消息的文本和二进制数据传输. WebSo ...

  6. 支付宝小程序使用MQTT over WebSocket连接阿里云IoT物联网平台

    前言 之前写了一篇微信小程序使用MQTT over WebSocket连接阿里云IoT物联网平台,介绍了如何使用mqtt.js在微信小程序上连接mqtt服务器,文中顺带提了mqtt.js是支持支付宝小 ...

  7. 微信小程序websocket连接

    websocket.wxml <view class="send-content"><text>发送内容:</text><input cl ...

  8. 【Web通信】WebSocket详解:WebSocket是什么?如何使用WebSocket?在Vue中封装WebSocket(心跳监测)。nginx配置websocket。

    一.WebSocket相关定义 1. WebSocket定义 WebSocket 是一种基于TCP的全双工通信协议,它提供了一种在浏览器和服务器之间建立持久连接来交换数据的方法.数据可以作为" ...

  9. websocket连接

    websocket连接 mounted() {this.initWebSocket(); }, websocket相关函数封装 initWebSocket() { this.ws = new WebS ...

最新文章

  1. 数据库期末复习重点,临时抱佛脚高分通过考试
  2. c语言用一维数组求字符串,c语言一维数组练习题.doc
  3. 朴素贝叶斯(NaiveBayes)针对小数据集中文文本分类预测
  4. html滚动字幕如何向下移动,按向下键的同时,菜单选项向下移动,浏览器右边的滚动条也跟着跑怎么办。这个bug怎么改...
  5. 4还是火箭弹好 rust_做人还是“软”一些好!身体这4个地方越硬越危险,看看你有没有...
  6. Python基础—10-常用模块:time,calendar,datetime
  7. 问题:jquery event.which详解
  8. es 全量同步mysql_MySQL用得好好的,为什么要转ES?
  9. mysql处理点云_Oracle云时代MySQL HTAP解决方案
  10. Pixel 3 的最佳照片功能
  11. qq企业邮箱怎么删除邮件服务器,腾讯企业邮箱如何删除邮件,有什么要注意的呢?...
  12. 免证书发布ipa文件真机测试
  13. 论文笔记:EPNet: Enhancing Point Features with Image Semantics for 3D Object Detection
  14. 淘宝直通车关键数据 如何利用直通车获取手淘搜索流量 如何利用定向操作获得猜你喜欢流量
  15. PS关于打开图片或者直接拖入图片结果显示程序错误
  16. 163邮箱会员揭秘,163邮箱注册,你最想了解的几件事
  17. 生活中软件易用性的例子_多用“举出例子”“比如说”,来进行生活中的语言交流...
  18. TCP/IP 事件选择模型
  19. 阅读和了解什么是形式化方法?
  20. linux 脚本 wait,shell脚本使用 timeout + wait 完成: 超时退出执行,等待执行完毕并处理执行结果 - yanbin's Blog...

热门文章

  1. Ubantu安装KDE桌面环境_下载KDE主题
  2. 数字图像处理第十一章——表达与描述
  3. [数据结构 -- C语言] 堆实现Top-K问题,原来王者荣耀的排名是这样实现的,又涨知识了
  4. O2OA下载及安装部署-Linux环境
  5. 通过硬盘盘符查询硬盘槽位
  6. 程序员私活收入:随随便便月入1万多,有的走向人生巅峰
  7. 穿越机组装配置心得(基于F4飞控)
  8. 关于交流接触器的基本知识_交流接触器的功能认识
  9. 计算机学院贺凤,计算机学院举办2018年暑期社会实践宣讲会
  10. 通过小程序积分营销工具提升用户消费的三种方式