更多文章,請在 Github blog查看
構建網絡應用的過程當中,咱們常常須要與服務器進行持續的通信以保持雙方信息的同步。一般這種持久通信在不刷新頁面的狀況下進行,消耗必定的內存資源常駐後臺,而且對於用戶不可見。在 WebSocket 出現以前,咱們有如下解決方案:javascript
當前Web應用中較常見的一種持續通訊方式,一般採起 setInterval
或者 setTimeout
實現。例如若是咱們想要定時獲取並刷新頁面上的數據,能夠結合Ajax寫出以下實現:php
setInterval(function() { $.get("/path/to/server", function(data, status) { console.log(data); }); }, 10000);
上面的程序會每隔10秒向服務器請求一次數據,並在數據到達後存儲。這個實現方法一般能夠知足簡單的需求,然而同時也存在着很大的缺陷:在網絡狀況不穩定的狀況下,服務器從接收請求、發送請求到客戶端接收請求的總時間有可能超過10秒,而請求是以10秒間隔發送的,這樣會致使接收的數據到達前後順序與發送順序不一致。因而出現了採用 setTimeout
的輪詢方式:html
function poll() { setTimeout(function() { $.get("/path/to/server", function(data, status) { console.log(data); // 發起下一次請求 poll(); }); }, 10000); }
程序首先設置10秒後發起請求,當數據返回後再隔10秒發起第二次請求,以此類推。這樣的話雖然沒法保證兩次請求之間的時間間隔爲固定值,可是能夠保證到達數據的順序。java
上面兩種傳統的輪詢方式都存在一個嚴重缺陷:程序在每次請求時都會新建一個HTTP請求,然而並非每次都能返回所需的新數據。當同時發起的請求達到必定數目時,會對服務器形成較大負擔。這時咱們能夠採用長輪詢方式解決這個問題。git
長輪詢與如下將要提到的服務器發送事件和WebSocket不能僅僅依靠客戶端JavaScript實現,咱們同時須要服務器支持並實現相應的技術。
長輪詢的基本思想是在每次客戶端發出請求後,服務器檢查上次返回的數據與這次請求時的數據之間是否有更新,若是有更新則返回新數據並結束這次鏈接,不然服務器 hold 住這次鏈接,直到有新數據時再返回相應。而這種長時間的保持鏈接能夠經過設置一個較大的
HTTP timeout` 實現。下面是一個簡單的長鏈接示例:github
服務器(PHP):web
<?php // 示例數據爲data.txt $filename= dirname(__FILE__)."/data.txt"; // 從請求參數中獲取上次請求到的數據的時間戳 $lastmodif = isset( $_GET["timestamp"])? $_GET["timestamp"]: 0 ; // 將文件的最後一次修改時間做爲當前數據的時間戳 $currentmodif = filemtime($filename); // 當上次請求到的數據的時間戳*不舊於*當前文件的時間戳,使用循環"hold"住當前鏈接,並不斷獲取文件的修改時間 while ($currentmodif <= $lastmodif) { // 每次刷新文件信息的時間間隔爲10秒 usleep(10000); // 清除文件信息緩存,保證每次獲取的修改時間都是最新的修改時間 clearstatcache(); $currentmodif = filemtime($filename); } // 返回數據和最新的時間戳,結束這次鏈接 $response = array(); $response["msg"] =Date("h:i:s")." ".file_get_contents($filename); $response["timestamp"]= $currentmodif; echo json_encode($response); ?>
客戶端:ajax
function longPoll (timestamp) { var _timestamp; $.get("/path/to/server?timestamp=" + timestamp) .done(function(res) { try { var data = JSON.parse(res); console.log(data.msg); _timestamp = data.timestamp; } catch (e) {} }) .always(function() { setTimeout(function() { longPoll(_timestamp || Date.now()/1000); }, 10000); }); }
長輪詢能夠有效地解決傳統輪詢帶來的帶寬浪費,可是每次鏈接的保持是以消耗服務器資源爲代價的。尤爲對於Apache+PHP 服務器,因爲有默認的 worker threads
數目的限制,當長鏈接較多時,服務器便沒法對新請求進行相應。算法
服務器發送事件(如下簡稱SSE)
是HTML 5規範的一個組成部分,能夠實現服務器到客戶端的單向數據通訊。經過 SSE ,客戶端能夠自動獲取數據更新,而不用重複發送HTTP請求。一旦鏈接創建,「事件」便會自動被推送到客戶端。服務器端SSE經過 事件流(Event Stream)
的格式產生並推送事件。事件流對應的 MIME類型 爲 text/event-stream
,包含四個字段:event、data、id和retry。event表示事件類型,data表示消息內容,id用於設置客戶端 EventSource
對象的 last event ID string
內部屬性,retry指定了從新鏈接的時間。json
服務器(PHP):
<?php header("Content-Type: text/event-stream"); header("Cache-Control: no-cache"); // 每隔1秒發送一次服務器的當前時間 while (1) { $time = date("r"); echo "event: ping\n"; echo "data: The server time is: {$time}\n\n"; ob_flush(); flush(); sleep(1); } ?>
客戶端中,SSE藉由 EventSource
對象實現。EventSource
包含五個外部屬性:onerror, onmessage, onopen, readyState、url,以及兩個內部屬性:reconnection time
與 last event ID string
。在onerror屬性中咱們能夠對錯誤捕獲和處理,而 onmessage
則對應着服務器事件的接收和處理。另外也可使用 addEventListener
方法來監聽服務器發送事件,根據event字段區分處理。
客戶端:
var eventSource = new EventSource("/path/to/server"); eventSource.onmessage = function (e) { console.log(e.event, e.data); } // 或者 eventSource.addEventListener("ping", function(e) { console.log(e.event, e.data); }, false);
SSE相較於輪詢具備較好的實時性,使用方法也很是簡便。然而SSE只支持服務器到客戶端單向的事件推送,並且全部版本的IE(包括到目前爲止的Microsoft Edge)都不支持SSE。若是須要強行支持IE和部分移動端瀏覽器,能夠嘗試 EventSource Polyfill
(本質上仍然是輪詢)。SSE的瀏覽器支持狀況以下圖所示:
>>>>>>>>>>>> | 傳統輪詢 | 長輪詢 | 服務器發送事件 | WebSocket |
---|---|---|---|---|
瀏覽器支持 | 幾乎全部現代瀏覽器 | 幾乎全部現代瀏覽器 | Firefox 6+ Chrome 6+ Safari 5+ Opera 10.1+ | IE 10+ Edge Firefox 4+ Chrome 4+ Safari 5+ Opera 11.5+ |
服務器負載 | 較少的CPU資源,較多的內存資源和帶寬資源 | 與傳統輪詢類似,可是佔用帶寬較少 | 與長輪詢類似,除非每次發送請求後服務器不須要斷開鏈接 | 無需循環等待(長輪詢),CPU和內存資源不以客戶端數量衡量,而是以客戶端事件數衡量。四種方式裏性能最佳。 |
客戶端負載 | 佔用較多的內存資源與請求數。 | 與傳統輪詢類似。 | 瀏覽器中原生實現,佔用資源很小。 | 同Server-Sent Event。 |
延遲 | 非實時,延遲取決於請求間隔。 | 同傳統輪詢。 | 非實時,默認3秒延遲,延遲可自定義。 | 實時。 |
實現複雜度 | 很是簡單。 | 須要服務器配合,客戶端實現很是簡單。 | 須要服務器配合,而客戶端實現甚至比前兩種更簡單。 | 須要Socket程序實現和額外端口,客戶端實現簡單。 |
WebSocket 協議在2008年誕生,2011年成爲國際標準。全部瀏覽器都已經支持了。
WebSocket一樣是HTML 5規範的組成部分之一,現標準版本爲 RFC 6455。WebSocket 相較於上述幾種鏈接方式,實現原理較爲複雜,用一句話歸納就是:客戶端向 WebSocket 服務器通知(notify)一個帶有全部 接收者ID(recipients IDs)
的事件(event),服務器接收後當即通知全部活躍的(active)客戶端,只有ID在接收者ID序列中的客戶端纔會處理這個事件。因爲 WebSocket 自己是基於TCP協議的,因此在服務器端咱們能夠採用構建 TCP Socket 服務器的方式來構建 WebSocket 服務器。
這個 WebSocket 是一種全新的協議。它將 TCP 的 Socket(套接字)
應用在了web page上,從而使通訊雙方創建起一個保持在活動狀態鏈接通道,而且屬於全雙工(雙方同時進行雙向通訊)。
實際上是這樣的,WebSocket 協議是借用 HTTP協議 的 101 switch protocol 來達到協議轉換的,從HTTP協議切換成WebSocket通訊協議。
它的最大特色就是,服務器能夠主動向客戶端推送信息,客戶端也能夠主動向服務器發送信息,是真正的雙向平等對話,屬於服務器推送技術的一種。其餘特色包括:
WebSocket協議被設計來取代現有的使用HTTP做爲傳輸層的雙向通訊技術,並受益於現有的基礎設施(代理、過濾、身份驗證)。
本協議有兩部分:握手和數據傳輸。
來自客戶端的握手看起來像以下形式:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
來自服務器的握手看起來像以下形式:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat
來自客戶端的首行遵守 Request-Line 格式。 來自服務器的首行遵守 Status-Line 格式。
Request-Line 和 Status-Line 製品定義在 RFC2616。
一旦客戶端和服務器都發送了它們的握手,且若是握手成功,接着開始數據傳輸部分。 這是一個每一端均可以的雙向通訊信道,彼此獨立,隨意發生數據。
一個成功握手以後,客戶端和服務器來回地傳輸數據,在本規範中提到的概念單位爲「消息」。 在線路上,一個消息是由一個或多個幀的組成。 WebSocket 的消息並不必定對應於一個特定的網絡層幀,能夠做爲一個能夠被一箇中間件合併或分解的片斷消息。
一個幀有一個相應的類型。 屬於相同消息的每一幀包含相同類型的數據。 從廣義上講,有文本數據類型(它被解釋爲 UTF-8 RFC3629文本)、二進制數據類型(它的解釋是留給應用)、和控制幀類型(它是不許備包含用於應用的數據,而是協議級的信號,例如應關閉鏈接的信號)。這個版本的協議定義了六個幀類型並保留10以備未來使用。
首先,客戶端發起協議升級請求。能夠看到,採用的是標準的 HTTP 報文格式,且只支持GET方法。
GET / HTTP/1.1 Host: localhost:8080 Origin: http://127.0.0.1:3000 Connection: Upgrade Upgrade: websocket Sec-WebSocket-Version: 13 Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==
重點請求首部意義以下:
Sec-WebSocket-Versionheader
,裏面包含服務端支持的版本號。Sec-WebSocket-Accept
是配套的,提供基本的防禦,好比惡意的鏈接,或者無心的鏈接。服務端返回內容以下,狀態代碼101表示協議切換。到此完成協議升級,後續的數據交互都按照新的協議來。
HTTP/1.1 101 Switching Protocols Connection:Upgrade Upgrade: websocket Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=
Sec-WebSocket-Accept
根據客戶端請求首部的 Sec-WebSocket-Key
計算出來。
計算公式爲:
將 Sec-WebSocket-Key
跟 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
拼接。
經過 SHA1 計算出摘要,並轉成 base64 字符串。
僞代碼以下:
>toBase64( sha1( Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ) )
WebSocket 客戶端、服務端通訊的最小單位是 幀(frame)
,由 1 個或多個幀組成一條完整的 消息(message)
。
用於數據傳輸部分的報文格式是經過本節中詳細描述的 ABNF 來描述。
下面給出了 WebSocket 數據幀的統一格式。熟悉 TCP/IP 協議的同窗對這樣的圖應該不陌生。
從左到右,單位是比特。好比 FIN
、RSV1
各佔據 1 比特,opcode
佔據 4 比特。
內容包括了標識、操做代碼、掩碼、數據、數據長度等。
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
針對前面的格式概覽圖,這裏逐個字段進行講解,若有不清楚之處,可參考協議規範,或留言交流。
若是是 1,表示這是 消息(message)
的最後一個分片(fragment)
,若是是 0,表示不是是 消息(message)
的最後一個 分片(fragment)
。
通常狀況下全爲 0。當客戶端、服務端協商採用 WebSocket 擴展時,這三個標誌位能夠非 0,且值的含義由擴展進行定義。若是出現非零的值,且並無採用 WebSocket 擴展,鏈接出錯。
操做代碼,Opcode 的值決定了應該如何解析後續的 數據載荷(data payload)
。若是操做代碼是不認識的,那麼接收端應該 斷開鏈接(fail the connection)
。可選的操做代碼以下:
表示是否要對數據載荷進行掩碼操做。從客戶端向服務端發送數據時,須要對數據進行掩碼操做;從服務端向客戶端發送數據時,不須要對數據進行掩碼操做。
若是服務端接收到的數據沒有進行過掩碼操做,服務端須要斷開鏈接。
若是 Mask 是 1,那麼在 Masking-key
中會定義一個 掩碼鍵(masking key)
,並用這個掩碼鍵來對數據載荷進行反掩碼。全部客戶端發送到服務端的數據幀,Mask 都是 1。
單位是字節。爲 7 位,或 7+16 位,或 1+64 位。
假設數 Payload length === x,若是
此外,若是 payload length
佔用了多個字節的話,payload length
的二進制表達採用 網絡序(big endian,重要的位在前)
。
全部從客戶端傳送到服務端的數據幀,數據載荷都進行了掩碼操做,Mask 爲 1,且攜帶了 4 字節的 Masking-key
。若是 Mask 爲 0,則沒有 Masking-key
。
備註:載荷數據的長度,不包括 mask key 的長度。
載荷數據:包括了擴展數據、應用數據。其中,擴展數據 x 字節,應用數據 y 字節。
擴展數據:若是沒有協商使用擴展的話,擴展數據數據爲 0 字節。全部的擴展都必須聲明擴展數據的長度,或者能夠如何計算出擴展數據的長度。此外,擴展如何使用必須在握手階段就協商好。若是擴展數據存在,那麼載荷數據長度必須將擴展數據的長度包含在內。
應用數據:任意的應用數據,在擴展數據以後(若是存在擴展數據),佔據了數據幀剩餘的位置。載荷數據長度 減去 擴展數據長度,就獲得應用數據的長度。
掩碼鍵(Masking-key)
是由客戶端挑選出來的 32 位的隨機數。掩碼操做不會影響數據載荷的長度。掩碼、反掩碼操做都採用以下算法:
首先,假設:
算法描述爲: original-octet-i 與 masking-key-octet-j 異或後,獲得 transformed-octet-i。
j = i MOD 4 transformed-octet-i = original-octet-i XOR masking-key-octet-j
一旦 WebSocket 客戶端、服務端創建鏈接後,後續的操做都是基於數據幀的傳遞。
WebSocket 根據 opcode
來區分操做的類型。好比0x8表示斷開鏈接,0x0-0x2
表示數據交互。
WebSocket 的每條消息可能被切分紅多個數據幀。當 WebSocket 的接收方收到一個數據幀時,會根據FIN的值來判斷,是否已經收到消息的最後一個數據幀。
FIN=1 表示當前數據幀爲消息的最後一個數據幀,此時接收方已經收到完整的消息,能夠對消息進行處理。FIN=0,則接收方還須要繼續監聽接收其他的數據幀。
此外,opcode
在數據交換的場景下,表示的是數據的類型。0x01表示文本,0x02表示二進制。而0x00比較特殊,表示延續幀(continuation frame)
,顧名思義,就是完整消息對應的數據幀還沒接收完。
WebSocket 爲了保持客戶端、服務端的實時雙向通訊,須要確保客戶端、服務端之間的 TCP 通道保持鏈接沒有斷開。然而,對於長時間沒有數據往來的鏈接,若是依舊長時間保持着,可能會浪費包括的鏈接資源。
但不排除有些場景,客戶端、服務端雖然長時間沒有數據往來,但仍須要保持鏈接。這個時候,能夠採用心跳來實現。
ping、pong 的操做,對應的是 WebSocket 的兩個控制幀,opcode分別是 0x九、0xA
。
一旦發送或接收到一個Close控制幀,這就是說,_WebSocket 關閉階段握手已啓動,且 WebSocket 鏈接處於 CLOSING 狀態。
當底層TCP鏈接已關閉,這就是說 WebSocket鏈接已關閉 且 WebSocket 鏈接處於 CLOSED 狀態。 若是 TCP 鏈接在 WebSocket 關閉階段已經完成後被關閉,WebSocket鏈接被說成已經 徹底地 關閉了。
若是WebSocket鏈接不能被創建,這就是說,WebSocket鏈接關閉,但不是 徹底的 。
當關閉一個已經創建的鏈接(例如,當在打開階段握手已經完成後發送一個關閉幀),端點能夠代表關閉的緣由。 由端點解釋這個緣由,而且端點應該給這個緣由採起動做,本規範是沒有定義的。 本規範定義了一組預約義的狀態碼,並指定哪些範圍能夠被擴展、框架和最終應用使用。 狀態碼和任何相關的文本消息是關閉幀的可選的組件。
當發送關閉幀時端點可使用以下預約義的狀態碼。
狀態碼 | 名稱 | 描述 |
---|---|---|
0–999 | 保留段, 未使用. | |
1000 | CLOSE_NORMAL | 正常關閉; 不管爲什麼目的而建立, 該連接都已成功完成任務. |
1001 | CLOSE_GOING_AWAY | 終端離開, 可能由於服務端錯誤, 也可能由於瀏覽器正從打開鏈接的頁面跳轉離開. |
1002 | CLOSE_PROTOCOL_ERROR | 因爲協議錯誤而中斷鏈接. |
1003 | CLOSE_UNSUPPORTED | 因爲接收到不容許的數據類型而斷開鏈接 (如僅接收文本數據的終端接收到了二進制數據). |
1004 | 保留. 其意義可能會在將來定義. | |
1005 | CLOSE_NO_STATUS | 保留. 表示沒有收到預期的狀態碼. |
1006 | CLOSE_ABNORMAL | 保留. 用於指望收到狀態碼時鏈接非正常關閉 (也就是說, 沒有發送關閉幀). |
1007 | Unsupported Data | 因爲收到了格式不符的數據而斷開鏈接 (如文本消息中包含了非 UTF-8 數據). |
1008 | Policy Violation | 因爲收到不符合約定的數據而斷開鏈接. 這是一個通用狀態碼, 用於不適合使用 1003 和 1009 狀態碼的場景. |
1009 | CLOSE_TOO_LARGE | 因爲收到過大的數據幀而斷開鏈接. |
1010 | Missing Extension | 客戶端指望服務器商定一個或多個拓展, 但服務器沒有處理, 所以客戶端斷開鏈接. |
1011 | Internal Error | 客戶端因爲遇到沒有預料的狀況阻止其完成請求, 所以服務端斷開鏈接. |
1012 | Service Restart | 服務器因爲重啓而斷開鏈接. |
1013 | Try Again Later | 服務器因爲臨時緣由斷開鏈接, 如服務器過載所以斷開一部分客戶端鏈接. |
1014 | 由 WebSocket 標準保留以便將來使用. | |
1015 | TLS Handshake | 保留. 表示鏈接因爲沒法完成 TLS 握手而關閉 (例如沒法驗證服務器證書). |
1016–1999 | 由 WebSocket 標準保留以便將來使用. | |
2000–2999 | 由 WebSocket 拓展保留使用. | |
3000–3999 | 能夠由庫或框架使用.不該由應用使用. 能夠在 IANA 註冊, 先到先得. | |
4000–4999 | 能夠由應用使用. |
WebSocket 對象提供了用於建立和管理 WebSocket 鏈接,以及能夠經過該鏈接發送和接收數據的 API。
WebSocket 構造器方法接受一個必須的參數和一個可選的參數:
WebSocket WebSocket(in DOMString url, in optional DOMString protocols); WebSocket WebSocket(in DOMString url,in optional DOMString[] protocols);
表示要鏈接的URL。這個URL應該爲響應WebSocket的地址。
能夠是一個單個的協議名字字符串或者包含多個協議名字字符串的數組。這些字符串用來表示子協議,這樣作可讓一個服務器實現多種 WebSocket子協議(例如你可能但願經過制定不一樣的協議來處理不一樣類型的交互)。若是沒有制定這個參數,它會默認設爲一個空字符串。
構造器方法可能拋出如下異常:SECURITY_ERR
試圖鏈接的端口被屏蔽。
var ws = new WebSocket('ws://localhost:8080');
執行上面語句以後,客戶端就會與服務器進行鏈接。
屬性名 | 類型 | 描述 |
---|---|---|
binaryType | DOMString | 一個字符串表示被傳輸二進制的內容的類型。取值應當是"blob"或者"arraybuffer"。"blob"表示使用DOM Blob 對象,而"arraybuffer"表示使用 ArrayBuffer 對象。 |
bufferedAmount | unsigned long | 調用 send()) 方法將多字節數據加入到隊列中等待傳輸,可是還未發出。該值會在全部隊列數據被髮送後重置爲 0。而當鏈接關閉時不會設爲0。若是持續調用send(),這個值會持續增加。只讀。 |
extensions | DOMString | 服務器選定的擴展。目前這個屬性只是一個空字符串,或者是一個包含全部擴展的列表。 |
onclose | EventListener | 用於監聽鏈接關閉事件監聽器。當 WebSocket 對象的readyState 狀態變爲 CLOSED 時會觸發該事件。這個監聽器會接收一個叫close的 CloseEvent 對象。 |
onerror | EventListener | 當錯誤發生時用於監聽error事件的事件監聽器。會接受一個名爲「error」的event對象。 |
onmessage | EventListener | 一個用於消息事件的事件監聽器,這一事件當有消息到達的時候該事件會觸發。這個Listener會被傳入一個名爲"message"的 MessageEvent 對象。 |
onopen | EventListener | 一個用於鏈接打開事件的事件監聽器。當readyState的值變爲 OPEN 的時候會觸發該事件。該事件代表這個鏈接已經準備好接受和發送數據。這個監聽器會接受一個名爲"open"的事件對象。 |
protocol | DOMString | 一個代表服務器選定的子協議名字的字符串。這個屬性的取值會被取值爲構造器傳入的protocols參數。 |
readyState | unsigned short | 鏈接的當前狀態。取值是 Ready state constants 之一。 只讀。 |
url | DOMString | 傳入構造器的URL。它必須是一個絕對地址的URL。只讀。 |
實例對象的 onopen 屬性,用於指定鏈接成功後的回調函數。
ws.onopen = function () { ws.send('Hello Server!'); }
若是要指定多個回調函數,可使用addEventListener方法。
ws.addEventListener('open', function (event) { ws.send('Hello Server!'); });
實例對象的 onclose 屬性,用於指定鏈接關閉後的回調函數。
ws.onclose = function(event) { var code = event.code; var reason = event.reason; var wasClean = event.wasClean; // handle close event }; ws.addEventListener("close", function(event) { var code = event.code; var reason = event.reason; var wasClean = event.wasClean; // handle close event });
實例對象的 onmessage 屬性,用於指定收到服務器數據後的回調函數。
ws.onmessage = function(event) { var data = event.data; // 處理數據 }; ws.addEventListener("message", function(event) { var data = event.data; // 處理數據 });
注意,服務器數據多是文本,也多是 二進制數據(blob對象或Arraybuffer對象)。
ws.onmessage = function(event){ if(typeof event.data === String) { console.log("Received data string"); } if(event.data instanceof ArrayBuffer){ var buffer = event.data; console.log("Received arraybuffer"); } }
除了動態判斷收到的數據類型,也可使用 binaryType
屬性,顯式指定收到的二進制數據類型。
// 收到的是 blob 數據 ws.binaryType = "blob"; ws.onmessage = function(e) { console.log(e.data.size); }; // 收到的是 ArrayBuffer 數據 ws.binaryType = "arraybuffer"; ws.onmessage = function(e) { console.log(e.data.byteLength); };
這些常量是 readyState
屬性的取值,能夠用來描述 WebSocket 鏈接的狀態。
常量 | 值 | 描述 |
---|---|---|
CONNECTING | 0 | 鏈接還沒開啓。 |
OPEN | 1 | 鏈接已開啓並準備好進行通訊。 |
CLOSING | 2 | 鏈接正在關閉的過程當中。 |
CLOSED | 3 | 鏈接已經關閉,或者鏈接沒法創建。 |
關閉 WebSocket 鏈接或中止正在進行的鏈接請求。若是鏈接的狀態已是 closed
,這個方法不會有任何效果
void close(in optional unsigned short code, in optional DOMString reason);
code 可選
一個數字值表示關閉鏈接的狀態號,表示鏈接被關閉的緣由。若是這個參數沒有被指定,默認的取值是1000 (表示正常鏈接關閉)。 請看 CloseEvent 頁面的 list of status codes來看默認的取值。
reason 可選
一個可讀的字符串,表示鏈接被關閉的緣由。這個字符串必須是不長於123字節的UTF-8 文本(不是字符)。
可能拋出的異常
unpaired surrogates
。經過 WebSocket 鏈接向服務器發送數據。
void send(in DOMString data); void send(in ArrayBuffer data); void send(in Blob data);
data:要發送到服務器的數據。
可能拋出的異常:
unpaired surrogates
的字符串。發送文本的例子。
ws.send('your message');
發送 Blob 對象的例子。
var file = document .querySelector('input[type="file"]') .files[0]; ws.send(file);
發送 ArrayBuffer 對象的例子。
// Sending canvas ImageData as ArrayBuffer var img = canvas_context.getImageData(0, 0, 400, 320); var binary = new Uint8Array(img.data.length); for (var i = 0; i < img.data.length; i++) { binary[i] = img.data[i]; } ws.send(binary.buffer);
WebSocket 服務器的實現,能夠查看維基百科的列表。
經常使用的 Node 實現有如下三種。
WebSocket 是基於 TCP 的獨立的協議。它與 HTTP 惟一的關係是它的握手是由 HTTP 服務器解釋爲一個 Upgrade 請求。
WebSocket協議試圖在現有的 HTTP 基礎設施上下文中解決現有的雙向HTTP技術目標;一樣,它被設計工做在HTTP端口80和443,也支持HTTP代理和中間件,
HTTP服務器須要發送一個「Upgrade」請求,即101 Switching Protocol到HTTP服務器,而後由服務器進行協議轉換。
前面提到了,Sec-WebSocket-Key/Sec-WebSocket-Accept
在主要做用在於提供基礎的防禦,減小惡意鏈接、意外鏈接。
做用大體概括以下:
避免服務端收到非法的 websocket 鏈接(好比 http 客戶端不當心請求鏈接 websocket 服務,此時服務端能夠直接拒絕鏈接)
確保服務端理解 websocket 鏈接。由於 ws 握手階段採用的是 http 協議,所以可能 ws 鏈接是被一個 http 服務器處理並返回的,此時客戶端能夠經過 Sec-WebSocket-Key
來確保服務端認識 ws 協議。(並不是百分百保險,好比老是存在那麼些無聊的 http 服務器,光處理 Sec-WebSocket-Key
,但並無實現 ws 協議。。。)
用瀏覽器裏發起 ajax 請求,設置 header 時,Sec-WebSocket-Key
以及其餘相關的 header 是被禁止的。這樣能夠避免客戶端發送 ajax 請求時,意外請求協議升級(websocket upgrade)
能夠防止反向代理(不理解 ws 協議)返回錯誤的數據。好比反向代理先後收到兩次 ws 鏈接的升級請求,反向代理把第一次請求的返回給 cache 住,而後第二次請求到來時直接把 cache 住的請求給返回(無心義的返回)。
Sec-WebSocket-Key
主要目的並非確保數據的安全性,由於 Sec-WebSocket-Key
、Sec-WebSocket-Accept
的轉換計算公式是公開的,並且很是簡單,最主要的做用是預防一些常見的意外狀況(非故意的)。
WebSocket 協議中,數據掩碼的做用是加強協議的安全性。但數據掩碼並非爲了保護數據自己,由於算法自己是公開的,運算也不復雜。除了加密通道自己,彷佛沒有太多有效的保護通訊安全的辦法。
那麼爲何還要引入掩碼計算呢,除了增長計算機器的運算量外彷佛並無太多的收益(這也是很多同窗疑惑的點)。
答案仍是兩個字:安全。但並非爲了防止數據泄密,而是爲了防止早期版本的協議中存在的代理緩存污染攻擊(proxy cache poisoning attacks)
等問題。