聊天功能开发中所遇到的问题

一直以来我认为Websocket就是长连接。长连接就是客户端和服务器一直保持连接,并且两者都可以主动发消息给另一方

基于现在的理解列出上面这句话中错误的知识点:

  • websocket和长连接是两个不同的概念
  • 主动发消息的功能不是长连接的特点
  • 加粗部分对长连接这个概念的描述不够透彻,太过片面,不算错误(后面会讲到)

概念混淆(长连接,Websocket)

长连接:一个网站中通常会有很多请求,如果每个请求都需要进行TCP连接的建立和断开,那么是相当耗时的,处理速度也会大大降低,在Http 1.1中支持进行长连接(Connection: keep-alive),即:一个http操作建立TCP连接后,进行数据传输,结束后并不会立即断开而是保持连接,后面的http请求使用同一个连接进行数据传输,不需要再次建立连接,始终使用一个TCP连接

Websocket:一种在单个TCP连接上进行全双工通信的协议。使得客户端和服务器之间的数据交换更加简单,允许服务端主动向客户端推送数据。在Websocket的API中,浏览器和服务器只要完成一次握手,就可以创建持久性的连接,并进行双向数据传输。(HTML5中新定义的协议,可以很好的节省服务器的资源和带宽,实时进行通讯)

根据对这两个概念的理解,得出Websocket协议本身就实现了长连接,因为它是基于单个TCP连接进行通信,在此基础上还实现了服务器可主动发送数据,进行双向通信的功能

为什么要用Websocket

有这样的需求场景:很多网站需要实现推送功能,这就需要服务器主动向浏览器发送请求,但是Http协议的弊端就是通信只能由客户端发起,那么我们为了实现推送功能,就需要客户端不停的去问服务器,于是就有了下面的对话。这就是最常见的轮询技术,要定时的向服务器发请求,但是请求中可能包含较长的头部,真正有效的数据只有小部分,因此这种方式会浪费很多资源,增加服务器压力。而Websocket实现了服务器主动向客户端推送消息,因此像聊天室这种业务场景更多的会使用Wescoket来实现

client:你有没有消息哇。。。
server:暂时没有,你过会再来
client:我又来了,现在有没有。。。
server:有了有了,给你拿去。。。

Websocket使用的关键点——心跳保活策略

Websocket提供的API使用都比较简单,这里不再赘述,主要想说心跳保活的问题,因为在连接建立后,有可能存在网络断开或者别的异常情况,这个时候是没法触发浏览器或者服务器的onclose事件,无法知道断开连接,也就无法进行重连,并且收发的数据也会丢失掉,因此我们就有了Websocekt的心跳,就是说明连接还处于正常状态。

心跳的检测的机制:每隔一段时间客户端向服务器发送一个心跳包,服务器收到后会返回一个数据包,以此来确认两者都活着,否则任意一方在规定时间内没有收到心跳包,则会认定网络断开,需要重连。若重连N次还是失败,则会提示用户网络问题,依赖着心跳检测才可以保障长连接的存在

前后端约定的规则:

  • 每隔30秒发送一次心跳包
  • 6秒内未返回心跳包断开重连
  • 重连超过10次不再连接
createWebSocket () {
      this.ws = new WebSocket(`ws://localhost:3000`)

      this.ws.onopen = () => {
        this.reconnectCount = 0
        this.heartCheck().start()
        console.log('Connection to server opened')
      }

      this.ws.onmessage = (event) => {
        const response = JSON.parse(event.data)
        this.heartCheck().reset()
        console.log('Client received a message', response)
      }

      this.ws.onclose = () => {
        console.log('connection closed')
        clearTimeout(this.heartTimer)
        this.reconnect()
      }
    }

    // ws重连操作
    reconnect () {
      this.reconnectCount = this.reconnectCount + 1
      console.log('重连' + this.reconnectCount + '次')
      if (this.reconnectCount > 10) {
        throw new Error('重新连接失败')
      }
      this.createWebSocket()
    }

    // 心跳检测
    heartCheck () {
      const _self = this
      return {
        start: function () {
          _self.heartTimer = setTimeout(function () {
            _self.ws.send(JSON.stringify({
              'type': 1,
              'timestamp': new Date().getTime()
            }))
            _self.closeTimer = setTimeout(() => _self.ws.close(), 6000) // 超过6秒未返回心跳包断开连接
          }, 30000) // 每隔30秒发心跳包检测
        },
        reset: function () {
          clearTimeout(_self.heartTimer)
          clearTimeout(_self.closeTimer)
          this.start()
        }

      }
    }

页面滑动的分页功能

需求:在联系人列表页滑动,当滚动条距离底部小于50px触发请求获取第二页的数据然后展示

问题拆解:

  • 监听dom的滚动事件
  • 计算滚动条距离底部的距离

对Dom进行事件监听比较容易,我当时在第二个问题上花的时间比较久,最终明白了如何计算。
先搞明白Dom的三个属性:scrollHeight、clientHeight、scrollTop

  • scrollHeight:容器里所有内容的高度
  • clientHeight:容器的高度,可见高度
  • scrollTop:滚动条滚动过的距离

举例:一个DIV的高度是400px(clientHeight),里面有一个很长的列表,这个列表的高度是1000px(scrollHeight),说明还有600px的内容是不可见的,要想显示剩余的内容,就需要拖动滚动条来显示。当不拉滚动条时,此时scrollTop是0,如果把滚动条拉到底,剩下600px的内容显示出来,此时scrollTop就是600,于是我们认为scrollTop就是滚动条可以滚动的距离,也就是剩余内容的高度[0, 600]
scrollHeight - clientHeight - scrollTop = 滚动条距离底部的距离

有了这个公式我们的滑动分页功能就没多大问题了
但是在后面自测的时候发现:在滑动的过程中不可能精准的在小于50px的范围内停一次,有可能在45,34用户都停下了,这就导致请求发了不止一次,而我们理想是只发一次。最后我用一个变量isLoading去判断,请求发出后isLoading为true,回来后改为false,在scroll事件处理函数中去判断这个变量的值,为true,则不发请求。

 // 滚动条距离底部小于50px时触发请求
    handleScrollEvent () {
      const element = this.$refs.scrollUserList
      if (element.scrollHeight - element.clientHeight - element.scrollTop < 50 && !this.isLoading) {
        this.page += 1
        this.getFriendsList(this.page)
      }
    }

    async getFriendsList (page = 0) {
      this.isLoading = true
      const params = {
        wechatId: localStorage.getItem('loginWechatId'),
        productId: 211,
        token: 'dalfdll',
        page,
        limit: 20
      }

      const response = await chatService.getFriendsList(params)
      const { success } = response

      if (success) {
        this.isLoading = false
      }
    }

你可能感兴趣的:(聊天功能开发中所遇到的问题)