1、什么是WebSocket
WebSocket 是一種自然的全雙工、雙向、單套接字連接。使用WebSocket,你的HTTP 請求變成打開WebSocket 連接(WebSocket 或者WebSocket over TLS(TransportLayer Security,傳輸層安全性,原稱“SSL”))的單一請求,並且重用從客戶端到服務器以及服務器到客戶端的同一連接。WebSocket 減少了延遲,因為一旦建立起WebSocket 連接,服務器可以在消息可用時發送它們。例如,和輪詢不同,WebSocket只發出一個請求。服務器不需要等待來自客戶端的請求。相似地,客戶端可以在任何時候向服務器發送消息。相比輪詢不管是否有可用消息,每隔一段時間都發送一個請求,單一請求大大減少了延遲。
2、WebSocket API
WebSocket API 使你可以通過Web,在客戶端應用程序和服務器端進程之間建立全雙工的雙向通信。WebSocket 接口規定了可用於客戶端的方法以及客戶端與網絡的交互方式。首先,你要調用WebSocket 構造函數(constructor),創建一個WebSocket 連接。構造函數返回WebSocket 對象實例。你可以監聽該對象上的事件,這些事件告訴你何時連接打開,何時消息到達,何時連接關閉以及何時發生錯誤。你可以與WebSocket 對象交互,發送消息或者關閉連接。下面來研究WebSocket API 的各個方面。
3、WebSocket 構造函數
為了建立到服務器的WebSocket 連接,使用WebSocket 接口,通過指向一個代表所要連接端點的URL,實例化一個WebSocket對象。WebSocket 協議定義了兩種URL 方案(URL scheme)—ws 和wss,分別用於客戶端和服務器之間的非加密與加密流量。ws(WebSocket) 方案與HTTP URI 方案類似。wss(WebSocketSecure,WebSocket 安全)URI 方案表示使用傳輸層安全性(TLS,也叫SSL)的WebSocket 連接,使用HTTPS 采用的安全機制來保證HTTP 連接的安全。WebSocket 構造函數有一個必需的參數URL(指向連接目標的URL)和一個可選參數protocols(為了建立連接,服務器必須在其響應中包含的一個或一組協議名稱)。在protocols 參數中可以使用的協議包括XMPP(eXtensible Messaging and PresenceProtocol, 可擴展消息處理現場協議)、SOAP(Simple ObjectAccess Protocol,簡單對象訪問協議)或者自定義協議。
var ws = new WebSocket("ws://www.websocket.org");
帶有協議支持的WebSocket 構造函數示例
// Connecting to the server with multiple protocol choices var echoSocket = new WebSocket("ws://echo.websocket.org", ["com.kaazing.echo", "example.imaginary.protocol"]) echoSocket.onopen = function(e) { // Check the protocol chosen by the server console.log(echoSocket.protocol); }
由於WebSocket 服務器ws://echo.websocket.org 只理解com.kaazing.echo 協議, 而不理解example.imaginary.protocol,該服務器在觸發WebSocket open 事件的時候選擇com.kaazing.echo 協議。使用數組為你提供了讓應用程序對不同服務器使用不同協議的靈活性。
4、WebSocket 事件
WebSocket API 是純事件驅動的。應用程序代碼監聽WebSocket對象上的事件,以便處理輸入數據和連接狀態的改變。WebSocket協議也是事件驅動的。客戶端應用程序不需要輪詢服務器來得到更新的數據。消息和事件將在服務器發送它們的時候異步到達。WebSocket 編程遵循異步編程模式,也就是說,只要WebSocket連接打開,應用程序就簡單地監聽事件。客戶端不需要主動輪詢服務器得到更多的信息。要開始監聽事件,只要為WebSocket 對象添加回調函數。也可以使用addEventListener() DOM 方法為WebSocket 對象添加事件監聽器。
WebSocket 對象調度4 個不同的事件:
open
message
error
close
和所有Web API 一樣,可以用on< 事件名稱> 處理程序屬性
監聽這些事件,也可以使用addEventListener(); 方法。
4.1、WebSocket 事件:open
一旦服務器響應了WebSocket 連接請求,open 事件觸發並建立一個連接。open 事件對應的回調函數稱作onopen。
ws.onopen = function(e) { console.log("Connection open..."); };
到open 事件觸發時,協議握手已經完成,WebSocket 已經准備好發送和接收數據。如果應用程序接收到一個open 事件,那么可以確定WebSocket 服務器成功地處理了連接請求,並且同意與應用程序通信。
4.2、WebSocket messagess事件
WebSocket 消息包含來自服務器的數據。你也可能聽說過組成WebSocket 消息的WebSocket 幀(Frame)。message 事件在接收到消息時觸發,對應於該事件的回調函數是onmessage。
ws.onmessage = function(e) { if(typeof e.data === "string"){ console.log("String message received", e, e.data); } else { console.log("Other message received", e, e.data); } };
除了文本,WebSocket 消息還可以處理二進制數據,這種數據作為Blob 消息或者ArrayBuffer 消息處理。因為設置WebSocket 消息二進制數據類型的應用程序會影響二進制消息,所以必須在讀取數據之前決定用於客戶端二進制輸入數據的類型。
ws.binaryType = "blob"; // Event handler for receiving Blob messages ws.onmessage = function(e) { if(e.data instanceof Blob){ console.log("Blob message received", e.data); var blob = new Blob(e.data); } ws.binaryType = "arraybuffer"; // Event handler for receiving ArrayBuffer messages ws.onmessage = function(e) { if(e.data instanceof ArrayBuffer){ console.log("ArrayBuffer Message Received", + e.data); // e.data is an ArrayBuffer. Create a byte view of that object. var a = new Uint8Array(e.data); } };
4.3、 WebSocket 事件:error
error 事件在響應意外故障的時候觸發。與該事件對應的回調函數為onerror。錯誤還會導致WebSocket 連接關閉。如果你接收一個error 事件,可以預期很快就會觸發close 事件。close 事件中的代碼和原因有時候能告訴你錯誤的根源。error事件處理程序是調用服務器重連邏輯以及處理來自WebSocket 對象的異常的最佳場所。
ws.onerror = function(e){ console.log('websocked error'); handerError(); }
4.4、 WebSocket 事件:close
close 事件在WebSocket 連接關閉時觸發。對應於close 事件的回調函數是onclose。一旦連接關閉,客戶端和服務器不再能接收或者發送消息。
ws.onclose = function(e) { console.log("Connection closed", e); };
WebSocket close 事件在連接關閉時觸發,這可能有多種原因,比如連接失敗或者成功的WebSocket 關閉握手。WebSocket 對象特性readyState 反映了連接的狀態(2 為正在關閉,3 為已關閉)。
close 事件有3 個有用的屬性(property),可以用於錯誤處理和恢復:wasClean、code 和error。wasClean 屬性是一個布爾屬性,表示連接是否順利關閉。如果WebSocket 的關閉是對來自服務器的一個close 幀的響應,則該屬性為true。如果連接是因為其他原因(例如,因為底層TCP 連接關閉)關閉,則該屬性為false。code 和reason 屬性表示服務器發送的關閉握手狀態。這些屬性和WebSocket.close() 方法中的code 和reason 參數一致。
5、WebSocket 方法
WebSocket 對象有兩個方法:send() 和close()。
5.1、WebSocket 方法:send()
使用WebSocket 在客戶端和服務器之間建立全雙工雙向連接后,就可以在連接打開時(也就是說,在調用onopen 監聽器之后,調用onclose 監聽器之前)調用send() 方法。使用send() 方法可以從客戶端向服務器發送消息。在發送一條或者多條消息之后,可以保持連接打開,或者調用close() 方法終止連接。
ws.send("Hello WebSocket!");
send() 方法在連接打開的時候發送數據。如果連接不可用或者關閉,它拋出一個有關無效連接狀態的異常。人們開始使用WebSocket API 時常犯的一個錯誤是試圖在連接打開之前發送消息。
// Wait until the open event before calling send(). var ws = new WebSocket("ws://echo.websocket.org") ws.onopen = function(e) { ws.send("Initial data"); }
如果想發送消息響應另一個事件, 可以檢查WebSocketreadyState 屬性,並選擇只在套接字打開時發送數據。
function myEventHandler(data) { if (ws.readyState === WebSocket.OPEN) { // The socket is open, so it is ok to send the data. ws.send(data); } else { // Do something else in this case. //Possibly ignore the data or enqueue it. } }
除了文本(字符串)消息之外,WebSocket API 允許發送二進制數據,這對於實現二進制協議特別有用。這樣的二進制協議可能是TCP 上層的標准互聯網協議,這些協議的載荷可能是Blob 或ArrayBuffer。
5.2、WebSocket 方法:close()
使用close() 方法, 可以關閉WebSocket 連接或者終止連接嘗試。如果連接已經關閉,該方法就什么都不做。在調用close() 之后,不能在已經關閉的WebSocket 上發送任何數據。可以向close() 方法傳遞兩個可選參數:code(數字型的狀態代碼)和reason(一個文本字符串)。傳遞這些參數能夠向服務器傳遞關於客戶關閉連接原因的信息。
6、WebSocket 對象特性
可以使用多種WebSocket 對象特性提供關於WebSocket 對象的更多信息:readyState、bufferedAmount 和protocol。
6.1、WebSocket 對象特性:readyState
下表readyState 特性、取值和狀態描述
特性常量 | 取值 | 狀態 |
WebSocket.CONNECTING | 0 | 連接正在進行中,但還未建立 |
WebSocket.OPEN | 1 | 連接已經建立。消息可以在客戶端和服務器之間傳遞 |
WebSocket.CLOSING | 2 | 連接正在進行關閉握手 |
WebSocket.CLOSED | 3 | 連接已經關閉,不能打開 |
6.2、WebSocket 對象特性:bufferedAmount
設計應用程序時,你可能想要檢查發往服務器的緩沖數據量,特別是在客戶端應用程序向服務器發送大量數據的時候。盡管調用send() 是立即生效的,但是數據在互聯網上的傳輸卻不是如此。瀏覽器將為你的客戶端應用程序緩存出站數據,從而使你可以隨時調用send(),發送任意數量的數據。然而,如果你想知道數據在網絡上傳送的速率,WebSocket 對象可以告訴你緩存的大小。你可以使用bufferedAmount 特性檢查已經進入隊列,但是尚未發送到服務器的字節數。這個特性報告的值不包括協議組幀開銷或者操作系統、網絡硬件所進行的緩沖。
代碼展示一個使用bufferedAmount 特性每秒發送更新的例子。如果網絡無法承受這一速率,它會相應地作出調整。
// 10k max buffer size. var THRESHOLD = 10240; // Create a New WebSocket connection var ws = new WebSocket("ws://echo.websocket.org/updates"); // Listen for the opening event ws.onopen = function () { // Attempt to send update every second. setInterval( function() { // Send only if the buffer is not full if (ws.bufferedAmount < THRESHOLD) { ws.send(getApplicationState()); } }, 1000); };
對於限制應用向服務器發送數據的速率,從而避免網絡飽和,bufferedAmount 特性很有用。
6.3、 WebSocket 對象特性:protocol
在前面關於WebSocket 構造函數的討論中, 我們提到了protocol 參數,它讓服務器知道客戶端理解並可在WebSocket上使用的協議。WebSocket 對象的protocol 特性提供了另一條關於WebSocket 實例的有用信息。客戶端和服務器協議協商的結果可以在WebSocket 對象上看到。protocol 特性包含在打開握手期間WebSocket 服務器選擇的協議名,換句話說,protocol特性告訴你特定WebSocket 上使用的協議。protocol 特性在最初的握手完成之前為空,如果服務器沒有選擇客戶端提供的某個協議,該特性保持空值。
例子:
<!DOCTYPE html> <title>WebSocket Echo Client</title> <h2>Websocket Echo Client</h2> <div id="output"></div> <script> // Initialize WebSocket connection and event handlers function setup() { output = document.getElementById("output"); ws = new WebSocket("ws://echo.websocket.org/echo"); // Listen for the connection open event then call the sendMessage function ws.onopen = function(e) { log("Connected"); sendMessage("這是發送的數據") } // Listen for the close connection event ws.onclose = function(e) { log("Disconnected: " + e.reason); } // Listen for connection errors ws.onerror = function(e) { log("Error "); } // Listen for new messages arriving at the client ws.onmessage = function(e) { log("Message received: " + e.data); // Close the socket once one message has arrived. ws.close(); } } // Send a message on the WebSocket. function sendMessage(msg){ ws.send(msg); log("Message sent"); } // Display logging information in the document. function log(s) { var p = document.createElement("p"); p.style.wordWrap = "break-word"; p.textContent = s; output.appendChild(p); // Also log information on the javascript console console.log(s); } // Start running the example. setup(); </script> </html>
7、WebSocket 瀏覽器兼容性檢測
if (window.WebSocket){
console.log("This browser supports WebSocket!");
} else {
console.log("This browser does not support WebSocket.");
}
8、在WebSocket 中使用HTML5 媒體
作為HTML5 和Web 平台的一部分,WebSocket API 可以很好地和所有HTML5 特性(feature)配合。這個API 所能發送和接收的數據類型廣泛地用於傳輸應用程序數據和媒體。字符串當然可以表示XML 和JSON 等Web 數據格式。二進制類型可以和拖放(Drag-and-Drop)、FileReader、WebGL 和Web Audio API 等集成。我們來看看如何結合WebSocket 使用HTML5 媒體。代碼清單展示了一個結合WebSocket 使用HTML5 媒體的完整客戶端應用程序。
你可以根據這些代碼創建自己的HTML 文件。
<!DOCTYPE html> <title>WebSocket Image Drop</title> <h1>Drop Image Here</h1> <script> // Initialize WebSocket connection var wsUrl = "ws://echo.websocket.org/echo"; var ws = new WebSocket(wsUrl); ws.onopen = function() { console.log("open"); } // Handle binary image data received on the WebSocket ws.onmessage = function(e) { var blob = e.data; console.log("message: " + blob.size + " bytes"); // Work with prefixed URL API if (window.webkitURL) { URL = webkitURL; } var uri = URL.createObjectURL(blob); var img = document.createElement("img"); img.src = uri; document.body.appendChild(img); } // Handle drop event document.ondrop = function(e) { document.body.style.backgroundColor = "#fff"; try { e.preventDefault(); handleFileDrop(e.dataTransfer.files[0]); return false; } catch(err) { console.log(err); } } // Provide visual feedback for the drop area document.ondragover = function(e) { e.preventDefault(); document.body.style.backgroundColor = "#6fff41"; } document.ondragleave = function() { document.body.style.backgroundColor = "#fff"; } // Read binary file contents and send them over WebSocket function handleFileDrop(file) { var reader = new FileReader(); reader.readAsArrayBuffer(file); reader.onload = function() { console.log("sending: " + file.name); ws.send(reader.result); } } </script> </html>
文章參考:HTML5+WebSocket權威指南