最近在一個項目中,須要使用到websocket,以前對websocket不是很瞭解,因而就花了一點時間來熟悉websocket。nginx
在瀏覽器與服務器通訊間,傳統的 HTTP 請求在某些場景下並不理想,好比實時聊天、實時性的小遊戲等等,web
其面臨主要兩個缺點:ajax
其基於 HTTP 的主要解決方案有:數組
能夠看到,基於 HTTP 協議的方案都包含一個本質缺陷 —— 「被動性」,服務端沒法下推消息,僅能由客戶端發起請求不斷詢問是否有新的消息,同時對於客戶端與服務端都存在性能消耗。瀏覽器
WebSocket 是 HTML5 開始提供的一種瀏覽器與服務器間進行全雙工通信的網絡技術。 WebSocket 通訊協議於2011年被IETF定爲標準RFC 6455,WebSocketAPI 被 W3C 定爲標準。 在 WebSocket API 中,瀏覽器和服務器只須要要作一個握手的動做,而後,瀏覽器和服務器之間就造成了一條快速通道。二者之間就直接能夠數據互相傳送。
WebSocket 是 HTML5 中提出的新的網絡協議標準,其包含幾個特色:安全
在瀏覽器中使用 Websocket 很是簡單,在支持 Websocket 的瀏覽器中提供了原生的 WebSocekt 對象,其中對於消息的接收與數據幀處理在瀏覽器中已經封裝好了。bash
如下將用一個簡單的例子解釋如何使用 WebSocekt;服務器
瀏覽器中提供了原生類 WebSocket ,使用 new 關鍵字實例化它:websocket
WebSocket WebSocket(String url,optional String | [] protocols);
接收兩個參數:網絡
實例化對象提供兩個方法:
鏈接狀態:
1. WebSocket.CONNECTING 0 鏈接還沒開啓;
2. WebSocket.OPEN 1 鏈接已開啓並準備好進行通訊;
3. WebSocket.CLOSING 3 鏈接正在關閉的過程當中;
4. WebSocket.CLOSED 4 鏈接已經關閉,或者鏈接沒法創建;
實例化對象中能夠監聽到如下事件:
const ws = new WebSocket('ws://localhost:8080'); let sendTimmer = null; let sendCount = 0; ws.onopen = function () { console.log('@open'); sendCount++; ws.send('Hello Server!' + sendCount); sendTimmer = setInterval(function () { sendCount++; ws.send('Hi Server!' + sendCount); if (sendCount === 10) { ws.close(); } }, 2000); }; ws.onmessage = function (e) { console.log('@message'); console.log(e.data); }; ws.onclose = function () { console.log('@close'); sendTimmer && clearInterval(sendTimmer); }; ws.onerror = function () { console.log('@error'); };
控制檯能夠看到
@open @message Hello Client @message received: Hello Server!1(From Server) @message received: Hi Server!2(From Server) @message received: Hi Server!3(From Server) @message received: Hi Server!4(From Server) @message received: Hi Server!5(From Server) @message received: Hi Server!6(From Server) @message received: Hi Server!7(From Server) @message received: Hi Server!8(From Server) @message received: Hi Server!9(From Server) @close
首先觸發 open 事件,以後每次發送數據服務端都會回覆數據,所以觸發了 message 事件,當發送 10 次以後瀏覽器主動斷開鏈接,所以觸發 close 事件;這裏最後一次發送以後未收到服務端回覆也是由於客戶端當即斷開了鏈接;
固然,更具體的數據交互能夠從 network 看到;
對 WebSocket 實例監聽事件有兩種方式,這裏以 message 事件爲例:
ws.onmessage = function () {};
ws.addEventListener('message', function () {});
在 message 回調函數中獲得 MessageEvent 類型參數 e ,咱們須要的數據能夠經過 e.data 獲取;
須要注意的一點是:不論服務端與客戶端,其接受到的數據都是序列化後的字符串(固然也有 ArrayBuffer|Blob 類型數據),不少時候咱們須要解析處理數據,好比 JSON.parse(e.data)
;
因爲網絡環境複雜,某些狀況會出現斷開鏈接或者鏈接出錯,須要咱們在 close 或者 error 事件中監聽非正常斷開並重連;
因爲一些緣由在 error 時瀏覽器並不會響應回調事件,所以穩妥的作法還須要在 open 以後開啓一個定時任務去判斷當前的鏈接狀態 readyState ,在出現異常狀況下嘗試重連;
websocket規範定義了心跳機制,一方能夠經過發送ping(opcode 0x9)消息給另外一方,另外一方收到ping後應該儘量快的返回pong(0xA)。
心跳機制是用於檢測鏈接的對方在線狀態,所以若是沒有心跳,那麼沒法判斷一方還在鏈接狀態中,一些網絡層好比 nginx 或者瀏覽器層會主動斷開鏈接,
在 JavaScript 中,WebSocket 並無開放 ping/pong 的 API ,雖然瀏覽器自帶了心跳處理,然而不一樣廠商的實現也不盡相同,所以須要在咱們開發時候與服務端約定好一個自實現的心跳機制;
好比瀏覽器中,檢測到 open 事件後,啓動一個定時任務,每次發送數據 0x9 給服務端,而服務端返回 0xA 做爲響應;
實踐下來,心跳的定時任務通常是相隔 15-20 秒發送一次。
前文說到,Websocket 是創建與 TCP 之上,那麼其與 HTTP 協議有和關係呢?
Websocket 鏈接分爲建連階段與鏈接階段,在創建鏈接階段藉助於 HTTP ,而在鏈接階段則與 HTTP 無關。
從瀏覽器的 Network 中,找到 ws 鏈接,能夠看到:
General Request URL:ws://localhost:8080/ Request Method:GET Status Code:101 Switching Protocols Response Headers HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: py9bt3HbjicUUmFWJfI0nhGombo= Request Headers GET ws://localhost:8080/ HTTP/1.1 Host: localhost:8080 Connection: Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket Origin: http://localhost:8080 Sec-WebSocket-Version: 13 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36 DNT: 1 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,la;q=0.6,ja;q=0.5 Sec-WebSocket-Key: 2idFk3+96Hs5hh+c9GOQCg== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
這是一個標準的 HTTP 請求,相比於咱們常見的 HTTP 請求協議,請求頭中多了幾個字段:
Connection: Upgrade Upgrade: websocket Sec-WebSocket-Version: 13 Sec-WebSocket-Key: 2idFk3+96Hs5hh+c9GOQCg== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Connection 爲 Upgrade ,Upgrade 爲 websocket ,表示告知 Nginx 與 Apache 等服務器該次鏈接並不是爲 HTTP 鏈接,實質上是一個 websocket ,所以服務器會轉發到相應的 websocket 任務處理;
Sec-WebSocket-Key 是一個 Base64 encode 的值,由瀏覽器隨機生成的,用於驗證服務器鏈接的正確性;
Sec-WebSocket-Versio 表示爲使用的 websocket 服務版本;
響應頭中:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: py9bt3HbjicUUmFWJfI0nhGombo=
能夠看到其返回狀態碼爲 101 ,表示切換協議;
Upgrade 與 Connection 用於回覆客戶端表示已經切換協議成功;
Sec-WebSocket-Accept 字段與 Sec-WebSocket-Key 相對應,用於驗證服務的正確性;
當經過 HTTP 創建鏈接握手後,接下來則是真正的 Websocket 鏈接了,其基於 TCP 收發數據,Websocket 封裝並開放接口。
在 HTTP 協議中,不少時候爲了加密與安全須要使用 HTTPS 請求(HTTP + TCL);
相應的,在 Websocket 協議中,也是可使用加密傳輸的 —— wss ,好比 wss://localhost:8080。
使用的也是與 HTTPS 同樣的證書,在這裏通常是交由 Nginx 等服務層去作證書處理。