最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 关于Websocket的两次实践(Vue项目实现在线聊天&Angular项目首页实时推送图表数据)

    正文概述 掘金(Delicia Lani)   2021-07-16   773

    摘要:该文章记录了我使用websocket的两次实践经历,在第一次实践过程中,踩了很多坑。第二次实践,可谓得心应手,但是很多理论性还很欠缺。通过该文章,从理论到实践,一举全部拿下。

    目录

    一、Websocket理论

    (1)Websocket是什么?

    (2)Websocket出现的背景?

    (3)采用该协议的优势?

    (4)握手协议

    (5)Websocket和Socket的区别?

    (6)关于socket.io

    二、Vue实现在线聊天(实践与踩坑)

    三、Angular项目首页实时推送图表数据


    一、Websocket理论

    (1)Websocket是什么?

    Websocket是一种网络通信协议,是一种在单个TCP连接上的全双工通信协议。

    WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

    全双工通信:又称为双向同时通信,即通信的双方可以同时发送和接收信息的信息交互方式。

    关于通信协议的分类,该文章图文并茂,介绍很详细:blog.csdn.net/Dingjiawang…

    (2)Websocket出现的背景?

    很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

    而比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源。

    在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

    (3)采用该协议的优势?

    • 较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了。
    • 更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和Comet等类似的长轮询比较,其也能在短时间内更多次地传递数据。
    • 保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。
    • 更好的二进制支持。Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。
    • 可以支持扩展。Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。
    • 更好的压缩效果。相对于HTTP压缩,Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。

    (4)握手协议

    WebSocket同HTTP一样也是应用层的协议,但是它是一种双向通信协议,是建立在TCP之上的。

    Websocket 通过HTTP/1.1 协议的101状态码进行握手。

    为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)。

    握手过程:

    1. 浏览器、服务器建立TCP连接,三次握手。这是通信的基础,传输控制层,若失败后续都不执行。
    2. TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的版本号等信息。(开始前的HTTP握手)
    3. 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。
    4. 当收到了连接成功的消息后,通过TCP通道进行传输通信。

    (5)Websocket和Socket的区别?

    Socket是TCP、IP网络的API,是为了方便使用TCP或UDP而抽象出来的一层,位于应用层和传输层之间的一组接口,而Websocket是一个典型的应用层协议。

    (6)关于socket.io

    Socket.IO是node.js的一个模块,它是通过WebSocket进行通信的一种简单方式。WebSocket协议很复杂,从头开始写一个支持WebSocket的应用程序将需要花费很多时间。Socket.IO提供服务器和客户端双方的组件,所以只需一个模块就可以给应用程序加入对WebSocket的支持。Socket.IO也解决了各浏览器的支持问题(不是所有浏览器都支持WebSocket)并让实时通信可以跨几乎所有常用的浏览器实现。Socket.IO的设计非常好,将实时通信带入应用程序的过程便得非常简单。如果想做任何涉及在web服务器和浏览器之间通信的事情,那么nodejs和Socket.IO是极好的选择哦!

    # npm安装socket.op
    $ npm install --save socket.io
    

    二、Vue实现在线聊天(实践与踩坑)

    需求:在后台管理系统中加入售后服务人员与客户的在线聊天功能。

    框架:Vue

    实践第一步:连接websocket及携带token

    连接websocket 的方式我所接触过的包括原生方式,代码如下:

    `initWebSocket () {
        // 初始化websocket
        const wsuri = 'wss://XXXXXXXXXXXXXX/ws/adminOnlineService'
        this.websock = new WebSocket(wsuri)
    },`
    

    踩坑一:对于我的项目而言,这样连接报错500,原因是没有携带token,该方式携带token的方式我所了解的如下:
    (1)send发送参数,这种方式的劣势为每次发送消息,都会重新连接一次websocket;
    (2)请求地址中带参数,如:var  wss = new WebSocket("wss://" + url?token + "/webSocketServer");
    (3)基于协议头:this.websock = new WebSocket(wsuri, ['Bearer' + store.state.token]);
    经历了一番尝试之后,我认为无论采用哪种方式,都需要跟服务端协商,即前端传什么样的类型,后端应采用相应的处理方式,不要轻易否定任何一种方式,这是一个尝试的过程。之所以这样说,是因为前端尝试了很多种方式,最终决定放在请求地址中,此时服务端做相应的处理。
    连接websocket的另一种方式是使用socket.io-client

    `onConnect: () => {
          console.log('connect')
          this.message = 'connect'
        },
        onDisconnect: () => {
          console.log('disconnect')
          this.message = 'disconnect'
        },
        connect: () => {
          let socket = io('wss://XXXXXXXXXXX', {
            path: '/welfare/ws/adminOnlineService',
            query: {
              'Authorization': 'Bearer  abdadfssadfasdf'
            }
          })
          socket.connect()
    }`
    

    这样在请求的时候会带上token,但是同样token会拼在url中,需要服务端进一步做处理。该方式连接成功,但是遇到的问题是在连接成功之后,会断连,之后会立马连接,如此循环,直到刷新页面为止。该问题当时没有解决。但是原生方式没有存在该问题,连接一次即可。

    实践第二步:在点击登录按钮时就进行websocket的连接

    点击登录按钮即连接websocket,要求定义全局方法,当时采用的是vuex。虽然时间过去很久了,并且现在看来并不需要采用这种方案,但是再来回顾下当时的实现思路吧。

     在vuex的modules下新建了websocket.js文件,其中的代码如下所示:

    import store from './user'
    const state = {
      websock: null
    }
    
    const mutations = {
      STAFF_UPDATEWEBSOCKET (state, websock) {
        state.websock = websock
      }
      // STAFF_SEND (state, text) {
      //   state.websock.send(text)
      // }
    }
    
    // 实现websocket的连接,需要携带参数token
    const actions = {
      // 用到 ES2015 的参数解构来简化代码(特别是我们需要调用 commit 很多次的时候)
      STAFF_WEBSOCKET ({ commit }) {
        let token = encodeURI('Bearer ' + store.state.token)
        const wsuri = 'wss://XXXXXXXXX/?Authorization=' + token + '&EIO=3&transport=websocket'
        commit('STAFF_UPDATEWEBSOCKET', new WebSocket(wsuri))
        // 只有定义了onopen方法,才能继续实现接收消息,即在使用的地方调用onmessage方法。
        state.websock.onopen = function () {
        }
        // 心跳包,30s左右无数据浏览器会断开连接Heartbeat
        setInterval(function () {
          state.websock.send(JSON.stringify({
            'heart': true
          }))
        }, 30000)
      }
    }
    
    // 该部分为了获取websocket的相关方法。会发现此处跟mutations 里的写法是类似的,但是,想使用return,需要将相关数据写在getters里面。
    const getters = {
      STAFF_UPDATE (state) {
        return state.websock
      }
    }
    export default {
      state,
      mutations,
      actions,
      getters
    }
    

     相关代码注释在上述代码中已经有了体现,使用方法在下面的代码中:

    1.调用websocket的send方法,即点击发送的时候,会调用send方法,将消息发送给服务端,下述代码是针对不同的定义方式,所采取的不同方法,比如,第三个方法是取得getters中的;第二个方法是取得mutations中的注释的STAFF_SEND中的方法;第一个是取得actions中定义的方法。

    乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作

    // Action 通过 store.dispatch 方法触发
    this.$store.dispatch('STAFF_WEBSOCKET')
    // this.$store.commit('STAFF_SEND').send('这是来自客户端的消息')
    this.$store.getters.STAFF_UPDATE.send('sdfsfs')
    

    Actions 支持同样的载荷方式和对象方式进行分发:

    // 以载荷形式分发
    store.dispatch('incrementAsync', {
      amount: 10
    })
    
    // 以对象形式分发
    store.dispatch({
      type: 'incrementAsync',
      amount: 10
    })
    

     2.通过websocket实现接收来自服务端的消息,实现方式如下代码:

    onmessage () {
      let that = this
      this.$store.getters.STAFF_UPDATE.onmessage = function (evt) {
      let message = JSON.parse(evt.data)
      that.messages.push({
         content: message.content,
         self: false
      })
     }
    }
    

    关键是从getters中获取onmessage方法,上面强调过了,在调用该方法之前需要实现onopen方法。

    如上,就可以实现聊天了。

    实践第三步:websocket的事件和方法

    //实例化一个WebSocket对象,并传入要连接的决定URL
    var socket = new WebSocket("url");//    url中要使用ws://来代替http:// ;使用wss来代替https://
    //当成功建立连接时会触发open事件
    socket.onopen = function(){
        alert("established");
    }
    //当发生错误时会触发error事件
    socket.onerror = function(){
        alert("error!");
    }
    //当连接关闭时会触发close事件
    socket.onclose = function(){
        alert("closed!");
    }
    //使用send() 方法发送数据 只能接受字符串 json对象要先序列化成json字符串
    socket.send(str);
    //当服务端像客户端发来消息,WebSocket对象就会触发message事件
    socket.onmessage = function(event){
        console.log(event.data);//返回的数据 也为字符串形式
    }
    //调用close()方法 会关闭Web Sockets连接,可在任何时候调用close()方法
     socket.close();
    

    三、Angular项目首页实时推送图表数据

    在上述Websocket出现背景的地方,介绍了Websocket出现的原因。其实想来,这也是在该项目中,该需求使用该方案的原因。接触的其他项目中,页面中也会有实时刷新,会在每隔一段时间后,请求近10个接口。 在本次需求中,数据量更大,请求接口量更多,采用了该方式。

    下面是实现代码,其实使用过程都是一样的,主要看业务逻辑。

    initWebSocket() {
        const url = '*******';
        const ws = new WebSocket(url);
    
        ws.onopen = () => {
          this.clearWS();
          this.ws = ws;
          ws.onmessage = evt => {
            // 下述是业务逻辑
            const data = JSON.parse(evt.data);
            if (this.simpleMode) {
              this.reciveFromWs4Simple(data);
            } else {
              this.reciveFromWs(data);
            }
          };
          ws.onclose = () => {
            this.clearWS();
          };
          ws.onerror = () => {
            this.clearWS();
          };
        };
      }
       /**
       * 清除引用, 事件
       */
      private clearWS() {
        const ws: WebSocket = this.ws;
        if (ws) {
          ws.onclose = null;
          ws.onopen = null;
          ws.onmessage = null;
        }
        this.ws = null;
      }
      /**
       * 改成心跳监测连接是否断开的逻辑. 每隔10秒检查一次
       */
      private heartbeat() {
        this.reConnectTimer = window.setInterval(() => {
          const ws: WebSocket | null = this.ws;
          if (!this.isWSAvailable(ws)) {
            this.reConnect();
          }
        }, 10000);
      }
       /**
       * 做个重连, 用close和error做个伪重连
       */
      private reConnect() {
        console.warn('reconnect');
        this.clearWS();
        this.initWebSocket();
      }
    sendByWs(data) {
        const ws = this.ws;
        if (!ws) {
          console.warn('[ws-发送数据失败]: Websocket 还没准备好或已断开');
          return;
        }
    
        switch (ws.readyState) {
          case WebSocket.CONNECTING:
            console.warn('[ws-发送数据失败]: Websocket 正在连接中...');
            break;
          case WebSocket.OPEN:
            ws.send(JSON.stringify(data));
            break;
          default:
            console.warn('[ws-发送数据失败]: Websocket 即将断开或已经断开');
        }
      }
    
      switchLive(live) {
        this.live = live;
        const name = 'live';
        this.sendByWs({ name, live });
      }
    ngOnDestroy() {
        this.ws && this.ws.close(1000, '用户已经离开当前页面或页面发生刷新');
        this.reConnectTimer && clearInterval(this.reConnectTimer);
      }
    

    起源地下载网 » 关于Websocket的两次实践(Vue项目实现在线聊天&Angular项目首页实时推送图表数据)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元