WebSocket

**WebSocket** 是一種網路傳輸協定，可在單個 TCP 連接上進行全雙工通訊，位於 OSI 模型的應用層。WebSocket 協定在 2011 年由 IETF 標準化為 RFC 6455，後由 RFC 7936 補充規範。Web IDL 中的 WebSocket API 由 W3C 標準化。

WebSocket 使得客戶端和伺服器之間的資料交換變得更加簡單，允許伺服器端主動向客戶端推播資料。在 WebSocket API 中，瀏覽器和伺服器只需要完成一次交握，兩者之間就可以建立永續性的連接，並進行雙向資料傳輸。

## 簡介

WebSocket 是一種與 HTTP 不同的協定。兩者都位於 OSI 模型的應用層，並且都依賴於傳輸層的 TCP 協定。
雖然它們不同，但是 RFC 6455 中規定：`it is designed to work over HTTP ports 80 and 443 as well as to support HTTP proxies and intermediaries`（WebSocket 通過 HTTP 埠 80 和 443 進行工作，並支援 HTTP 代理和中介），從而使其與 HTTP 協定相容。
為了實現相容性，WebSocket 交握使用 HTTP Upgrade 頭從 HTTP 協定更改為 WebSocket 協定。

WebSocket 協定支援 Web 瀏覽器（或其他客戶端應用程式）與 Web 伺服器之間的互動，具有較低的開銷，便於實現客戶端與伺服器的即時資料傳輸。
伺服器可以通過標準化的方式來實現，而無需客戶端首先請求內容，並允許訊息在保持連接打開的同時來回傳遞。通過這種方式，可以在客戶端和伺服器之間進行雙向持續對話。
通訊通過 TCP 埠 80 或 443 完成，這在防火牆阻止非 Web 網路連接的環境下是有益的。另外，Comet 之類的技術以非標準化的方式實現了類似的雙向通訊。

大多數瀏覽器都支援該協定，包括 Google Chrome、Firefox、Safari、Microsoft Edge、Internet Explorer 和 Opera。

與 HTTP 不同，WebSocket 提供全雙工通訊。此外，WebSocket 還可以在 TCP 之上實現訊息流。TCP 單獨處理位元組流，沒有原生的訊息概念。
在 WebSocket 之前，使用 Comet 可以實現全雙工通訊。但是 Comet 存在 TCP 交握和 HTTP 頭的開銷，因此對於小訊息來說效率很低。WebSocket 協定旨在解決這些問題。

WebSocket 協定規範將 `ws`（WebSocket）和 `wss`（WebSocket Secure）定義為兩個新的統一資源識別碼（URI）方案，分別對應明文和加密連接。除了方案名稱和片段 ID（不支援 `#`）之外，其餘的 URI 組件都被定義為此 URI 的通用語法。

使用瀏覽器開發人員工具，開發人員可以檢查 WebSocket 交握以及 WebSocket 框架。

## 歷史

WebSocket 最初在 HTML5 規範中被參照為 TCPConnection，作為基於 TCP 的通訊端 API 的預留位置。2008 年 6 月，Michael Carter 進行了一系列討論，最終形成了稱為 WebSocket 的協定。

「WebSocket」這個名字是 Ian Hickson 和 Michael Carter 之後在 #whatwg IRC 聊天室創造的，隨後由 Ian Hickson 撰寫並列入 HTML5 規範，並在 Michael Carter 的 Cometdaily 部落格上宣布。
2009 年 12 月，Google Chrome 4 是第一個提供標準支援的瀏覽器，預設情況下啟用了 WebSocket。WebSocket 協定的開發隨後於 2010 年 2 月從 W3C 和 WHATWG 小組轉移到 IETF，並在 Ian Hickson 的指導下進行了兩次修訂。

該協定被多個瀏覽器預設支援並啟用後，RFC 於 2011 年 12 月在 Ian Fette 下完成。

## 背景

早期，很多網站為了實現推播技術，所用的技術都是輪詢。輪詢是指由瀏覽器每隔一段時間（如每秒）向伺服器發出 HTTP 請求，然後伺服器返回最新的資料給客戶端。這種傳統的模式帶來很明顯的缺點，即瀏覽器需要不斷的向伺服器發出請求，然而 HTTP 請求與回覆可能會包含較長的頭部，其中真正有效的資料可能只是很小的一部分，所以這樣會消耗很多頻寬資源。

比較新的輪詢技術是 Comet。這種技術雖然可以實現雙向通訊，但仍然需要反覆發出請求。而且在 Comet 中普遍採用的 HTTP 長連接也會消耗伺服器資源。

在這種情況下，HTML5 定義了 WebSocket 協定，能更好的節省伺服器資源和頻寬，並且能夠更即時地進行通訊。

Websocket 使用 `ws` 或 `wss` 的統一資源標誌符（URI）。其中 `wss` 表示使用了 TLS 的 Websocket。如：

```
ws://example.com/wsapi
wss://secure.example.com/wsapi
```

Websocket 與 HTTP 和 HTTPS 使用相同的 TCP 埠，可以繞過大多數防火牆的限制。預設情況下，Websocket 協定使用 80 埠；執行在 TLS 之上時，預設使用 443 埠。

## 優點

* 較少的控制開銷。在連接建立後，伺服器和客戶端之間交換資料時，用於協定控制的封包頭部相對較小。在不包含擴充的情況下，對於伺服器到客戶端的內容，此頭部大小只有 2 至 10 位元組（和封包長度有關）；對於客戶端到伺服器的內容，此頭部還需要加上額外的 4 位元組的遮罩。相對於 HTTP 請求每次都要攜帶完整的頭部，此項開銷顯著減少了。
* 更強的即時性。由於協定是全雙工的，所以伺服器可以隨時主動給客戶端下發資料。相對於 HTTP 請求需要等待客戶端發起請求伺服器端才能回應，延遲明顯更少；即使是和 Comet 等類似的長輪詢比較，其也能在短時間內更多次地傳遞資料。
* 保持連接狀態。與 HTTP 不同的是，Websocket 需要先建立連接，這就使得其成為一種有狀態的協定，之後通訊時可以省略部分狀態資訊。而 HTTP 請求可能需要在每個請求都攜帶狀態資訊（如身分認證等）。
* 更好的二進位支援。Websocket 定義了二進位影格，相對 HTTP，可以更輕鬆地處理二進位內容。
* 可以支援擴充。Websocket 定義了擴充，使用者可以擴充協定、實現部分自訂的子協定。如部分瀏覽器支援壓縮等。
* 更好的壓縮效果。相對於 HTTP 壓縮，Websocket 在適當的擴充支援下，可以沿用之前內容的上下文，在傳遞類似的資料時，可以顯著地提高壓縮率。

## 交握協定

WebSocket 是獨立的、建立在 TCP 上的協定。

Websocket 通過 HTTP/1.1 協定的 101 狀態碼進行交握。

為了建立 Websocket 連接，需要通過瀏覽器發出請求，之後伺服器進行回應，這個過程通常稱為「交握」（Handshaking）。

### 例子

一個典型的 Websocket 交握請求如下：

客戶端請求：

```
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
```

### 欄位說明

* Connection 必須設定 Upgrade，表示客戶端希望連接升級。
* Upgrade 欄位必須設定 Websocket，表示希望升級到 Websocket 協定。
* Sec-WebSocket-Key 是隨機的字串，伺服器端會用這些資料來構造出一個 SHA-1 的資訊摘要。把「Sec-WebSocket-Key」加上一個特殊字串「258EAFA5-E914-47DA-95CA-C5AB0DC85B11」，然後計算 SHA-1 摘要，之後進行 Base64 編碼，將結果做為「Sec-WebSocket-Accept」頭的值，返回給客戶端。如此操作，可以儘量避免普通 HTTP 請求被誤認為 Websocket 協定。
* Sec-WebSocket-Version 表示支援的 Websocket 版本。RFC6455 要求使用的版本是 13，之前草案的版本均應當棄用。
* Origin 欄位是必須的。如果缺少 origin 欄位，WebSocket 伺服器需要回覆 HTTP 403 狀態碼（禁止存取）。
* 其他一些定義在 HTTP 協定中的欄位，如 Cookie 等，也可以在 Websocket 中使用。

## 瀏覽器支援

WebSocket 協定的安全版本在 Firefox 6、Safari 6、Google Chrome 14、Opera 12.10 和 Internet Explorer 10 中實現。詳細的協定測試套件報告列出了這些瀏覽器與特定協定方面的一致性。

Opera 11 和 Safari 5 中實現了較舊的，不太安全的協定版本，以及 iOS 4.2 中的 Safari 行動版本。OS7 中的 BlackBerry Browser 實現了 WebSockets。由於存在漏洞，它在 Firefox 4 和 5 以及 Opera 11 中被禁用。

所有最新的瀏覽器支援最新規範（RFC 6455）的 WebSocket 協定。一個詳細的測試報告列出了這些瀏覽器支援的 Websocket 版本。

1 基於 Gecko 6–10 版本的瀏覽器的 WebSocket 物件為「mozwebsocket」，需要添加額外的代碼。

## 伺服器

在伺服器方面，網上都有不同對 websocket 支援的伺服器：

* PHP - https://code.google.com/p/phpwebsocket/ （頁面存檔備份，存於網際網路檔案館）
* jetty （頁面存檔備份，存於網際網路檔案館）（版本 7 開始支援 websocket）
* netty （頁面存檔備份，存於網際網路檔案館）
* Ruby - https://github.com/gimite/web-socket-ruby （頁面存檔備份，存於網際網路檔案館）
* Kaazing
* Tomcat（7.0.27 支援 websocket，建議用 tomcat8，7.0.27 中的介面已經過時）
* WebLogic （頁面存檔備份，存於網際網路檔案館）（12.1.2 開始支援）
* Node.js - https://github.com/Worlize/WebSocket-Node （頁面存檔備份，存於網際網路檔案館）
* Node.js - http://socket.io （頁面存檔備份，存於網際網路檔案館）
* Nginx
* mojolicious （頁面存檔備份，存於網際網路檔案館）
* Python - https://github.com/abourget/gevent-socketio （頁面存檔備份，存於網際網路檔案館）
* Django （頁面存檔備份，存於網際網路檔案館）
* erlang - https://github.com/ninenines/cowboy （頁面存檔備份，存於網際網路檔案館）

## 參考資料