本文是對 WebSocket 變化的介紹,是對以下資料的摘錄:
WebSocket 概覽
WebSocket 是一個計算機間的通信協議,它能夠在單個 TCP 鏈接上構建一個雙工的交流通道。WebSocket 協議的標準是由 IETE 在 2011年的 RFC 6455 中制定的。
WebSocket 是一個與 HTTP 并不相同的 TCP 的協議。WebSocket 和 HTTP 協議都是在7層網絡模型(OSI model)中的,并且都依賴第4層的 TCP 協議。盡管兩者并不相同,但 RFC 6455 中聲明 “WebSocket 是可以工作在 HTTP協議 的80和443端口之上的,并且能夠支持 HTTP 代理和中介”,這使得 WebSocket 可以兼容 HTTP 協議。為了實現這個兼容目標,WebSocket 的握手(handshake)使用了 HTTP 的 Upgrade 頭部,從而實現從 HTTP 協議轉換為 WebSocket 協議的目標。
WebSocket 能夠建立客戶端和服務器間的雙向通信,目前很多瀏覽器都已經支持該協議了。同樣,服務端也需要提供相應的支持。
WebSocket 協議標志是 ws
(WebSocket)和 wss
(WebSocket Secure)。
為什么需要 WebSocket
由于 HTTP 是客戶端發起的單向請求,對于聊天室這樣需要服務端推送信息的場景就不是很適合。當然,客戶端可以通過“輪詢”的方式來了解服務端的信息,但是這樣的效率比較低,比較浪費資源(因為必須不停連接,或者 HTTP 連接始終打開)。
和 HTTP 不同,WebSocket 是一個全雙工協議,屬于服務器推送技術的一種。在 WebSocket 之前,在80端口可以通過 Comet 通道實現全雙工。但是由于 TCP 握手和 HTTP 頭部的開銷,對于數據量不大的信息來說,這樣的機制不是很高效。WebSocket 協議的目標就是解決這些問題并且提供相應的安全保障。
握手協議
WebSocket 建立連接的過程如下:
- 客戶端發送 WebSocket 握手請求(和 HTTP 一樣, 每行要以
\r\n
結尾,最后要有一個額外的空行)。
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
- 服務端回應握手請求。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
可以看到,客戶端發送的請求頭部除了 Upgrade
外,還有 Sec-WebSocket-Key
字段,它是包含base64編碼的隨機字節,服務端需要在 WebSocket-Accept
字段中返回這個鍵的hash值。這是為了防止緩存代理重發之前的 WebSocket 會話。
通過類 HTTP 形式的握手形式,可以使得服務端在同一個端口處理 HTTP 或者 WebSocket 協議。一旦 WebSocket 握手完成,通信馬上變換成一個與 HTTP 協議不同的雙向通道。
此外,從安全的角度來說,提供 Origin
標簽是很有必要的,可以避免跨站點的 WebSocket 劫持攻擊。
數據格式
全雙工的通道建立之后,傳輸的數據將會以盡可能小的格式進行封裝:一個小的頭部,緊跟著的是有效數據載體 payload。WebSocket 傳輸的內容被稱為“消息”,這個消息可以被劃分成多個數據幀。這樣在只有一部分初始數據(完整數據還未準備好)的時候就可以提前發送數據了。通過擴展,可以同時多路復用多個數據流(這樣可以避免大負載數據對 socket 的獨占使用)。
FIN(1 bit)
表明消息是否是最后一幀。第一條消息也可能是最后一幀。
RSV1, RSV2, RSV3(每個都是1位)
必須是0,除非擴展定義了非0值的意義。如果收到非0值,并且沒有具體的定義,那么接收端必須使連接失敗。
Opcode(4位)
聲明 “Payload data” 的含義。如果收到一個未知編碼,那么接收端必須使連接失敗。目前有以下這些值:
-
%x0
denotes a continuation frame,表明是一個持續幀。 -
%x1
denotes a text frame,表明是一個文本幀。 -
%x2
denotes a binary frame,表明是一個二進制幀。 -
%x3-7
are reserved for further non-control frames,為未來的非控制幀保留。 -
%x8
denotes a connection close,定義一個連接結束。 -
%x9
denotes a ping,表示 ping 操作。 -
%xA
denotes a pong,表示 pong 操作。 -
%xB-F
are reserved for further control frames,為未來的控制幀保留。
__ Mask__(1 bit)
定義 “Payload data” 是否是隱秘的。如果設置為1,那么 masking-key
中會提供一個值,并且可以根據 5.3節 取消對應的標志。所有客戶端發送到服務端的幀都需要把這位設置為1。
Payload length(7位 或 7+16位 或 7+64位)
表示 “Payload data” 的長度。分為這幾種情況:
- 如果是 0-125,那么就是實際長度。
- 如果是 126,那么接下來的2個字節作為一個16位的無符號整數,表示實際的長度。
- 如果是 127,那么接下來的8個字節作為一個64位的無符號整數,表示實際的長度。
其中多字節表示的長度在網絡中必須按序排列。值得注意的是,在所有例子中,長度必須按最短的表示方式表示。Payload 數據的長度是由 “Extension data” (擴展數據)和 “Extension data”(應用數據)組成的,當擴展數據的長度為0時,payload 數據的長度就是應用數據的長度。
Masking-key(0位或者4位)
所有從客戶端發送到服務端的數據幀都需要有一個32位的 Masking-key
。當 mask
位被設置為1的時候,這個域存在;當 mask
位被設置為0的時候,這個域不存在。可以看 5.3節 來進一步了解。
Payload data(x+y位)
由 "Extension data"(擴展數據)和 "Application data"(應用數據)組成。
- Extension data(x位):擴展數據默認是0位,除非實現了一個擴展協議。每一個擴展的協議必須說明擴展數據的長度、長度是如何計算的、以及在握手階段擴展是如何約定的。如果該部分數據存在,那么將會被記錄到總的 payload 長度中。
- Application data(y位):應用數據在擴展數據之后,應用數據的長度等于 payload 的長度減去擴展數據的長度。
其它資料
進一步了解可以參考這些文檔:
- w3cschool
- HTML5 WebSocket
- WebSocket - Web APIs | MDN
- websocket.org
- WebSocket 的實現比較
- WebSocket 是什么原理?為什么可以實現持久連接?
推薦一款非常特別的 WebSocket 服務器:Websocketd。它的最大特點,就是后臺腳本不限語言,標準輸入(stdin)就是 WebSocket 的輸入,標準輸出(stdout)就是 WebSocket 的輸出。