轉載請註明出處:WebSocket解析php
如今,不少網站爲了實現推送技術,所用的技術都是輪詢。輪詢是指在特定的時間間隔(如每一秒),由瀏覽器對服務器發起HTTP請求,而後由服務器返回數據給瀏覽器
。因爲HTTP協議是惰性的,只有客戶端發起請求,服務器纔會返回數據。輪詢技術實現的前提條件一樣是基於這種機制。而WebSocket屬於服務端推送技術,本質是一種應用層協議,能夠實現持久鏈接的全雙工雙向通訊。在介紹WebSocket以前,先談談輪詢技術和HTTP流技術。html
文章目錄html5
Ajax短輪詢即客戶端週期性的向服務器發起HTTP請求,無論服務器是否真正獲取到數據,都會向客戶端返回響應。每一個request對應一個response,因爲HTTP/1.1的持久鏈接(創建一次TCP鏈接,發送多個請求)和管線化技術(異步發送請求),使得HTTP請求能夠在創建一次TCP鏈接以後發起多個異步請求。android
這種傳統的模式帶來很明顯的缺點,即瀏覽器須要不斷的向服務器發出請求,然而HTTP請求在每次發送時都會帶上很長的請求頭部字段,其中真正有效的數據可能只是很小的一部分(如Cookie字段),顯然服務器會浪費帶寬等資源。ios
有朋友可能會想,那能夠加大Ajax的傳輸時間,如改成3s爲一個週期。可是時間長了,對於實時性要求比較高的項目來講,頁面更新的數據也就太慢了。web
而比較新的技術向服務器輪詢獲取數據的實現是Comet,即服務端推送。簡單的說,服務端推送就是在客戶端發起HTTP請求以後,服務器能夠主動的向客戶端推送數據。實現Comet的方式有兩種:Ajax長輪詢和HTTP流。ajax
Ajax長輪詢自己不是一個真正的推送。長輪詢是短輪詢的一種變體。在客戶端向服務器發起HTTP請求以後,服務器並非每次都當即響應:當服務器獲得最新數據時,會向客戶端傳輸數據;當數據沒有更新時,服務器會保持這個鏈接,等待更新數據以後,才向客戶端傳輸數據。固然,若是服務端數據長時間沒有更新,一段時間後,請求就會超時。客戶端收到超時信息後,會從新發送一個HTTP請求給服務器。瀏覽器
也就是說,只有在服務器獲取更新後的數據,纔會向客戶端傳輸數據。這種方式也存在弊端。雖然服務端能夠主動的向客戶端傳輸數據,可是依然須要反覆發出請求(HTTP請求數量比短輪詢少不少)。服務器
短輪詢和長輪詢的相同點在於客戶端都須要向服務器發起HTTP請求,不一樣點在於服務器如何響應:短輪詢是服務器當即響應,無論數據是否有效;長輪詢是等待數據更新後響應。websocket
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流作一個小小的總結
因爲服務器推送的重要性(實現賽事結果更新、聊天室等),HTML5實現了兩個服務端推送接口,SSE和WebSocket。
SSE(Server-Sent Eevents,服務器發送事件)用於建立到服務器的單向鏈接,服務器經過這個鏈接能夠發送任意數量的數據。實現SSE有如下幾點要求
用法以下,其實理解了服務器推送以後,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是一種協議,與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高級程序設計 第三版》