JS 服務器推送技術 WebSocket 入門指北

JS 服務器推送技術 WebSocket 入門指北

最近在工作中遇到了需要服務器推送消息的場景,這裡總結一下收集整理WebSocket相關資料的收穫。

1. 概述

1.1 服務器推送

WebSocket作為一種通信協議,屬於服務器推送技術的一種,IE10+支持。

服務器推送技術不止一種,有短輪詢、長輪詢、WebSocket、Server-sent Events(SSE)等,他們各有優缺點:

JS 服務器推送技術 WebSocket 入門指北

短輪詢最簡單,在一些簡單的場景也會經常使用,就是隔一段時間就發起一個ajax請求。那麼長輪詢是什麼呢?

長輪詢(Long Polling)是在Ajax輪詢基礎上做的一些改進,在沒有更新的時候不再返回空響應,而且把連接保持到有更新的時候,客戶端向服務器發送Ajax請求,服務器接到請求後hold住連接,直到有新消息才返回響應信息並關閉連接,客戶端處理完響應信息後再向服務器發送新的請求。它是一個解決方案,但不是最佳的技術方案。

如果說短輪詢是客戶端不斷打電話問服務端有沒有消息,服務端回覆後立刻掛斷,等待下次再打;長輪詢是客戶端一直打電話,服務端接到電話不掛斷,有消息的時候再回復客戶端並掛斷。

SSE(Server-Sent Events)與長輪詢機制類似,區別是每個連接不只發送一個消息。客戶端發送一個請求,服務端保持這個連接直到有新消息發送回客戶端,仍然保持著連接,這樣連接就可以支持消息的再次發送,由服務器單向發送給客戶端。然而IE直到11都不支持,不多說了....

1.2 WebSocket的特點

為什麼已經有了輪詢還要WebSocket呢,是因為短輪詢和長輪詢有個缺陷:通信只能由客戶端發起。

那麼如果後端想往前端推送消息需要前端去輪詢,不斷查詢後端是否有新消息,而輪詢的效率低且浪費資源(必須不停 setInterval 或 setTimeout 去連接,或者 HTTP 連接始終打開),WebSocket提供了一個文明優雅的全雙工通信方案。一般適合於對數據的實時性要求比較強的場景,如通信、股票、直播、共享桌面,特別適合於客戶端與服務頻繁交互的情況下,如聊天室、實時共享、多人協作等平臺。

特點

  1. 建立在 TCP 協議之上,服務器端的實現比較容易。
  2. 與 HTTP 協議有著良好的兼容性。默認端口也是80和443,並且握手階段採用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理服務器。
  3. 數據格式比較輕量,性能開銷小,通信高效。服務器與客戶端之間交換的標頭信息大概只有2字節;
  4. 可以發送文本,也可以發送二進制數據。
  5. 沒有同源限制,客戶端可以與任意服務器通信。
  6. 協議標識符是 ws(如果加密,則為wss),服務器網址就是 URL。ex:ws://example.com:80/some/path
  7. 不用頻繁創建及銷燬TCP請求,減少網絡帶寬資源的佔用,同時也節省服務器資源;
  8. WebSocket是純事件驅動的,一旦連接建立,通過監聽事件可以處理到來的數據和改變的連接狀態,數據都以幀序列的形式傳輸。服務端發送數據後,消息和事件會異步到達。
  9. 無超時處理。

HTTP與WS協議結構

WebSocket協議標識符用 ws表示。`wss協議表示加密的WebSocket協議,對應HTTPs協議。結構如下:

  • HTTP: TCP > HTTP
  • HTTPS: TCP > TLS > HTTP
  • WS: TCP > WS
  • WSS
    : TCP > TLS > WS

2 WebSocket的通信過程

首先,Websocket是一個持久化的協議,相對於HTTP這種非持久的協議來說。

一個HTTP的通信生命週期通過 Request 來界定,也就是一個 Request 一個 Response ,那麼在 HTTP1.0 中,這次HTTP請求就結束了。在HTTP1.1中進行了改進,有了一個 keep-alive,在一個HTTP連接中,可以發送多個Request,接收多個Response,也就是合併多個請求。但是一個Request只能對應一個Response,而且這個Response是被動的,不能主動發起。

Websocket 其實是一個新協議,但是為了兼容現有瀏覽器的握手規範而借用了HTTP的協議來完成一部分握手。

WebSocket是純事件驅動的,一旦連接建立,通過監聽事件可以處理到來的數據和改變的連接狀態,數據都以幀序列的形式傳輸。服務端發送數據後,消息和事件會異步到達。WebSocket編程遵循一個異步編程模型,只需要對WebSocket對象增加回調函數就可以監聽事件。

2.1 WebSocket通信流程圖


JS 服務器推送技術 WebSocket 入門指北


這裡可以看出傳統HTTP通訊與WebSocket通訊的通信流程上的區別,下圖顯示WebSocket主要的三步中瀏覽器和服務器端分別做了哪些事情。

JS 服務器推送技術 WebSocket 入門指北

2.2 建立連接的握手

當Web應用程序調用 newWebSocket(url)接口時,客戶端就開始了與地址為url的WebServer建立握手連接的過程。

  1. 客戶端與服務端通過TCP三次握手建立連接,如果這個建立連接失敗,那麼後面的過程就不會執行,Web應用程序將收到錯誤消息通知。
  2. 在TCP建立連接成功後,客戶端通過HTTP協議傳送WebSocket支持的版本號、協議的字版本號、原始地址、主機地址等等一些列字段給服務器端。
  3. 服務端收到客戶端發送來的握手請求後,如果數據包數據和格式正確、客戶端和服務端的協議版本號匹配等等,就接受本次握手連接,並給出相應的數據回覆,同樣回覆的數據包也是採用HTTP協議傳輸。
  4. 客戶端收到服務端回覆的數據包後,如果數據包內容、格式都沒有問題的話,就表示本次連接成功,觸發 onopen,此時Web開發者就可以在此時通過 send()向服務器發送數據。否則握手連接失敗,Web應用程序觸發 onerror,並且能知道連接失敗的原因。

這個握手很像HTTP,但是實際上卻不是,它允許服務器以HTTP的方式解釋一部分handshake的請求,然後切換為websocket。

2.3 WebSocket握手報文

一個瀏覽器發出的WebSocket請求報文類似於:

JS 服務器推送技術 WebSocket 入門指北

HTTP1.1協議規定,Upgrade頭信息表示將通信協議從HTTP/1.1轉向該項所指定的協議。

  • Connection:Upgrade表示瀏覽器通知服務器,如果可以,就升級到webSocket協議。
  • Origin用於驗證瀏覽器域名是否在服務器許可的範圍內。
  • Sec-WebSocket-Key則是用於握手協議的密鑰,是瀏覽器生成的Base64編碼的16字節隨機字符串。
  • Sec-WebSocket-Protocol是一個用戶定義的字符串,用來區分同URL下,不同的服務所需要的協議。
  • Sec-WebSocket-Version是告訴服務器所使用的協議版本。

服務端WebSocket回覆報文:

JS 服務器推送技術 WebSocket 入門指北

  • 服務器端同樣用 Connection:Upgrade通知瀏覽器,服務端已經成功切換協議。
  • Sec-WebSocket-Accept是經過服務器確認並且加密過後的 Sec-WebSocket-Key。
  • Sec-WebSocket-Location表示進行通信的WebSocket網址。
  • Sec-WebSocket-Protocol表示最終使用的協議。

在這樣一個類似於HTTP通信的握手結束之後,下面就按照WebSocket協議進行通信了。客戶端與服務器之間不會再發生HTTP通信,一切由WebSocket 協議接管。

3. WebSocket API

瀏覽器提供了一個WebSocket對象的實現,可以用這個對象來創建和管理WebSocket連接,並且可以通過該連接發送和接受數據。WebSocket是事件驅動的,因此只需要對WebSocket對象增加回調函數就可以監聽事件的發生。

跟XMLHttpRequest一樣,通過該構造函數先new出來對象實例 constws=newWebSocket('ws://localhost:8080'),再使用對象下掛載的屬性與方法來操作。後文都用ws來指代WebSocket的實例。

查看DEMO

3.1 ws上常用屬性

ws.readyState

WebSocket實例對象類似於XHR有個的只讀屬性 readyState來指示連接的當前狀態:

JS 服務器推送技術 WebSocket 入門指北

一個示例:

JS 服務器推送技術 WebSocket 入門指北

ws.onopen / ws.onclose

實例對象的 onopen屬性,用於指定連接成功後的回調函數。

JS 服務器推送技術 WebSocket 入門指北

如果要指定多個回調函數,可以 addEventListener。

JS 服務器推送技術 WebSocket 入門指北

實例對象的 onclose屬性,用於指定連接關閉後的回調函數。

JS 服務器推送技術 WebSocket 入門指北

ws.onmessage

實例對象的 onmessage屬性,用於指定收到服務器數據後的回調函數。

JS 服務器推送技術 WebSocket 入門指北

注意,服務器數據可能是文本,也可能是二進制數據(blob對象或Arraybuffer對象)。

JS 服務器推送技術 WebSocket 入門指北

除了動態判斷收到的數據類型,也可以使用 binaryType屬性,顯式指定收到的二進制數據類型。binaryType取值應當是'blob'或者'arraybuffer','blob'表示使用 Blob 對象,而'arraybuffer'表示使用 ArrayBuffer 對象。

JS 服務器推送技術 WebSocket 入門指北

查看DEMO

ws.bufferedAmount

實例對象的 bufferedAmount只讀屬性,表示還有多少字節的二進制數據沒有發送出去。它可以用來判斷髮送是否結束。該值會在所有隊列數據被髮送後重置為 0,而當連接關閉時不會設為0。如果持續調用send(),這個值會持續增長。

JS 服務器推送技術 WebSocket 入門指北

ws.onerror

實例對象的 onerror屬性,用於指定報錯時的回調函數。

JS 服務器推送技術 WebSocket 入門指北

3.2 ws上常用方法

ws.close()

關閉WebSocket連接或停止正在進行的連接請求。如果連接的狀態已經是closed,這個方法不會有任何效果。

ws.send()

實例對象的 send()方法用於向服務器發送數據。

JS 服務器推送技術 WebSocket 入門指北

最後一個ArrayBuffer對象栗子中的canvas_context實例是CanvasRenderingContext2D類型的對象,其上的 .getImageData()方法返回一個ImageData對象。

網上的帖子大多深淺不一,甚至有些前後矛盾,在下的文章都是學習過程中的總結,如果發現錯誤,歡迎留言指出~

文章轉至公眾號:前端下午茶

喜歡文章的記得點個關注不迷路~

私信+轉發回覆關

JS 服務器推送技術 WebSocket 入門指北

鍵字(資料)送你一份前端面試題


分享到:


相關文章: