美文网首页
聊天功能开发中所遇到的问题

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

作者: 不知所语 | 来源:发表于2019-02-02 14:36 被阅读12次

    一直以来我认为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
          }
        }
    

    相关文章

      网友评论

          本文标题:聊天功能开发中所遇到的问题

          本文链接:https://www.haomeiwen.com/subject/rtyssqtx.html