HTML5的Websocket(理論篇 I)
* 先請來TA的鄰居:*
http:無狀態、基於tcp請求/響應模式的應用層協議 (A:哎呀,上次你請我吃飯了么? B:我想想, 上次請你吃了么)tcp:面向連接、保證高可靠性(數據無丟失、數據無失序、數據無錯誤、數據無重複到達) 傳輸層協議。(看啊,大閱兵,如此規整有秩序)
為什麼要引入Websocket:
RFC開篇介紹:本協議的目的是為了解決基於瀏覽器的程序需要拉取資源時必須發起多個HTTP請求和長時間的輪詢的問題。
long poll(長輪詢): 客戶端發送一個request後,伺服器拿到這個連接,如果有消息,才返回response給客戶端。沒有消息,就一直不返回response。之後客戶端再次發送request, 重複上次的動作。
從上可以看出,http協議的特點是伺服器不能主動聯繫客戶端,只能由客戶端發起。它的被動性預示了在完成雙向通信時需要不停的連接或連接一直打開,這就需要伺服器快速的處理速度或高並發的能力,是非常消耗資源的。
這個時候,Websocket出現了。
Websocket是什麼:
RFC中寫到:WebSocket協議使在控制環境下運行不受信任代碼的客戶端和能夠選擇與那些代碼通信的遠程主機之間能夠雙向通信。
對,劃重點:雙向通信
Websocket在連接之後,客戶端可以主動發送消息給伺服器,伺服器也可以主動向客戶端推送消息。比如:預訂車票信息,除了我們發請求詢問車票如何,當然更希望如果有新消息,可以直接通知我們。
其特點:
(1)握手階段採用 HTTP 協議,默認埠是80和443
(2)建立在TCP協議基礎之上,和http協議同屬於應用層
(4)可以發送文本,也可以發送二進位數據
(5)沒有同源限制,客戶端可以與任意伺服器通信
(6)協議標識符是ws(如果加密,為wss),如ws://localhost:8023
簡單來說,Websocket協議分為兩部分:握手和數據傳輸。
Websocket API:
這裡是指客戶端 API。
WebSocket 構造函數
通過調用WebSocket構造函數來創建一個WebSocket實例對象,建立客戶端與伺服器的連接。n
const ws = new WebSocket(ws://localhost:8023);n
Websocket事件
WebSocket 是純事件驅動,通過監聽事件可以處理到來的數據和改變的連接狀態。服務端發送數據後,消息和事件會非同步到達。n
open:
服務端響應WebSocket連接請求,就會觸發open事件。onopen是響應的回調函數。
// 連接請求open事件處理:nws.onopen=e=>{n console.log(Connection success);n ws.send(`Hello ${e}`);n};n
如果要指定多個回調函數,可以使用addEventListener方法。
ws.addEventListener(open, e => {n ws.send(`Hello ${e}`);n});n
當open事件觸發時,意味著握手階段已結束。服務端已經處理了連接的請求,可以準備收發數據。
Message:
收到伺服器數據,會觸發消息事件,onmessage是響應的回調函數。如下:
// 接受文本消息的事件處理:nws.onmessage = e => {nconst data = e.data;nif (typeof data === "string") {n console.log("Received string message ",data);n} else if (data instanceof Blob) {n console.log("Received blob message ", data);n}n};n
伺服器數據可能是文本,也可能是二進位數據,有Blob和ArrayBuffer兩種類型,在讀取到數據之前需要決定好數據的類型。
Error
發生錯誤會觸發error事件, onerror是響應的回調函數, 會導致連接關閉。
//異常處理nws.onerror = e => {n console.log("WebSocket Error: " , e);n handleErrors(e);n};n
Close
當連接關閉時觸發close事件,對應onclose方法,連接關閉之後,服務端和客戶端就不能再通信。
WebSocket 規範中定義了ping 幀 和pong 幀,可以用來做心跳重連,網路狀態查詢等,但是目前 瀏覽器只會自動發送pong幀,而不會發ping 幀。(有興趣可詳查ping和pong幀)
//關閉連接處理nws.onclose = e => {nconst code = e.code;nconst reason = e.reason;n console.log("Connection close", code, reason);n};n
WebSocket 方法:
WebSocket 對象有兩個方法:send 和 close
send:
客戶端和伺服器建立連接後,可以調用send方法去發送消息。
//發送一個文本消息nws.send("this is websocket");n
在open事件的回調中調用send()方法傳送數據:
const ws = new WebSocket(ws://localhost:8023);nws.onopen = e => {n console.log(Connection success);n ws.send(`Hello ${e}`);n};n
如果想通過響應其他事件發送消息,可通過判斷當前的Websocket的readyState屬性。接下來會說到readyState.
close
close方法用來關閉連接。調用close方法後,將不能發送數據。close方法可以傳入兩個可選的參數,code 和reason, 以告訴服務端為什麼終止連接。
ws.close();nn//1000是狀態碼,代表正常結束。nws.close(1000, "Closing normally");n
WebSocket 屬性
- readyState:
readyState值表示連接狀態,是只讀屬性。它有以下四個值:
WebSocket.CONNECTING :連接正在進行,但還沒有建立 WebSocket.OPEN :連接已經建立,可以發送消息 WebSocket.CLOSING :連接正在進行關閉握手 WebSocket.CLOSED :連接已經關閉或不能打開
除了在open事件回調中調用send方法,可通過判斷readyState值來發送消息。
function bindEventHandler(data) {nif (ws.readyState === WebSocket.OPEN) {n ws.send(data);n} else {n//do somethingn}n}n
bufferedAmount:
當客戶端傳輸大量數據時,瀏覽器會緩存將要流出的數據,bufferedAmount屬性可判斷有多少位元組的二進位數據沒有發送出去,發送是否結束。
ws.onopen = function () {n setInterval( function() {n//緩存未滿的時候發送nif (ws.bufferedAmount < 1024 * 5) {n ws.send(data);n}n}, 2000);n};n
protocol:
protocol代表客戶端使用的WebSocket協議。當握手協議未成功,這個屬性是空。
* 接下來,我們說說握手階段過程。*
當我們創建Websocket實例對象與伺服器建立連接時,
const ws = new WebSocket(ws://localhost:8023);n
首先客戶端向伺服器發起一個握手請求,其請求報文的內容如下:
GET /game HTTP/1.1nHost: 10.242.17.102:8023nCache-Control: no-cachenUpgrade: websocketnConnection: UpgradenSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==nSec-WebSocket-Protocol: gamenSec-WebSocket-Version: 10nOrigin: http://192.168.185.16nAccept-Encoding: gzip, deflate, sdchnAccept-Language: zh-CN,zh;q=0.8n
從請求頭中可以看出,其實是一個基於http的握手請求。與通常的http請求不同的是,增加了一些頭信息。
- Upgrade欄位: 通知伺服器,現在要使用一個升級版協議 - Websocket。
- Sec-WebSocket-Key: 是一個Base64編碼的值,這個是瀏覽器隨機生成,通知伺服器,需要驗證下是否可以進行Websocket通信
- Sec_WebSocket-Protocol: 是用戶自定義的字元串,用來標識服務所需要的協議
- Sec-WebSocket-Version: 通知伺服器所使用的協議版本
伺服器響應:
當伺服器返回以下內容,就表示已經接受客戶端請求啦,可以建立Websocket通信啦。nHTTP/1.1 101 Switching ProtocolsnUpgrade: websocketnConnection: UpgradenSec-WebSocket-Accept: SIEylb7zRYJAEgiqJXaOW3V+ZWQ=n
- 101 狀態碼,表示要轉換協議啦
- Upgrde: 通知客戶端將要升級成Websocket協議
- Sec-WebSocket-Accept: 經過伺服器確認,並且加密過後的 Sec-WebSocket-Key。用來證明客戶端和伺服器之間能進行通信了。
至此,客戶端和伺服器握手成功建立了Websocket連接,通信不再使用http數據幀,而採用Websocket獨立的數據幀。
以上是Websocket協議的基礎理論篇I, 歡迎小夥伴兒們接力(理論篇II, 實戰篇神馬的), 一起學習一起積累~
推薦閱讀:
※談談 HTTP 緩存
※APP精細化HTTP分析(二):響應性能分析與優化
※請正確使用http狀態碼,謝謝!
※HTTP伺服器的本質:tinyhttpd源碼分析及拓展