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的接口去除登錄攔截,就調通了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM