WebSocket協議解析

WebSocket協議解析

轉載請註明出處:WebSocket解析php

如今,不少網站爲了實現推送技術,所用的技術都是輪詢。輪詢是指在特定的時間間隔(如每一秒),由瀏覽器對服務器發起HTTP請求,而後由服務器返回數據給瀏覽器
。因爲HTTP協議是惰性的,只有客戶端發起請求,服務器纔會返回數據。輪詢技術實現的前提條件一樣是基於這種機制。而WebSocket屬於服務端推送技術,本質是一種應用層協議,能夠實現持久鏈接的全雙工雙向通訊。在介紹WebSocket以前,先談談輪詢技術和HTTP流技術。html

文章目錄html5

  • 傳統輪詢技術:Ajax短輪詢
  • Comet
    • Ajax長輪詢
    • HTTP流
  • HTML5實現服務端推送
    • SSE
    • WebSocket

Ajax短輪詢(Ajax Polling)

Ajax短輪詢即客戶端週期性的向服務器發起HTTP請求,無論服務器是否真正獲取到數據,都會向客戶端返回響應。每一個request對應一個response,因爲HTTP/1.1的持久鏈接(創建一次TCP鏈接,發送多個請求)和管線化技術(異步發送請求),使得HTTP請求能夠在創建一次TCP鏈接以後發起多個異步請求。android

這種傳統的模式帶來很明顯的缺點,即瀏覽器須要不斷的向服務器發出請求,然而HTTP請求在每次發送時都會帶上很長的請求頭部字段,其中真正有效的數據可能只是很小的一部分(如Cookie字段),顯然服務器會浪費帶寬等資源。ios

有朋友可能會想,那能夠加大Ajax的傳輸時間,如改成3s爲一個週期。可是時間長了,對於實時性要求比較高的項目來講,頁面更新的數據也就太慢了。web

Comet(服務端推送)

而比較新的技術向服務器輪詢獲取數據的實現是Comet,即服務端推送。簡單的說,服務端推送就是在客戶端發起HTTP請求以後,服務器能夠主動的向客戶端推送數據。實現Comet的方式有兩種:Ajax長輪詢和HTTP流。ajax

Ajax長輪詢(Ajax Long-polling)

Ajax長輪詢自己不是一個真正的推送。長輪詢是短輪詢的一種變體。在客戶端向服務器發起HTTP請求以後,服務器並非每次都當即響應:當服務器獲得最新數據時,會向客戶端傳輸數據;當數據沒有更新時,服務器會保持這個鏈接,等待更新數據以後,才向客戶端傳輸數據。固然,若是服務端數據長時間沒有更新,一段時間後,請求就會超時。客戶端收到超時信息後,會從新發送一個HTTP請求給服務器。瀏覽器

也就是說,只有在服務器獲取更新後的數據,纔會向客戶端傳輸數據。這種方式也存在弊端。雖然服務端能夠主動的向客戶端傳輸數據,可是依然須要反覆發出請求(HTTP請求數量比短輪詢少不少)。服務器

短輪詢和長輪詢的相同點在於客戶端都須要向服務器發起HTTP請求,不一樣點在於服務器如何響應:短輪詢是服務器當即響應,無論數據是否有效;長輪詢是等待數據更新後響應。websocket

HTTP流

HTTP流不一樣於輪詢技術,HTTP流只創建一次TCP鏈接,在3次握手以後進行HTTP通訊,此時客戶端向服務器發起一個HTTP請求,而服務器保持鏈接打開,週期性的向客戶端傳輸數據。雙方在沒有明確提出斷開鏈接時,服務器就會持續向客戶端傳輸數據。也就是說,假如服務器數據沒有更新,服務器不會返回響應,而是保持鏈接;若是數據更新了,會當即將數據傳輸給客戶端。此時會發起下一個HTTP請求,過程周而復始。

在JS中,能夠經過偵聽readystatechange事件及檢測readyState的值是否爲3來實現HTTP流。隨着不斷從服務器接收數據,readyState的值會週期性的變爲3。當readyState值變爲3時,responseText屬性就會保存接受到的全部數據。此時,就須要比較此前接收到的數據,決定從什麼位置開始取得最新的數據。用XHR對象實現HTTP流的方式以下:

let httpStream = (url, processor, finished) => {
  let xhr = new XMLHttpRequest()
  let received = 0
  xhr.open(url, 'get', true)
  xhr.addEvetntListener('readystatechange', () => {
    let result
    if (xhr.readyState === 3) {
      result = xhr.responseText.slice(received)
      received += result.length
      processor(result)
    } else if (xhr.readyState === 4) {
      finished(xhr.responseText)
    }
  })
}

只要readyState爲3,就對responseText進行分隔以獲取最新數據。這裏的received表示記錄已經處理了多少字符。而後經過processor回調函數來處理最新數據。而當readyState爲4時,表示數據已經徹底獲取到,則直接將xhr.responseText傳入finished回調函數處理便可。

調用方式以下.

httpStream(url, data => {
  console.log(data)
}, finishedData => {
  console.log(data)
})

對(長、短)輪詢和HTTP流作一個小小的總結

  1. 傳統輪詢技術(Ajax短輪詢)是客戶端向服務器發起HTTP請求,不管數據是否更新,服務器都會傳輸數據。一個request對應一個response。
  2. 服務器推送技術(Ajax長輪詢)是短輪詢的變種,是客戶端向服務器發起HTTP請求,只有等待數據更新後纔會傳輸數據,不然服務器保持鏈接狀態。接着發起下一次HTTP請求,一個request對應一個response。
  3. 服務器推送技術(HTTP流),在客戶端只發起一次HTTP請求,服務器保持鏈接狀態,在數據更新以後,服務器會傳輸數據,不然保持鏈接狀態。此時一個requset對應多個response。
  4. 不管是短輪詢、長輪詢,仍是HTTP流,相同點在於都須要客戶端先發起HTTP請求

HTML5實現服務端推送

因爲服務器推送的重要性(實現賽事結果更新、聊天室等),HTML5實現了兩個服務端推送接口,SSE和WebSocket。

SSE

SSE(Server-Sent Eevents,服務器發送事件)用於建立到服務器的單向鏈接,服務器經過這個鏈接能夠發送任意數量的數據。實現SSE有如下幾點要求

  1. 服務器響應的MIME類型必須是text/event-stream。
  2. 必須按照指定的格式輸出。

用法以下,其實理解了服務器推送以後,SSE使用起來相對簡單

// EventSource接受的參數必須同源。
// 使用message事件監遵從服務器收到的消息,並存儲在event.data對象裏。
let source = new EventSource('index.php')
source.onmessage = e => {
  console.log(e.data)
}

SSE在IE下都不支持,ios4.0以上、android4.4以上都支持SSE。

WebSocket

鋪墊了那麼久的前文,終於到WebSocket了... :) 感謝各位朋友不嫌棄的耐心閱讀。

簡單來講,WebSocket是一種協議,與HTTP協議同樣位於應用層,都是TCP/IP協議的子集。HTTP協議是單向通訊協議,只有客戶端發起HTTP請求,服務端纔會返回數據。而WebSocket協議是雙向通訊協議,在創建鏈接以後,客戶端和服務器均可以主動向對方發送或接受數據。WebSocket協議創建的前提須要藉助HTTP協議,創建鏈接以後,持久鏈接的雙向通訊就與HTTP協議無關了。

WebSocket協議的目標是在一個獨立的持久鏈接上提供全雙工雙向通訊。客戶端和服務器能夠向對方主動發送和接受數據。在JS中建立WebSocket後,會有一個HTTP請求發向瀏覽器以發起請求。在取得服務器響應後,創建的鏈接會使用HTTP升級將HTTP協議轉換爲WebSocket協議。也就是說,使用標準的HTTP協議沒法實現WebSocket,只有支持那些協議的專門瀏覽器才能正常工做。

請認真閱讀、記住上面一段話。: )

因爲WebScoket使用了自定義協議,因此URL與HTTP協議略有不一樣。未加密的鏈接爲ws://,而不是http://。加密的鏈接爲wss://,而不是https://。

使用JavaScript是實現WebScoket協議相對簡單,如下是WebSocket APIs

// 打開WebSocket, 傳遞的參數url沒有同源策略的限制。
let websocket = new WebSocket(url)

// 監聽open事件,在成功創建websocket時向url發送純文本字符串數據(若是是對象則必須序列化處理)。
websocket.onopen = () => {
  if (websocket.readyState === WebSocket.OPEN) {
    websocket.send('hello world')
  }
}

// 監聽message事件,在服務器響應時接受數據。返回的數據存儲在事件對象中。
websocket.onmessage = e => {
  let data = e.data
  console.log(data)
}

// 監聽error事件,在發生錯誤時觸發,鏈接不能持續。
websocket.onerror = () => {
  console.log('websocket connecting error!!')
}

// 監聽close事件,在鏈接關閉時觸發。只有close事件的事件對象擁有額外的信息。能夠經過這些信息來查看關閉狀態
websocket.onclose = e => {
  let clean = e.wasClean // 是否已經關閉
  let code = e.code // 服務器返回的數值狀態碼。
  let reason = e.reason //服務器返回的消息。
}

注意,WebScoket不支持DOM2語法爲事件綁定事件處理程序,所以必須使用DOM0級語法來每一個事件綁定事件處理程序。

// correct!
websocket.onerror = () => {}
// error!
websocket.addEventListener('error', () => {})

看完了WebSocket APIs以後,再來看看WebSocket是如何實現鏈接的(奶思,看到這裏的朋友耐心真棒.. 只剩下一點點了:) )

WebSocket是應用層協議,是TCP/IP協議的子集,經過HTTP/1.1協議的101狀態碼進行握手。也就是說,WebSocket協議的創建須要先借助HTTP協議,在服務器返回101狀態碼以後,就能夠進行websocket全雙工雙向通訊了,就沒有HTTP協議什麼事情了

參照wiki握手協議的例子:並對一些字段進行說明。

Connection:Connection必須設置爲Upgrade,表示客戶端但願鏈接升級

Upgrade:Upgrade必須設置爲WebSocket,表示在取得服務器響應以後,使用HTTP升級將HTTP協議轉換(升級)爲WebSocket協議。

Sec-WebSocket-key:隨機字符串,用於驗證協議是否爲WebSocket協議而非HTTP協議

Sec-WebSocket-Version:表示使用WebSocket的哪個版本。

Sec-WebSocket-Accept:根據Sec-WebSocket-Accept和特殊字符串計算。驗證協議是否爲WebSocket協議。

Sec-WebSocket-Location:與Host字段對應,表示請求WebSocket協議的地址。

HTTP/1.1 101 Switching Protocols:101狀態碼錶示升級協議,在返回101狀態碼後,HTTP協議完成工做,轉換爲WebSocket協議。此時就能夠進行全雙工雙向通訊了。

WebSocket協議的瀏覽器兼容性較好。

參考資料:

1.《JavaScript高級程序設計 第三版》

2.wiki: WebSocket

3.wiki 服務端推送技術

4.WebSocket 教程

5.WebSocket 與 Socket.IO

6.WebSocket 是什麼原理?爲何能夠實現持久鏈接?

相關文章
相關標籤/搜索