WebSocket 快速指南



WebSocket - 概述

從字面上講,握手可以定義為兩個人為了表示問候、祝賀、同意或告別而握手的動作。在計算機科學中,握手是一個確保伺服器與其客戶端同步的過程。握手是 WebSocket 協議的基本概念。

下圖顯示了伺服器與各個客戶端的握手過程:

Server

WebSocket – 定義

WebSocket 定義為伺服器和客戶端之間的雙向通訊,這意味著雙方可以同時通訊和交換資料。

WebSocket 的關鍵點是真正的併發性效能最佳化,從而產生更具響應性和更豐富的 Web 應用程式。

WebSocket 協議描述

此協議從根本上定義了全雙工通訊。WebSocket 在將桌面豐富的功能帶入 Web 瀏覽器方面向前邁進了一步。它代表了客戶端/伺服器 Web 技術長期以來期待的進步。

WebSocket 的主要特性如下:

  • WebSocket 協議正在標準化,這意味著藉助此協議可以實現 Web 伺服器和客戶端之間的即時通訊。

  • WebSocket 正在轉變為客戶端和伺服器之間即時通訊的跨平臺標準。

  • 此標準支援新型應用程式。藉助這項技術,即時 Web 應用程式的業務可以加速發展。

  • WebSocket 最大的優點是它透過單個 TCP 連線提供雙向通訊(全雙工)。

URL

HTTP 具有自己的一組方案,例如 http 和 https。WebSocket 協議在其 URL 模式中也定義了類似的方案。

下圖顯示了 WebSocket URL 的標記。

Protocol

瀏覽器支援

WebSocket 協議的最新規範定義為RFC 6455 – 一項提議的標準。

RFC 6455 受各種瀏覽器支援,例如 Internet Explorer、Mozilla Firefox、Google Chrome、Safari 和 Opera。

WebSocket - 雙向通訊

在深入探討 WebSocket 的需求之前,有必要了解一下現有的技術,這些技術用於伺服器和客戶端之間的雙向通訊。它們如下:

  • 輪詢
  • 長輪詢
  • 流式傳輸
  • 回發和 AJAX
  • HTML5

輪詢

輪詢可以定義為一種方法,無論傳輸中是否存在資料,它都會執行週期性請求。週期性請求以同步方式傳送。客戶端在指定的時間間隔內向伺服器傳送週期性請求。伺服器的響應包括可用資料或某些警告訊息。

長輪詢

長輪詢顧名思義,包括類似於輪詢的技術。客戶端和伺服器保持連線處於活動狀態,直到獲取某些資料或超時發生。如果由於某些原因連線丟失,客戶端可以重新開始並執行順序請求。

長輪詢只不過是輪詢過程的效能改進,但持續的請求可能會減慢該過程。

流式傳輸

它被認為是即時資料傳輸的最佳選擇。伺服器保持與客戶端的連線開啟並處於活動狀態,直到獲取所需資料為止。在這種情況下,連線被認為是無限期開啟的。流式傳輸包括 HTTP 標頭,這會增加檔案大小,從而增加延遲。這可以被認為是一個主要的缺點。

AJAX

AJAX 基於 JavaScript 的XmlHttpRequest 物件。它是非同步 JavaScript 和 XML 的縮寫形式。XmlHttpRequest 物件允許執行 JavaScript 而不重新載入整個網頁。AJAX 只發送和接收網頁的一部分。

使用XmlHttpRequest 物件的 AJAX 呼叫的程式碼片段如下:

var xhttp;

if (window.XMLHttpRequest) {
   xhttp = new XMLHttpRequest();
} else {
   // code for IE6, IE5
   xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}

WebSocket相比,AJAX的主要缺點是:

  • 它們傳送 HTTP 標頭,這會使總大小更大。
  • 通訊是半雙工的。
  • Web 伺服器消耗更多資源。

HTML5

HTML5 是一個用於開發和設計 Web 應用程式的強大框架。其主要支柱包括標記、CSS3JavaScript API。

下圖顯示了 HTML5 元件:

HTML5

以下程式碼片段描述了 HTML5 及其文件型別的宣告。

<!DOCTYPE html>

為什麼我們需要 WebSocket?

網際網路最初被設想為超文字標記語言 (HTML) 頁面的集合,這些頁面相互連結以形成一個概念性的資訊網路。隨著時間的推移,靜態資源的數量增加,更豐富的專案(例如影像)也開始成為網路結構的一部分。

伺服器技術進步,允許動態伺服器頁面——內容基於查詢生成的頁面。

很快,對更動態網頁的需求導致了動態超文字標記語言 (DHTML) 的出現。這都要感謝 JavaScript。在接下來的幾年裡,我們看到了跨框架通訊,試圖避免頁面重新載入,然後是框架內的HTTP 輪詢

但是,這些解決方案都沒有提供真正標準化的跨瀏覽器解決方案,用於伺服器和客戶端之間的即時雙向通訊。

這導致了對 WebSocket 協議的需求。它催生了全雙工通訊,將桌面豐富的功能帶到所有 Web 瀏覽器。

WebSocket – 功能

WebSocket 代表著 Web 通訊歷史上的一次重大升級。在其存在之前,Web 客戶端和伺服器之間的所有通訊都僅依賴於 HTTP。

WebSocket 有助於持久全雙工連線的動態流動。全雙工是指來自兩端的通訊速度相當快。

它被稱為改變遊戲規則者,因為它有效地克服了現有協議的所有缺點。

面向開發人員和架構師的 WebSocket

WebSocket 對開發人員和架構師的重要性:

  • WebSocket 是一種獨立的基於 TCP 的協議,但它旨在支援任何其他協議,這些協議傳統上只能在純 TCP 連線之上執行。

  • WebSocket 是一個傳輸層,在其之上可以執行任何其他協議。WebSocket API 支援定義子協議:可以解釋特定協議的協議庫。

  • 此類協議的示例包括 XMPP、STOMP 和 AMQP。開發人員不再需要考慮 HTTP 請求-響應範例。

  • 瀏覽器端唯一的要求是執行可以解釋 WebSocket 握手、建立和維護 WebSocket 連線的 JavaScript 庫。

  • 在伺服器端,行業標準是使用在 TCP 之上執行並利用 WebSocket 閘道器的現有協議庫。

下圖描述了 WebSocket 的功能:

Web

WebSocket 連線是透過 HTTP 啟動的;HTTP 伺服器通常將 WebSocket 握手解釋為升級請求。

WebSocket 可以作為現有 HTTP 環境的補充附加元件,也可以提供新增 Web 功能所需的架構。它依賴於更高階的全雙工協議,允許資料在客戶端和伺服器之間雙向流動。

WebSocket 的功能

WebSocket 在 Web 伺服器和客戶端之間建立連線,以便雙方都可以開始傳送資料。

建立 WebSocket 連線的步驟如下:

  • 客戶端透過稱為 WebSocket 握手的過程建立連線。

  • 該過程從客戶端向伺服器傳送常規 HTTP 請求開始。

  • 請求升級標頭。在此請求中,它通知伺服器請求用於 WebSocket 連線。

  • WebSocket URL 使用ws 方案。它們也用於安全的 WebSocket 連線,相當於 HTTPS。

初始請求標頭的簡單示例如下:

GET ws://websocket.example.com/ HTTP/1.1
Origin: http://example.com
Connection: Upgrade
Host: websocket.example.com
Upgrade: websocket

WebSocket – 實現

WebSocket 不僅在 Web 領域,而且在移動行業中也扮演著關鍵角色。WebSocket 的重要性如下所示。

  • 顧名思義,WebSocket 與 Web 相關。Web 由一些瀏覽器的技術組成;它是一個廣泛的通訊平臺,用於大量的裝置,包括臺式電腦、筆記型電腦、平板電腦和智慧手機。

  • 使用 WebSocket 的 HTML5 應用程式可以在任何支援 HTML5 的 Web 瀏覽器上執行。

  • 主流作業系統都支援 WebSocket。移動行業的每個主要參與者都在自己的原生應用程式中提供 WebSocket API。

  • WebSocket 被稱為全雙工通訊。WebSocket 的方法適用於某些類別的 Web 應用程式,例如聊天室,其中客戶端和伺服器的更新同時共享。

Web Socket

WebSocket 作為 HTML5 規範的一部分,允許 Web 頁面和遠端主機之間進行全雙工通訊。該協議旨在實現以下好處,這些好處可以被認為是關鍵點:

  • 透過單個連線(而不是兩個連線)使用全雙工減少不必要的網路流量和延遲

  • 透過代理和防火牆進行流式傳輸,同時支援上行和下行通訊。

WebSocket – 事件和操作

為了在它們之間進行通訊,需要從客戶端初始化到伺服器的連線。為了初始化連線,需要使用遠端或本地伺服器的 URL 建立 JavaScript 物件。

var socket = new WebSocket(“ ws://echo.websocket.org ”);

上述 URL 是一個公共地址,可用於測試和實驗。websocket.org 伺服器始終處於執行狀態,當它接收到訊息時會將其傳送回客戶端。

這是確保應用程式正常執行的最重要步驟。

WebSockets – 事件

Web Socket API 主要有四個事件

  • 開啟 (Open)
  • 訊息 (Message)
  • 關閉 (Close)
  • 錯誤 (Error)

每個事件都透過實現相應的函式來處理,例如onopen、onmessage、oncloseonerror 函式。也可以透過 addEventListener 方法來實現。

事件和函式的簡要概述如下:

開啟 (Open)

一旦客戶端和伺服器之間建立連線,Web Socket 例項就會觸發 open 事件。這被稱為客戶端和伺服器之間的初始握手。連線建立後觸發的事件稱為onopen

訊息 (Message)

當伺服器傳送一些資料時,通常會發生訊息事件。伺服器傳送給客戶端的訊息可以包括純文字訊息、二進位制資料或影像。每當傳送資料時,都會觸發onmessage 函式。

關閉 (Close)

關閉事件標誌著伺服器和客戶端之間通訊的結束。可以使用onclose 事件來關閉連線。使用onclose 事件標誌通訊結束之後,伺服器和客戶端之間將無法再傳輸任何訊息。關閉事件也可能由於連線不良而發生。

錯誤 (Error)

錯誤標誌著通訊過程中發生的某些錯誤。它透過onerror 事件來標記。onerror 事件之後總是會終止連線。每個事件的詳細描述將在後面的章節中討論。

WebSockets – 動作

事件通常在發生某些事情時觸發。另一方面,當用戶想要發生某些事情時,就會採取行動。動作是由使用者使用函式進行顯式呼叫的。

WebSocket 協議支援兩種主要動作:

  • send()
  • close()

send()

此操作通常用於與伺服器進行通訊,包括髮送訊息,其中包括文字檔案、二進位制資料或影像。

使用 send() 操作傳送的聊天訊息如下:

// get text view and button for submitting the message
var textsend = document.getElementById(“text-view”);
var submitMsg = document.getElementById(“tsend-button”);

//Handling the click event
submitMsg.onclick = function ( ) {
   // Send the data
   socket.send( textsend.value);
}

注意 – 只有在連線開啟時才能傳送訊息。

close()

此方法代表“再見”握手。它會完全終止連線,在重新建立連線之前,無法傳輸任何資料。

var textsend = document.getElementById(“text-view”);
var buttonStop = document.getElementById(“stop-button”);

//Handling the click event
buttonStop.onclick = function ( ) {
   // Close the connection if open
   if (socket.readyState === WebSocket.OPEN){
      socket.close( );
   }
}

也可以使用以下程式碼片段故意關閉連線:

socket.close(1000,”Deliberate Connection”);

WebSockets – 開啟連線

一旦客戶端和伺服器之間建立連線,Web Socket 例項就會觸發 open 事件。這被稱為客戶端和伺服器之間的初始握手。

連線建立後觸發的事件稱為onopen。建立 WebSocket 連線非常簡單。您只需呼叫WebSocket 建構函式並傳入伺服器的 URL 即可。

以下程式碼用於建立 WebSocket 連線:

// Create a new WebSocket.
var socket = new WebSocket('ws://echo.websocket.org');

連線建立後,open 事件將在您的 WebSocket 例項上觸發。

onopen 指的是客戶端和伺服器之間的初始握手,導致了第一次交易,並且 Web 應用程式已準備好傳輸資料。

以下程式碼片段描述了開啟 WebSocket 協議連線:

socket.onopen = function(event) {
   console.log(“Connection established”);
   // Display user friendly messages for the successful establishment of connection
   var.label = document.getElementById(“status”);
   label.innerHTML = ”Connection established”;
}

最好向等待建立 WebSocket 連線的使用者提供適當的反饋。但是,需要注意的是,WebSocket 連線速度相對較快。

建立的 WebSocket 連線演示文件位於以下 URL:https://www.websocket.org/echo.html

連線建立和對使用者的響應快照如下所示:

Snapshot

建立開啟狀態允許全雙工通訊和訊息傳輸,直到連線終止。

示例

構建客戶端 HTML5 檔案。

<!DOCTYPE html>
<html>
   <meta charset = "utf-8" />
   <title>WebSocket Test</title>

   <script language = "javascript" type = "text/javascript">
      var wsUri = "ws://echo.websocket.org/";
      var output;
	
      function init() {
         output = document.getElementById("output");
         testWebSocket();
      }
	
      function testWebSocket() {
         websocket = new WebSocket(wsUri);
			
         websocket.onopen = function(evt) {
            onOpen(evt)
         };
      }
	
      function onOpen(evt) {
         writeToScreen("CONNECTED");
      }
	
      window.addEventListener("load", init, false);
   
   </script>

   <h2>WebSocket Test</h2>
   <div id = "output"></div>

</html>

輸出如下:

Connected

上述 HTML5 和 JavaScript 檔案顯示了 WebSocket 的兩個事件的實現:

  • onLoad 用於建立 JavaScript 物件和初始化連線。

  • onOpen 建立與伺服器的連線併發送狀態。

WebSockets – 處理錯誤

一旦客戶端和伺服器之間建立連線,Web Socket 例項就會觸發open 事件。錯誤是針對通訊過程中發生的錯誤生成的。它透過onerror 事件來標記。onerror 事件之後總是會終止連線。

當通訊過程中出現錯誤時,會觸發onerror 事件。onerror 事件之後是連線終止,即close 事件。

最佳實踐是始終告知使用者意外錯誤並嘗試重新連線。

socket.onclose = function(event) {
   console.log("Error occurred.");
	
   // Inform the user about the error.
   var label = document.getElementById("status-label");
   label.innerHTML = "Error: " + event;
}

在錯誤處理方面,您必須同時考慮內部和外部引數。

  • 內部引數包括由於程式碼中的錯誤或意外的使用者行為而可能生成的錯誤。

  • 外部錯誤與應用程式無關;相反,它們與無法控制的引數有關。最重要的一項是網路連線。

  • 任何互動式雙向 Web 應用程式都需要活躍的網際網路連線。

檢查網路可用性

假設您的使用者正在享受您的 Web 應用程式,突然在他們任務的中間網路連線變得無響應。在現代原生桌面和移動應用程式中,檢查網路可用性是一項常見任務。

最常見的方法是簡單地嚮應該處於活動狀態的網站(例如,http://www.google.com)發出 HTTP 請求。如果請求成功,則桌面或移動裝置知道存在活動連線。同樣,HTML 具有XMLHttpRequest 用於確定網路可用性。

但是,HTML5 使其變得更加容易,並引入了一種方法來檢查瀏覽器是否可以接受 Web 響應。這是透過 navigator 物件實現的:

if (navigator.onLine) {
   alert("You are Online");
}else {
   alert("You are Offline");
}

離線模式意味著裝置未連線或使用者已從瀏覽器工具欄中選擇了離線模式。

以下是關於如何在發生 WebSocket 關閉事件時告知使用者網路不可用並嘗試重新連線的方法:

socket.onclose = function (event) {
   // Connection closed.
   // Firstly, check the reason.
	
   if (event.code != 1000) {
      // Error code 1000 means that the connection was closed normally.
      // Try to reconnect.
		
      if (!navigator.onLine) {
         alert("You are offline. Please connect to the Internet and try again.");
      }
   }
}

接收錯誤訊息的演示

以下程式說明了如何使用 WebSockets 顯示錯誤訊息:

<!DOCTYPE html>
<html>
   <meta charset = "utf-8" />
   <title>WebSocket Test</title>

   <script language = "javascript" type = "text/javascript">
      var wsUri = "ws://echo.websocket.org/";
      var output;
		
      function init() {
         output = document.getElementById("output");
         testWebSocket();
      }
		
      function testWebSocket() {
         websocket = new WebSocket(wsUri);
			
         websocket.onopen = function(evt) {
            onOpen(evt)
         };
			
         websocket.onclose = function(evt) {
            onClose(evt)
         };
			
         websocket.onerror = function(evt) {
            onError(evt)
         };
      }
		
      function onOpen(evt) {
         writeToScreen("CONNECTED");
         doSend("WebSocket rocks");
      }
		
      function onClose(evt) {
         writeToScreen("DISCONNECTED");
      }
		
      function onError(evt) {
         writeToScreen('<span style = "color: red;">ERROR:</span> ' + evt.data);
      } 
		
      function doSend(message) {
         writeToScreen("SENT: " + message); websocket.send(message);
      }
		
      function writeToScreen(message) {
         var pre = document.createElement("p"); 
         pre.style.wordWrap = "break-word"; 
         pre.innerHTML = message; output.appendChild(pre);
      }
		
      window.addEventListener("load", init, false);
   </script>
	
   <h2>WebSocket Test</h2>
   <div id = "output"></div>
	
</html>

輸出如下:

Disconnected

WebSockets – 傳送和接收訊息

訊息事件通常在伺服器傳送一些資料時發生。伺服器傳送給客戶端的訊息可以包括純文字訊息、二進位制資料或影像。每當傳送資料時,都會觸發onmessage 函式。

此事件充當客戶端對伺服器的“耳朵”。每當伺服器傳送資料時,都會觸發onmessage 事件。

以下程式碼片段描述了開啟 WebSocket 協議連線。

connection.onmessage = function(e){
   var server_message = e.data;
   console.log(server_message);
}

還需要考慮可以使用 WebSockets 傳輸哪些型別的資料。WebSocket 協議支援文字和二進位制資料。在 Javascript 中,text 指的是字串,而二進位制資料表示為ArrayBuffer

WebSockets 每次只支援一種二進位制格式。二進位制資料的宣告如下所示:

socket.binaryType = ”arrayBuffer”;
socket.binaryType = ”blob”;

字串

字串被認為是有用的,用於處理人類可讀的格式,例如 XML 和 JSON。每當觸發onmessage 事件時,客戶端都需要檢查資料型別並採取相應措施。

確定資料型別為字串的程式碼片段如下所示:

socket.onmessage = function(event){

   if(typeOf event.data === String ) {
      console.log(“Received data string”);
   }
}

JSON(JavaScript 物件表示法)

這是一種輕量級格式,用於在計算機之間傳輸人類可讀的資料。JSON 的結構由鍵值對組成。

示例

{
   name: “James Devilson”,
   message: “Hello World!”
}

以下程式碼顯示瞭如何處理 JSON 物件並提取其屬性:

socket.onmessage = function(event) {
   if(typeOf event.data === String ){
      //create a JSON object
      var jsonObject = JSON.parse(event.data);
      var username = jsonObject.name;
      var message = jsonObject.message;
		
      console.log(“Received data string”);
   }
}

XML

XML 解析並不困難,儘管技術因瀏覽器而異。最好的方法是使用 jQuery 等第三方庫進行解析。

在 XML 和 JSON 中,伺服器都以字串形式響應,該字串在客戶端進行解析。

ArrayBuffer

它包含結構化二進位制資料。包含的位按順序排列,以便可以輕鬆跟蹤位置。ArrayBuffer 方便儲存影像檔案。

使用 ArrayBuffer 接收資料相當簡單。使用instanceOf 運算子而不是等於運算子。

以下程式碼顯示瞭如何處理和接收 ArrayBuffer 物件:

socket.onmessage = function(event) {
   if(event.data instanceof ArrayBuffer ){
      var buffer = event.data;
      console.log(“Received arraybuffer”);
   }
}

演示應用程式

以下程式程式碼顯示瞭如何使用 WebSockets 傳送和接收訊息。

<!DOCTYPE html>
<html>
   <meta charset = "utf-8" />
   <title>WebSocket Test</title>

   <script language = "javascript" type = "text/javascript">
      var wsUri = "ws://echo.websocket.org/";
      var output;
		
      function init() {
         output = document.getElementById("output");
         testWebSocket();
      }
		
      function testWebSocket() {
         websocket = new WebSocket(wsUri);
			
         websocket.onopen = function(evt) {
            onOpen(evt)
         };
		
         websocket.onmessage = function(evt) {
            onMessage(evt)
         };
		
         websocket.onerror = function(evt) {
            onError(evt)
         };
      }
		
      function onOpen(evt) {
         writeToScreen("CONNECTED");
         doSend("WebSocket rocks");
      }
		
      function onMessage(evt) {
         writeToScreen('<span style = "color: blue;">RESPONSE: ' +
            evt.data+'</span>'); websocket.close();
      }

      function onError(evt) {
         writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
      }
		
      function doSend(message) {
         writeToScreen("SENT: " + message); websocket.send(message);
      }
		
      function writeToScreen(message) {
         var pre = document.createElement("p"); 
         pre.style.wordWrap = "break-word"; 
         pre.innerHTML = message; output.appendChild(pre);
      }
		
      window.addEventListener("load", init, false);
		
   </script>
	
   <h2>WebSocket Test</h2>
   <div id = "output"></div> 
	
</html>

輸出如下所示。

WebSocket Rocks

WebSockets – 關閉連線

關閉事件標誌著伺服器和客戶端之間通訊的結束。可以使用onclose 事件來關閉連線。使用onclose 事件標誌通訊結束之後,伺服器和客戶端之間將無法再傳輸任何訊息。關閉事件也可能由於連線不良而發生。

close() 方法代表“再見”握手。它會終止連線,除非連線再次開啟,否則無法交換任何資料。

與之前的示例類似,當用戶單擊第二個按鈕時,我們將呼叫close() 方法。

var textView = document.getElementById("text-view");
var buttonStop = document.getElementById("stop-button");

buttonStop.onclick = function() {
   // Close the connection, if open.
   if (socket.readyState === WebSocket.OPEN) {
      socket.close();
   }
}

也可以傳遞我們前面提到的程式碼和原因引數,如下所示。

socket.close(1000, "Deliberate disconnection");

以下程式碼全面概述瞭如何關閉或斷開 WebSocket 連線:

<!DOCTYPE html>
<html>
   <meta charset = "utf-8" />
   <title>WebSocket Test</title>

   <script language = "javascript" type = "text/javascript">
      var wsUri = "ws://echo.websocket.org/";
      var output;
	
      function init() {
         output = document.getElementById("output");
         testWebSocket();
      }
	
      function testWebSocket() {
         websocket = new WebSocket(wsUri);
		
         websocket.onopen = function(evt) {
            onOpen(evt)
         };
		
         websocket.onclose = function(evt) {
            onClose(evt)
         };
		
         websocket.onmessage = function(evt) {
            onMessage(evt)
         };
		
         websocket.onerror = function(evt) {
            onError(evt)
         };
      }
	
      function onOpen(evt) {
         writeToScreen("CONNECTED");
         doSend("WebSocket rocks");
      }
	
      function onClose(evt) {
         writeToScreen("DISCONNECTED");
      }
	
      function onMessage(evt) {
         writeToScreen('<span style = "color: blue;">RESPONSE: ' + 
            evt.data+'</span>'); websocket.close();
      }
	
      function onError(evt) {
         writeToScreen('<span style = "color: red;">ERROR:</span> '
            + evt.data);
      } 
	
      function doSend(message) {
         writeToScreen("SENT: " + message); websocket.send(message);
      }
	
      function writeToScreen(message) {
         var pre = document.createElement("p"); 
         pre.style.wordWrap = "break-word"; 
         pre.innerHTML = message; 
         output.appendChild(pre);
      }
	
      window.addEventListener("load", init, false);
   </script>
	
   <h2>WebSocket Test</h2>
   <div id = "output"></div>
	
</html>

輸出如下:

WebSocket DISCONNECTED

WebSockets – 工作伺服器

WebSocket 伺服器是一個簡單的程式,它能夠處理 WebSocket 事件和操作。它通常向 WebSocket 客戶端 API 公開類似的方法,並且大多數程式語言都提供了一種實現。下圖說明了 WebSocket 伺服器和 WebSocket 客戶端之間的通訊過程,重點說明了觸發的事件和操作。

下圖顯示了 WebSocket 伺服器和客戶端事件觸發:

Server Client

連線到 Web 伺服器

WebSocket 伺服器的工作方式類似於 WebSocket 客戶端。它在必要時響應事件並執行操作。無論使用哪種程式語言,每個 WebSocket 伺服器都會執行一些特定操作。

它初始化為一個 WebSocket 地址。它處理OnOpen、OnCloseOnMessage 事件,並向客戶端傳送訊息。

建立 WebSocket 伺服器例項

每個 WebSocket 伺服器都需要一個有效的主機和埠。在伺服器中建立 WebSocket 例項的示例如下所示:

var server = new WebSocketServer("ws://:8181");

任何有效的 URL 都可以與先前未使用的埠規範一起使用。記錄已連線的客戶端非常有用,因為它提供具有不同資料或向每個客戶端傳送不同訊息的詳細資訊。

Fleck 使用IwebSocketConnection 介面表示傳入的連線(客戶端)。每當有人連線到或斷開我們的服務時,都可以建立或更新空列表。

var clients = new List<IWebSocketConnection>();

之後,我們可以呼叫Start 方法並等待客戶端連線。啟動後,伺服器能夠接受傳入的連線。在 Fleck 中,Start 方法需要一個引數,該引數指示引發事件的套接字:

server.Start(socket) =>
{
});

OnOpen 事件

OnOpen 事件確定新的客戶端已請求訪問並執行初始握手。應將客戶端新增到列表中,並且可能應儲存與其相關的相關資訊,例如 IP 地址。Fleck 為我們提供了此類資訊,以及連線的唯一識別符號。

server.Start(socket) ⇒ {

   socket.OnOpen = () ⇒ {
      // Add the incoming connection to our list.
      clients.Add(socket);
   }
	
   // Handle the other events here...
});

OnClose 事件

只要客戶端斷開連線,就會觸發OnClose事件。客戶端將從列表中移除,並通知其餘客戶端其斷開連線。

socket.OnClose = () ⇒ {
   // Remove the disconnected client from the list.
   clients.Remove(socket);
};

OnMessage 事件

當客戶端向伺服器傳送資料時,就會觸發OnMessage事件。在這個事件處理程式中,可以將傳入的訊息傳輸到客戶端,或者可能只選擇其中一部分客戶端。

流程很簡單。請注意,此處理程式採用名為message的字串作為引數:

socket.OnMessage = () ⇒ {
   // Display the message on the console.
   Console.WriteLine(message);
};

Send() 方法

Send()方法簡單地將所需訊息傳輸到指定的客戶端。使用Send(),可以在客戶端之間儲存文字或二進位制資料。

OnMessage事件的工作原理如下:

socket.OnMessage = () ⇒ {
   foreach (var client in clients) {
      // Send the message to everyone!
      // Also, send the client connection's unique identifier in order
      // to recognize who is who.
      client.Send(client.ConnectionInfo.Id + " says: " + message);
   }
};

WebSockets - API

API – 定義

API是應用程式程式設計介面(Application Program Interface)的縮寫,是一套用於構建軟體應用程式的例程、協議和工具。

一些重要的特性包括:

  • API指定了軟體元件應該如何互動,並且在程式設計圖形使用者介面(GUI)元件時應該使用API。

  • 一個好的API透過提供所有構建塊來簡化程式的開發。

  • REST通常執行在HTTP之上,經常用於移動應用程式、社交網站、mashup工具和自動化業務流程。

  • REST風格強調透過有限數量的操作(動詞)來增強客戶端和服務之間的互動。

  • 透過為資源分配其自身的唯一通用資源識別符號(URI)來提供靈活性。

  • REST避免歧義,因為每個動詞都有特定的含義(GET、POST、PUT和DELETE)

WebSocket 的優勢

WebSocket解決了一些REST或HTTP本身存在的問題:

雙向通訊

HTTP是一個單向協議,客戶端總是發起請求。伺服器處理並返回響應,然後客戶端使用它。WebSocket是一個雙向協議,沒有預定義的訊息模式,例如請求/響應。客戶端或伺服器都可以向另一方傳送訊息。

全雙工

HTTP允許請求訊息從客戶端到伺服器,然後伺服器將響應訊息傳送回客戶端。在給定時間,要麼客戶端與伺服器通訊,要麼伺服器與客戶端通訊。WebSocket允許客戶端和伺服器彼此獨立地通訊。

單個TCP連線

通常,為HTTP請求啟動一個新的TCP連線,並在收到響應後終止。另一個HTTP請求/響應需要建立一個新的TCP連線。對於WebSocket,使用標準HTTP升級機制升級HTTP連線,並且客戶端和伺服器在WebSocket連線的生命週期內透過相同的TCP連線進行通訊。

下圖顯示了處理N條具有恆定有效負載大小的訊息所花費的時間(以毫秒為單位)。

Single Connection

以下是提供給該圖的原始資料:

Constant Payload

上圖和表顯示,REST開銷隨著訊息數量的增加而增加。這是因為需要啟動和終止許多TCP連線,並且需要傳送和接收許多HTTP標頭。

最後一列特別顯示了完成REST請求所需時間的乘法因子。

第二個圖顯示了透過改變有效負載大小來處理固定數量的訊息所花費的時間。

Websockets Rest

以下是提供給該圖的原始資料:

Constant Number

該圖顯示,處理REST端點請求/響應的增量成本最小,大部分時間都花在連線啟動/終止和遵守HTTP語義上。

結論

WebSocket是一個低階協議。所有內容,包括簡單的請求/響應設計模式、如何建立/更新/刪除資源需求、狀態碼等,都需要在其之上構建。所有這些對於HTTP來說都是明確定義的。

WebSocket是一個有狀態協議,而HTTP是一個無狀態協議。WebSocket連線可以在單個伺服器上垂直擴充套件,而HTTP可以水平擴充套件。WebSocket水平擴充套件有一些專有解決方案,但它們不是基於標準的。HTTP附帶了許多其他好處,例如快取、路由和多路複用。所有這些都需要在WebSocket之上定義。

WebSockets - JavaScript應用程式

下面的程式程式碼描述了使用JavaScript和WebSocket協議的聊天應用程式的工作原理。

<!DOCTYPE html>
<html lang = "en">

   <head>
      <meta charset = utf-8>
      <title>HTML5 Chat</title>
		
      <body>
		
         <section id = "wrapper">
			
            <header>
               <h1>HTML5 Chat</h1>
            </header>
				
            <style>
               #chat { width: 97%; }
               .message { font-weight: bold; }
               .message:before { content: ' '; color: #bbb; font-size: 14px; }
					
               #log {
                  overflow: auto;
                  max-height: 300px;
                  list-style: none;
                  padding: 0;
               }
					
               #log li {
                  border-top: 1px solid #ccc;
                  margin: 0;
                  padding: 10px 0;
               }
					
               body {
                  font: normal 16px/20px "Helvetica Neue", Helvetica, sans-serif;
                  background: rgb(237, 237, 236);
                  margin: 0;
                  margin-top: 40px;
                  padding: 0;
               }
					
               section, header {
                  display: block;
               }
					
               #wrapper {
                  width: 600px;
                  margin: 0 auto;
                  background: #fff;
                  border-radius: 10px;
                  border-top: 1px solid #fff;
                  padding-bottom: 16px;
               }
					
               h1 {
                  padding-top: 10px;
               }
					
               h2 {
                  font-size: 100%;
                  font-style: italic;
               }
					
               header, article > * {
                  margin: 20px;
               }
					
               #status {
                  padding: 5px;
                  color: #fff;
                  background: #ccc;
               }
					
               #status.fail {
                  background: #c00;
               }
					
               #status.success {
                  background: #0c0;
               }
					
               #status.offline {
                  background: #c00;
               }
					
               #status.online {
                  background: #0c0;
               }
					
               #html5badge {
                  margin-left: -30px;
                  border: 0;
               }
					
               #html5badge img {
                  border: 0;
               }
            </style>
				
            <article>
				
               <form onsubmit = "addMessage(); return false;">
                  <input type = "text" id = "chat" placeholder = "type and press 
                  enter to chat" />
               </form>
					
               <p id = "status">Not connected</p>
               <p>Users connected: <span id = "connected">0
                  </span></p>
               <ul id = "log"></ul>
					
            </article>
				
            <script>
               connected = document.getElementById("connected");
               log = document.getElementById("log");
               chat = document.getElementById("chat");
               form = chat.form;
               state = document.getElementById("status");
					
               if (window.WebSocket === undefined) {
                  state.innerHTML = "sockets not supported";
                  state.className = "fail";
               }else {
                  if (typeof String.prototype.startsWith != "function") {
                     String.prototype.startsWith = function (str) {
                        return this.indexOf(str) == 0;
                     };
                  }
						
                  window.addEventListener("load", onLoad, false);
               }
					
               function onLoad() {
                  var wsUri = "ws://127.0.0.1:7777";
                  websocket = new WebSocket(wsUri);
                  websocket.onopen = function(evt) { onOpen(evt) };
                  websocket.onclose = function(evt) { onClose(evt) };
                  websocket.onmessage = function(evt) { onMessage(evt) };
                  websocket.onerror = function(evt) { onError(evt) };
               }
					
               function onOpen(evt) {
                  state.className = "success";
                  state.innerHTML = "Connected to server";
               }
					
               function onClose(evt) {
                  state.className = "fail";
                  state.innerHTML = "Not connected";
                  connected.innerHTML = "0";
               }
					
               function onMessage(evt) {
                  // There are two types of messages:
                  // 1. a chat participant message itself
                  // 2. a message with a number of connected chat participants
                  var message = evt.data;
						
                  if (message.startsWith("log:")) {
                     message = message.slice("log:".length);
                     log.innerHTML = '<li class = "message">' + 
                        message + "</li>" + log.innerHTML;
                  }else if (message.startsWith("connected:")) {
                     message = message.slice("connected:".length);
                     connected.innerHTML = message;
                  }
               }
					
               function onError(evt) {
                  state.className = "fail";
                  state.innerHTML = "Communication error";
               }
					
               function addMessage() {
                  var message = chat.value;
                  chat.value = "";
                  websocket.send(message);
               }
					
            </script>
				
         </section>
			
      </body>
		
   </head>	
	
</html>

下面討論了聊天應用程式的關鍵功能和輸出:

要進行測試,請開啟兩個支援WebSocket的視窗,在上面鍵入訊息並按回車鍵。這將啟用聊天應用程式的功能。

如果未建立連線,則輸出如下所示。

HTML5 Chat

成功的聊天通訊輸出如下所示。

Browser Support

WebSockets - 與伺服器通訊

Web在很大程度上是圍繞HTTP的請求/響應範例構建的。客戶端載入網頁,然後在使用者點選下一頁之前不會發生任何事情。大約在2005年,AJAX開始使Web感覺更加動態。儘管如此,所有HTTP通訊都由客戶端控制,這需要使用者互動或定期輪詢才能從伺服器載入新資料。

允許伺服器在知道有新資料可用時立即將資料傳送給客戶端的技術已經存在了一段時間。它們被稱為“推送”“Comet”

使用長輪詢,客戶端開啟到伺服器的HTTP連線,該連線保持開啟狀態直到傳送響應。每當伺服器實際擁有新資料時,它都會發送響應。長輪詢和其他技術執行良好。然而,所有這些都存在一個共同的問題,它們帶有HTTP的開銷,這使得它們不適合低延遲應用程式。例如,瀏覽器中的多人射擊遊戲或任何其他具有即時元件的線上遊戲。

將套接字引入Web

WebSocket規範定義了一個API,用於在Web瀏覽器和伺服器之間建立“套接字”連線。簡單來說,客戶端和伺服器之間存在持久連線,並且雙方都可以隨時開始傳送資料。

可以使用建構函式簡單地開啟WebSocket連線:

var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);

ws是WebSocket連線的新URL模式。還有wss,用於安全WebSocket連線,就像https用於安全HTTP連線一樣。

立即向連線附加一些事件處理程式,可以讓你知道連線何時開啟、收到傳入訊息或發生錯誤。

第二個引數接受可選的子協議。它可以是一個字串或一個字串陣列。每個字串都應該表示一個子協議名稱,伺服器只接受陣列中傳遞的子協議之一。可以透過訪問WebSocket物件的protocol屬性來確定已接受的子協議

// When the connection is open, send some data to the server
connection.onopen = function () {
   connection.send('Ping'); // Send the message 'Ping' to the server
};

// Log errors
connection.onerror = function (error) {
   console.log('WebSocket Error ' + error);
};

// Log messages from the server
connection.onmessage = function (e) {
   console.log('Server: ' + e.data);
};

與伺服器通訊

一旦我們與伺服器建立連線(當開啟事件觸發時),我們就可以開始使用連線物件上的send(your message)方法向伺服器傳送資料。它過去只支援字串,但在最新的規範中,它現在也可以傳送二進位制訊息了。要傳送二進位制資料,可以使用Blob或ArrayBuffer物件。

// Sending String
connection.send('your message');

// 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];
}

connection.send(binary.buffer);

// Sending file as Blob
var file = document.querySelector('input[type = "file"]').files[0];
connection.send(file);

同樣,伺服器也可能隨時向我們傳送訊息。每當發生這種情況時,onmessage回撥就會觸發。回撥接收一個事件物件,實際訊息可以透過data屬性訪問。

在最新的規範中,WebSocket也可以接收二進位制訊息。二進位制幀可以以Blob或ArrayBuffer格式接收。要指定接收到的二進位制的格式,請將WebSocket物件的binaryType屬性設定為'blob'或'arraybuffer'。預設格式為'blob'。

// Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'
connection.binaryType = 'arraybuffer';
connection.onmessage = function(e) {
   console.log(e.data.byteLength); // ArrayBuffer object if binary
};

WebSocket的另一個新增功能是擴充套件。使用擴充套件,可以傳送壓縮、多路複用等幀。

// Determining accepted extensions
console.log(connection.extensions);

跨域通訊

作為一個現代協議,跨域通訊直接整合到WebSocket中。WebSocket允許在任何域上的各方之間進行通訊。伺服器決定是將其服務提供給所有客戶端,還是隻提供給位於一組明確定義的域中的客戶端。

代理伺服器

每項新技術都會帶來一組新的問題。對於WebSocket來說,它與代理伺服器的相容性就是一個問題,代理伺服器在大多數公司網路中都會協調HTTP連線。WebSocket協議使用HTTP升級系統(通常用於HTTP/SSL)將HTTP連線“升級”到WebSocket連線。一些代理伺服器不喜歡這樣做,並將斷開連線。因此,即使給定的客戶端使用WebSocket協議,也可能無法建立連線。這使得下一節更加重要 :)

伺服器端

使用WebSocket為伺服器端應用程式建立了一種全新的使用模式。雖然LAMP等傳統的伺服器堆疊是圍繞HTTP請求/響應週期設計的,但它們通常無法很好地處理大量的開啟的WebSocket連線。同時保持大量連線開啟需要一種能夠以低效能成本接收高併發性的架構。

WebSockets - 安全性

出於安全原因,應設計協議。WebSocket是一個全新的協議,並非所有Web瀏覽器都正確實現了它。例如,其中一些仍然允許混合使用HTTP和WS,儘管規範暗示相反。在本章中,我們將討論使用者應該瞭解的一些常見安全攻擊。

拒絕服務

拒絕服務(DoS)攻擊試圖使機器或網路資源對請求它的使用者不可用。假設有人以無時間間隔或極短的時間間隔向Web伺服器發出無限數量的請求。伺服器無法處理每個連線,要麼停止響應,要麼響應速度太慢。這可以稱為拒絕服務攻擊。

拒絕服務對於終端使用者來說非常令人沮喪,他們甚至無法載入網頁。

DoS攻擊甚至可以應用於對等通訊,迫使P2P網路的客戶端同時連線到受害者的Web伺服器。

中間人攻擊

讓我們透過一個例子來理解這一點。

假設一個人A透過IM客戶端與他的朋友B聊天。一些第三者想要檢視你們交換的訊息。因此,他與這兩個人建立了獨立的連線。他還作為你們通訊的隱形中間人向A和他朋友B傳送訊息。這被稱為中間人攻擊。

對於未加密的連線,中間人型別的攻擊更容易進行,因為入侵者可以直接讀取資料包。當連線被加密時,資訊必須由攻擊者解密,這可能非常困難。

從技術角度來看,攻擊者攔截公開金鑰訊息交換併發送訊息,同時將其請求的金鑰替換為自己的金鑰。顯然,使攻擊者難以工作的可靠策略是將SSH與WebSockets一起使用。

在交換關鍵資料時,大多數情況下,優先使用安全的WSS連線而不是未加密的WS。

XSS

跨站指令碼(XSS)是一種漏洞,允許攻擊者將客戶端指令碼注入網頁或應用程式。攻擊者可以使用你的應用程式中心傳送HTML或Javascript程式碼,並讓此程式碼在客戶端機器上執行。

WebSocket 本地防禦機制

預設情況下,WebSocket協議的設計是安全的。在現實世界中,使用者可能會遇到由於瀏覽器實現不佳而可能出現的一些問題。隨著時間的推移,瀏覽器廠商會立即修復任何問題。

當使用透過SSH(或TLS)的安全WebSocket連線時,會新增額外的安全層。

在WebSocket世界中,主要關注的是安全連線的效能。儘管頂部仍然存在額外的TLS層,但協議本身包含對此類使用的最佳化,此外,WSS透過代理執行得更流暢。

客戶端到伺服器的掩碼

在 WebSocket 伺服器和 WebSocket 客戶端之間傳輸的每條訊息都包含一個特定的金鑰,稱為掩碼金鑰,它允許任何符合 WebSocket 標準的中介能夠解掩碼並檢查訊息。如果中介不符合 WebSocket 標準,則無法影響訊息。實現 WebSocket 協議的瀏覽器負責處理掩碼。

安全工具箱

最後,可以提供一些有用的工具來調查 WebSocket 客戶端和伺服器之間資訊流,分析交換的資料,並識別潛在的風險。

瀏覽器開發者工具

Chrome、Firefox 和 Opera 在開發者支援方面都是優秀的瀏覽器。它們內建的工具可以幫助我們確定客戶端互動和資源的幾乎任何方面。這對安全至關重要。

WebSockets - 移動應用

顧名思義,WebSocket 使用的是網路。網路通常與瀏覽器頁面緊密相連,因為它們是線上顯示資料的主要手段。但是,非瀏覽器程式也使用線上資料傳輸。

iPhone(最初)和iPad(之後)的釋出,引入了無需使用網路瀏覽器即可實現網路互連的全新世界。取而代之的是,新型智慧手機和平板電腦利用原生應用程式的功能,提供獨特的使用者體驗。

移動端的重要性

目前,全球有數十億部活躍的智慧手機。也就是說,您的應用程式有數百萬潛在客戶。這些人使用他們的手機來完成日常任務、瀏覽網際網路、溝通或購物。

智慧手機已成為應用程式的代名詞。如今,任何使用者可以想到的用途都有相應的應用程式。大多數應用程式連線到網際網路以檢索資料、進行交易、收集新聞等等。

最好是利用現有的 WebSocket 知識,開發一個在智慧手機或平板電腦上原生執行的 WebSocket 客戶端。

原生移動應用與移動網站

這是一個常見的衝突,答案通常取決於目標使用者的需求。如果使用者熟悉現代設計趨勢,那麼設計一個響應式且對移動裝置友好的網站是必須的。但是,終端使用者必須確保內容(這才是真正重要的)透過智慧手機訪問與透過經典桌面瀏覽器訪問一樣容易。

毫無疑問,WebSocket 網路應用程式可以在任何相容 HTML5 的瀏覽器上執行,包括移動瀏覽器,例如 iOS 版 Safari 和移動版 Chrome。因此,無需擔心與智慧手機的相容性問題。

先決條件

為了開發智慧手機應用程式,需要安裝開發工具和 SDK。

smartphone

WebSockets 可以充當在連線的移動和平板客戶端之間傳輸訊息的通用中心。我們可以實現一個原生 iOS 應用程式,它與 WebSocket 伺服器的通訊方式就像 HTML5 JavaScript 客戶端一樣。

廣告
© . All rights reserved.