• vue使用websocket坎坷历程


    起因:项目首页左右两栏布局,左侧布局是一个列表始终固定,右侧布局路由跳转,左侧列表定时刷新(http轮训),右侧路由跳转时会有一些页面初始化的请求和用户点击交互的请求。

    目前很多定时刷新,都是http轮训,方便且快捷。但我这种情况有一个问题,定时器1s刷新请求接口,如果此时再操作右侧面板,会导致有请求失败的情况,

    因为1s定时刷新占用带宽,会导致其它的请求阻断。(ps:曾经对于定时刷新做过一个优化,即在发下一个请求前,取消上次的http请求,但感觉还是很蹩脚,感兴趣的可以去研究下)

    基于上述情况,最后采用websocket实时推送,在介绍使用前,有一些关于websocket特性在此说明下

    websocket特性:

    一、主要有ws(不加密)和wss(加密);ws是基于http请求建立握手,wss是基于https请求建立握手

    二、ws的握手基于http的get方式,协议应不小于1.1

    二、ws和wss的请求头会比单纯的http的请求头多很多特殊的header

    三、ws请求在建立连接后,通信双方都可以在任何时刻向另一方发送数据(http只能客户端发送请求给服务端)

    websocket的基本用法这里就不介绍了,网上相关的文章有很多,在此贴出不错的链接地址

    w3c对websocket的介绍:https://www.runoob.com/html/html5-websocket.html

    阮一峰大师的帖子:http://www.ruanyifeng.com/blog/2017/05/websocket.html

    项目环境:vue  nginx  webpack

    本地跑项目(http://localhost:8080/),项目地址是http,可以发送ws请求,本地使用webpack代理

    // 修改vue.config.js文件
      devServer: {
        open: true, // 启动后在浏览器打开
        proxy: { 
          '/api': {// 设置普通的http代理
            target: 'http://x.x.x.x:8080',
            changeOrigin: true,
            pathRewrite: {
              '^/api': ''
            }
          },
          '/socket': {// 设置websocket代理
            target: 'http://x.x.x.x:8080',
            ws: true, // 开启websocket代理  注意
            changeOrigin: true,
            pathRewrite: {
              '^/socket': ''
            }
        }}}

    本地开发到这里一切都很顺利,接下来需要线上测试环境代理websocket,线上环境使用nginx代理,如下修改nginx配置

    http {
       ...
        map $http_upgrade $connection_upgrade {
            default upgrade;
            ''      close;
        }
        ...
        server {
            listen       8084;
            server_name  _;
            root         /usr/share/nginx/html/pcNet;
            include /etc/nginx/default.d/*.conf;
            
            location ^~/socket/ {
               proxy_pass http://x.x.x.x:8080/;
               proxy_http_version 1.1; 
               proxy_set_header Host $host;
               proxy_set_header X-Real-IP $remote_addr;
               proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
               proxy_set_header Upgrade websocket;
               proxy_set_header Connection Upgrade;
            }        
    
            location ^~/api/ {
               proxy_pass http://x.x.x.x:8080/;
            }
            ...
        }}

    配置的属性参数:

    map指令:根据变量$http_upgrade的值创建新的变量$connection_upgrade,创建规则就是{}里面的东西,若规则没有做匹配,则$connection_upgrade为默认值upgrade,若$http_upgrade为空字符串的话,则$connection_upgrade值会是 close。

    proxy_pass: 要代理到的url

    roxy_http_version: 代理时使用的 http版本

    proxy_set_header Host:http请求的主机域名

    proxy_set_header X-Real-IP: 给代理设置原http请求的ip,填写$remote_addr 即可

    proxy_set_header X-Forwarded-For:反向代理之后转发之前的IP地址

    proxy_set_header Upgrade: 把代理时http请求头的Upgrade 设置为原来http请求的请求头,wss协议的请求头为websocket

    proxy_set_header Connection: 因为代理的wss协议,所以http请求头的Connection设置为Upgrade

     nginx代理设置好后,线上访问控制台报错:

    上图的意思是在https的项目里需要发起wss请求,故如下修改:

    到这里,客户端和服务器已能通讯。

    在此需要特别注意https是加密请求,需要SSL加密,nginx配置https的代理(wss的握手阶段是https请求)

    这个是比较麻烦的,在此贴出解决方案的链接:https://www.cnblogs.com/zhoudawei/p/9257276.html

    本文暂不介绍如何配置SSL,因为本项目的线上环境,网关做了拦截,会把https请求替换为http,wss请求替换为ws,这一点就省去申请CA证书和配置nginx支持https的麻烦

    经过上面的配置,我们的websocket在nginx上跑起来了,万分欢喜的我们,发现一分钟不发消息就自动短线了。

    造成原因:默认情况下,如果代理服务器nginx在60s内没有传输任何数据,则连接将被关闭。

    解决方案:nginx给出两种解决方案:

    1.修改 proxy_read_timeout(默认60秒)https://www.iteye.com/blog/happyqing-2320095

    2.客户端浏览器定时发送心跳包(时间要短于proxy_read_timeout)。

    第一种简单粗暴(我试了还没有成功,还请各位大神赐教),但是时间再长也是一个值,还是会有超时的可能,基于此我使用第二种方案,在60s内定时发送心跳包来重置保持连接的时间。

    vue单页面代码如下:

    export default {
      data () {
        return {
          socket: null,
          connectCount: 0, // 连接次数
          heartInterval: null
        }
      },
      created () {
        this.initSocket()
      },
      methods: {
        /**
         * 建立连接是http
         * 消息推送都是tcp连接,没有同源限制
         * 服务端人员try catch 消息推送不成功,则关闭连接
         */
        initSocket () {
          // 建立连接 (线上环境)
          const url = `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/socket`
          this.socket = new WebSocket(`${url}/meeting/wsServer/PC-${this.$store.getters.userId}`)
    
          this.socket.onmessage = (evt) => {
            if (evt.data === '连接成功' || evt.data.includes('refresh')) {
              this.heartCheck() // 重置心跳检测
              this.onRefresh() // 接收到推送消息,刷新列表
            }
          }
          // 监听窗口事件,当窗口关闭时,主动断开websocket连接
          window.onbeforeunload = () => {
            this.socket.close()
            this.heartInterval && clearTimeout(this.heartInterval)
          }
        },
        /**
         * 定时发送心跳包
         * 59s发送一次心跳,比nginx设置的最大连接时间短一点,以达到在临界点重置连接时间
         */
        heartCheck () {
          const that = this
          this.heartInterval && clearTimeout(this.heartInterval)
          this.heartInterval = setInterval(() => {
            if (this.socket.readyState === 1) { // 连接状态
              this.socket.send('ping')
            } else {
              that.connectCount += 1
              if (that.connectCount <= 5) {
                this.initSocket() // 断点重连5次
              }
            }
          }, 59 * 1000)
        }
      }
    }

    ps:当客户端网络不好,或者断网,服务器并不知情,还是会给客户端推送消息,造成资源浪费,故后端服务器要做异常处理,在消息推送不成功,主动关闭websocket连接。

    在此遇到一个问题,所有问题都解决了,但是始终建立握手没成功,后经排查是后端对ws的请求也做了登录拦截,检测没有用户token,给拒绝了

    然后把ws的接口去除登录拦截,就调通了。

  • 相关阅读:
    js的style.width取不到元素的宽度值
    git bush 无法使用箭头进行选择
    exports module.exports export export default之间的关系
    vue前端项目中excel文件下载
    vue -- router路由跳转错误 , NavigationDuplicated
    node url模块
    SSO CAS 单点系列
    离线电脑搭建开发环境
    Shader的语法
    NavMesh名字、层索引、层值之间的转换
  • 原文地址:https://www.cnblogs.com/caofeng11/p/12808675.html
Copyright © 2020-2023  润新知