WebSocket前端使用详解,一文带你搞懂WebSocket使用

357 阅读4分钟

WebSocket 是一种网络通信协议,很多高级功能都需要它。

本文介绍 WebSocket 使用方法。

一、为什么需要 WebSocket?

初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?

答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。

举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。

二、vue项目中WebSocket方法封装

1.代码封装

export class Websocket {
    messageSubject= null // subject对象,用于发送事件
    url= '' // 默认请求的url
    webSocket // websocket对象
    connectSuccess = false // websocket 连接成功
    period = 60 * 1000 * 3 // 10分钟检查一次
    serverTimeoutSubscription = null // 定时检测连接对象
    reconnectFlag = false // 重连
    reconnectPeriod = 5 * 1000 // 重连失败,则5秒钟重连一次
    reconnectSubscription = null // 重连订阅对象
    runTimeSubscription // 记录运行连接subscription
    runTimePeriod = 60 * 1000 // 记录运行连接时间
    isReconnect = true //是否重连  默认重连 当页面卸载时 不要重连
    constructor() {
      this.messageSubject = {}
      console.log('开始心跳监测')
      // 进入程序就进行心跳检测,避免出现开始就连接中断,后续不重连
      this.heartCheckStart()
      this.calcRunTime()
    }
  
    //发送消息
    sendMessage(msg) {
      this.webSocket.send(msg)
    }
  
    //创建新连接
    connect(url) {
      if (!!url) {
        this.url = url
      }
      this.createWebSocket()
    }
  
    //创建连接
    createWebSocket() {
      // 如果没有建立过连接,才建立连接并且添加时间监听
      this.webSocket = new WebSocket(this.url)
      // 建立连接成功
      this.webSocket.onopen = e => this.onOpen(e)
      // 接收到消息
      this.webSocket.onmessage = e => this.onMessage(e)
      // 连接关闭
      this.webSocket.onclose = e => this.onClose(e)
      // 异常
      this.webSocket.onerror = e => this.onError(e)
    }
  
    //连接打开
    onOpen(e) {
      console.log('websocket 已连接')
      // 设置连接成功
      this.connectSuccess = true
      // 如果是重连中
      if (this.reconnectFlag) {
        // 1.停止重连
        this.stopReconnect()
        // 2.重新开启心跳
        this.heartCheckStart()
        // 3.重新开始计算运行时间
        this.calcRunTime()
      }
    }
  
    // 接收到消息
    onMessage(event) {
      console.log('接收到的消息', event.data)
      // 将接受到的消息发布出去
      const message = JSON.parse(event.data)
      console.log('接收到消息时间', new Date().getTime())
      this.messageSubject = message
    }
  
    //连接关闭
    onClose(e) {
      console.log('连接关闭', e)
      this.connectSuccess = false
      this.webSocket.close()
      // 关闭时开始重连
      this.reconnect()
      this.stopRunTime()
      // throw new Error('webSocket connection closed:)');
    }
  
    //连接异常
    onError(e) {
      // 出现异常时一定会进onClose,所以只在onClose做一次重连动作
      console.log('连接异常', e)
      this.connectSuccess = false
      // throw new Error('webSocket connection error:)');
    }
  
    //开始重新连接
    reconnect() {
      if (!this.isReconnect) {
        //页面卸载时 不要重连
        return
      }
      // 如果已重连,则直接return,避免重复连接
      if (this.connectSuccess) {
        this.stopReconnect()
        console.log('已经连接成功,停止重连')
        return
      }
      // 如果正在连接中,则直接return,避免产生多个轮训事件
      if (this.reconnectFlag) {
        console.log('正在重连,直接返回')
        return
      }
      // 开始重连
      this.reconnectFlag = true
      // 如果没能成功连接,则定时重连
      let n = 0;
      this.reconnectSubscription = setTimeout(() => {
        console.log(`重连${n++}次`)
        const url = this.url
        // 重新连接
        this.connect(url)
      },this.reconnectPeriod)
    }
  
    //停止重连
    stopReconnect() {
      // 连接标识置为false
      this.reconnectFlag = false
      // 取消订阅
      if (typeof this.reconnectSubscription !== 'undefined' && this.reconnectSubscription != null) {
        clearTimeout(this.reconnectSubscription)
      }
    }
  
    //开始心跳监测
    heartCheckStart() {
      this.serverTimeoutSubscription = setTimeout(() => {
        // 保持连接状态,重置下
        if (this.webSocket != null && this.webSocket.readyState === 1) {
            console.log('连接状态,发送消息保持连接')
        } else {
            // 停止心跳
            this.heartCheckStop()
            // 开始重连
            this.reconnect()
            console.log('连接已断开,重新连接')
        }
      },this.period)
    }
  
    //停止心跳监测
    heartCheckStop() {
      // 取消订阅停止心跳
      if (typeof this.serverTimeoutSubscription !== 'undefined' && this.serverTimeoutSubscription != null) {
        clearTimeout(this.serverTimeoutSubscription)
      }
    }
  
    //开始计算运行时间
    calcRunTime() {
      let i = 0;
      this.runTimeSubscription = setTimeout(() => {
        console.log('运行时间', `${i++}分钟`)
      }, this.runTimePeriod);
    }
  
    //停止计算运行时间
    stopRunTime() {
      if (typeof this.runTimeSubscription !== 'undefined' && this.runTimeSubscription !== null) {
        clearTimeout(this.runTimeSubscription)
      }
    }
  
    //关闭连接
    close() {
      console.log('连接关闭')
      this.isReconnect = false
      this.webSocket.close()
      this.heartCheckStop()
      this.stopRunTime()
    }
}

2.方法调用

...
<script>
    import { Websocket } from '@/utils/websocket'
    export default {
        name: 'Websocket',
        data() {
            return {
             list: [],
              websocket: null,
            }
        },
        watch: {
          'websocket.messageSubject': function(newValue){
               if(newValue.message) {
                this.list.unshift(newValue.message)
               }
           }
        },
        created(){
           this.websocket = new Websocket();
           let host = location.host
           let url = ''
           if(location.protocol == 'https:') {
              url = `wss://${host}/...`
           }else {
              url = `ws://${host}/...`
           }
           this.websocket.connect(url)
        },
        methods: {},
        destroyed() {
         this.websocket.close()
         this.websocket = null
      },
    }
</script>
...

三、Angular## 项目中WebSocket方法封装

1.代码封装

import { Injectable } from '@angular/core'
import { interval, Subject } from 'rxjs'
@Injectable({
  providedIn: 'root'
})
export class SecurityWebsocketService {
  messageSubject: Subject<any> // subject对象,用于发送事件
  private url: string // 默认请求的url
  private webSocket: WebSocket // websocket对象
  connectSuccess = false // websocket 连接成功
  period = 60 * 1000 * 3 // 10分钟检查一次
  serverTimeoutSubscription: any = null // 定时检测连接对象
  reconnectFlag = false // 重连
  reconnectPeriod = 5 * 1000 // 重连失败,则5秒钟重连一次
  reconnectSubscription: any = null // 重连订阅对象
  runTimeSubscription: any // 记录运行连接subscription
  runTimePeriod = 60 * 10000 // 记录运行连接时间
  isReconnect: boolean = true //是否重连  默认重连 当页面卸载时 不要重连
  constructor() {
    this.messageSubject = new Subject()
    console.log('开始心跳监测')
    // 进入程序就进行心跳检测,避免出现开始就连接中断,后续不重连
    this.heartCheckStart()
    this.calcRunTime()
  }

  //发送消息
  sendMessage(msg: string) {
    this.webSocket.send(msg)
  }

  //创建新连接
  connect(url: string) {
    if (!!url) {
      this.url = url
    }
    this.createWebSocket()
  }

  //创建连接
  createWebSocket() {
    // 如果没有建立过连接,才建立连接并且添加时间监听
    this.webSocket = new WebSocket(this.url)
    // 建立连接成功
    this.webSocket.onopen = e => this.onOpen(e)
    // 接收到消息
    this.webSocket.onmessage = e => this.onMessage(e)
    // 连接关闭
    this.webSocket.onclose = e => this.onClose(e)
    // 异常
    this.webSocket.onerror = e => this.onError(e)
  }

  //连接打开
  onOpen(e: Event) {
    console.log('websocket 已连接')
    // 设置连接成功
    this.connectSuccess = true
    // 如果是重连中
    if (this.reconnectFlag) {
      // 1.停止重连
      this.stopReconnect()
      // 2.重新开启心跳
      this.heartCheckStart()
      // 3.重新开始计算运行时间
      this.calcRunTime()
    }
  }

  // 接收到消息
  onMessage(event: MessageEvent<any>) {
    console.log('接收到的消息', event.data)
    // 将接受到的消息发布出去
    const message = JSON.parse(event.data)
    console.log('接收到消息时间', new Date().getTime())
    this.messageSubject.next(message)
  }

  //连接关闭
  onClose(e: CloseEvent) {
    console.log('连接关闭', e)
    this.connectSuccess = false
    this.webSocket.close()
    // 关闭时开始重连
    this.reconnect()
    this.stopRunTime()
    // throw new Error('webSocket connection closed:)');
  }

  //连接异常
  private onError(e: Event) {
    // 出现异常时一定会进onClose,所以只在onClose做一次重连动作
    console.log('连接异常', e)
    this.connectSuccess = false
    // throw new Error('webSocket connection error:)');
  }

  //开始重新连接
  reconnect() {
    if (!this.isReconnect) {
      //页面卸载时 不要重连
      return
    }
    // 如果已重连,则直接return,避免重复连接
    if (this.connectSuccess) {
      this.stopReconnect()
      console.log('已经连接成功,停止重连')
      return
    }
    // 如果正在连接中,则直接return,避免产生多个轮训事件
    if (this.reconnectFlag) {
      console.log('正在重连,直接返回')
      return
    }
    // 开始重连
    this.reconnectFlag = true
    // 如果没能成功连接,则定时重连
    this.reconnectSubscription = interval(this.reconnectPeriod).subscribe(async val => {
      console.log(`重连:${val}次`)
      const url = this.url
      // 重新连接
      this.connect(url)
    })
  }

  //停止重连
  stopReconnect() {
    // 连接标识置为false
    this.reconnectFlag = false
    // 取消订阅
    if (typeof this.reconnectSubscription !== 'undefined' && this.reconnectSubscription != null) {
      this.reconnectSubscription.unsubscribe()
    }
  }

  //开始心跳监测
  heartCheckStart() {
    this.serverTimeoutSubscription = interval(this.period).subscribe(val => {
      // 保持连接状态,重置下
      if (this.webSocket != null && this.webSocket.readyState === 1) {
        console.log(val, '连接状态,发送消息保持连接')
      } else {
        // 停止心跳
        this.heartCheckStop()
        // 开始重连
        this.reconnect()
        console.log('连接已断开,重新连接')
      }
    })
  }

  //停止心跳监测
  heartCheckStop() {
    // 取消订阅停止心跳
    if (typeof this.serverTimeoutSubscription !== 'undefined' && this.serverTimeoutSubscription != null) {
      this.serverTimeoutSubscription.unsubscribe()
    }
  }

  //开始计算运行时间
  calcRunTime() {
    this.runTimeSubscription = interval(this.runTimePeriod).subscribe(period => {
      console.log('运行时间', `${period}分钟`)
    })
  }

  //停止计算运行时间
  stopRunTime() {
    if (typeof this.runTimeSubscription !== 'undefined' && this.runTimeSubscription !== null) {
      this.runTimeSubscription.unsubscribe()
    }
  }

  //关闭连接
  close() {
    console.log('连接关闭')
    this.isReconnect = false
    this.webSocket && this.webSocket.close()
    this.heartCheckStop()
    this.stopRunTime()
  }
}

2.方法调用

import { Component, OnInit } from '@angular/core'
import { WebsocketService } from './websocket.service'

@Component({
  selector: 'websocket',
  templateUrl: './websocket.component.html'
})
export class WebsocketComponent implements OnInit {
  data: any[] = []

  constructor(private websocket: WebsocketService) {}

  ngOnInit(): void {
    let host = location.host
    let protocol = location.protocol == 'https:' ? 'wss' : 'ws'
    let url = `${protocol}://${host}/...`
    this.websocket.connect(url)
    this.websocket.messageSubject.subscribe((data: any) => {
      try {
        console.log('websocket返回数据', data)
      } catch (e) {
        console.error(e)
      }
    })
  }

  ngOnDestroy() {
    this.websocket.close()
    this.websocket = null
  }
}