瀏覽器多個標籤頁之間的通訊

Jsp發表於2018-05-29

一:websocket通訊

全雙工(full-duplex)通訊自然可以實現多個標籤頁之間的通訊

WebSocket是HTML5新增的協議,它的目的是在瀏覽器和伺服器之間建立一個不受限的雙向通訊的通道,比如說,伺服器可以在任意時刻傳送訊息給瀏覽器。為什麼傳統的HTTP協議不能做到WebSocket實現的功能?這是因為HTTP協議是一個請求-響應協議,請求必須先由瀏覽器發給伺服器,伺服器才能響應這個請求,再把資料傳送給瀏覽器。

也有人說,HTTP協議其實也能實現啊,比如用輪詢或者Comet。這個機制的缺點一是實時性不夠,二是頻繁的請求會給伺服器帶來極大的壓力。

Comet本質上也是輪詢,但是在沒有訊息的情況下,伺服器先拖一段時間,等到有訊息了再回復。這個機制暫時地解決了實時性問題,但是它帶來了新的問題:以多執行緒模式執行的伺服器會讓大部分執行緒大部分時間都處於掛起狀態,極大地浪費伺服器資源。另外,一個HTTP連線在長時間沒有資料傳輸的情況下,鏈路上的任何一個閘道器都可能關閉這個連線,而閘道器是我們不可控的,這就要求Comet連線必須定期發一些ping資料表示連線“正常工作”。

WebSocket並不是全新的協議,而是利用了HTTP協議來建立連線。為什麼WebSocket連線可以實現全雙工通訊而HTTP連線不行呢?實際上HTTP協議是建立在TCP協議之上的,TCP協議本身就實現了全雙工通訊,但是HTTP協議的請求-應答機制限制了全雙工通訊。WebSocket連線建立以後,其實只是簡單規定了一下:接下來,我們們通訊就不使用HTTP協議了,直接互相發資料吧。安全的WebSocket連線機制和HTTPS類似。首先,瀏覽器用wss://xxx建立WebSocket連線時,會先通過HTTPS建立安全的連線,然後,該HTTPS連線升級為WebSocket連線,底層通訊走的仍然是安全的SSL/TLS協議。

WebSocket連線必須由瀏覽器發起,特點:

(1)建立在 TCP 協議之上,伺服器端的實現比較容易。

(2)與 HTTP 協議有著良好的相容性。預設埠也是80和443,並且握手階段採用 HTTP 協議,因此握手時不容易遮蔽,能通過各種 HTTP 代理伺服器。

(3)資料格式比較輕量,效能開銷小,通訊高效。

(4)可以傳送文字,也可以傳送二進位制資料。

(5)沒有同源限制,客戶端可以與任意伺服器通訊。

(6)協議識別符號是ws(如果加密,則為wss),伺服器網址就是 URL。

二:定時器setInterval+cookie

  1. 在頁面A設定一個使用 setInterval 定時器不斷重新整理,檢查 Cookies 的值是否發生變化,如果變化就進行重新整理的操作。
  2. 由於 Cookies 是在同域可讀的,所以在頁面 B 稽核的時候改變 Cookies 的值,頁面 A 自然是可以拿到的。
    這樣做確實可以實現我想要的功能,但是這樣的方法相當浪費資源。雖然在這個效能過盛的時代,浪費不浪費也感覺不出來,但是這種實現方案,確實不夠優雅。

三:使用localstorage

  • localstorage是瀏覽器多個標籤共用的儲存空間,所以可以用來實現多標籤之間的通訊(ps:session是會話級的儲存空間,每個標籤頁都是單獨的)。
  • 直接在window物件上新增監聽即可:

window.onstorage = (e) => {console.log(e)}
// 或者這樣
window.addEventListener('storage', (e) => console.log(e))
複製程式碼

  • onstorage以及storage事件,針對都是非當前頁面對localStorage進行修改時才會觸發,當前頁面修改localStorage不會觸發監聽函式。然後就是在對原有的資料的值進行修改時才會觸發,比如原本已經有一個key會a值為b的localStorage,你再執行:localStorage.setItem('a', 'b')程式碼,同樣是不會觸發監聽函式的。

四:html5瀏覽器的新特性SharedWorker

普通的webworker直接使用new Worker()即可建立,這種webworker是當前頁面專有的。然後還有種共享worker(SharedWorker),這種是可以多個標籤頁、iframe共同使用的。

  • SharedWorker可以被多個window共同使用,但必須保證這些標籤頁都是同源的(相同的協議,主機和埠號)
  • 首先新建一個js檔案worker.js,具體程式碼如下:

// sharedWorker所要用到的js檔案,不必打包到專案中,直接放到伺服器即可
let data = ''
onconnect = function (e) {
  let port = e.ports[0]

  port.onmessage = function (e) {
    if (e.data === 'get') {
      port.postMessage(data)
    } else {
      data = e.data
    }
  }
}
複製程式碼

  • webworker端(暫且這樣稱呼)的程式碼就如上,只需註冊一個onmessage監聽資訊的事件,客戶端(即使用sharedWorker的標籤頁)傳送message時就會觸發。

  • 注意webworker無法在本地使用,出於瀏覽器本身的安全機制,所以我這次的示例也是放在伺服器上的,worker.jsindex.html在同一目錄。

    瀏覽器多個標籤頁之間的通訊

    • 因為客戶端和webworker端的通訊不像websocket那樣是全雙工的,所以客戶端傳送資料和接收資料要分成兩步來處理。示例中會有兩個按鈕,分別對應的向sharedWorker傳送資料的請求以及獲取資料的請求,但他們本質上都是相同的事件--傳送訊息。

    • webworker端會進行判斷,傳遞的資料為'get'時,就把變數data的值回傳給客戶端,其他情況,則把客戶端傳遞過來的資料儲存到data變數中。下面是客戶端的程式碼:

      // 這段程式碼是必須的,開啟頁面後註冊SharedWorker,顯示指定worker.port.start()方法建立與worker間的連線
          if (typeof Worker === "undefined") {
            alert('當前瀏覽器不支援webworker')
          } else {
            let worker = new SharedWorker('worker.js')
            worker.port.addEventListener('message', (e) => {
              console.log('來自worker的資料:', e.data)
            }, false)
            worker.port.start()
            window.worker = worker
          }
      // 獲取和傳送訊息都是呼叫postMessage方法,我這裡約定的是傳遞'get'表示獲取資料。
      window.worker.port.postMessage('get')
      window.worker.port.postMessage('傳送資訊給worker')
      複製程式碼

    • 頁面A傳送資料給worker,然後開啟頁面B,呼叫window.worker.port.postMessage('get'),即可收到頁面A傳送給worker的資料。

  • 相關文章