什么是WebSokcet?
WebSocket是一種協議,並且是各大主流瀏覽器作為客戶端支持的協議。它的目標就是用來替代基於 XMLHTTPRequest和長輪詢的解決方案。應用在時時彈幕,消息推送,棋牌游戲等需要及時通訊的業務場景。
握手
WebSocket連接有兩個階段:握手(handshake)和數據傳輸(data transfer)。此握手非TCP三次握手,但是目的差不多,就是客戶端告訴瀏覽器我想要使用WebSocket協議進行通訊。客戶端需要發送如下請求,它是一個 HTTP Upgrade
請求:
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
客戶端發送握手請求
- Uri要滿足如下格式:
ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
- 在與服務端建立連接時,客戶端必須方發送握手請求,請求是一個HTTP的升級協議(Upgrade)請求。並且該請求必須滿足
- 握手請求必須是一個正常的HTTP請求。
- 請求方法必須為GET,並且HTTP協議最低為1.1
- 請求頭必須包含Host
- 請求頭必須包含Upgrade,並且值為websocket
- 請求頭必須包含Connection,並且值為Upgrade
- 請求頭必須包含Sec-WebSocket-Key,值為經過Base64轉換的長度為16字節的一組數據
- 請求頭必須包含Origin,如果客戶端是瀏覽器這個值肯定是有的,如果非瀏覽器的客戶端,這個值可以隨意改。
- 請求頭必須包含Sec-WebSocket-Version,並且值為13
- 請求頭可以帶一個Sec-WebSocket-Protocol,這個值告訴服務端客戶端想用的子協議,多個用逗號分開
- 請求頭可以帶一個Sec-WebSocket-Extensions,這個值告訴服務端客戶端支持的協議級別的擴展。
- 請求頭可以帶一個和權限校驗相關的頭,例如Cookie,Authentication等
當客戶端將握手請求發出去之后,就要等待服務端的響應了。當服務端成功響應之后,客戶端還需要做如下校驗:
- 返回的響應碼非101,例如401,500,403,503 等等,客戶端連接失敗
- 返回的響應頭部不包含Upgrade或者Upgrade的值不是websocket,客戶端連接失敗
- 返回的響應頭部不包含Connection或者Connection的值不是Upgrade,客戶端連接失敗
- 返回的響應頭部不包含Sec-WebSocket-Accept或者Sec-WebSocket-Accept的值並不是
Base64(SHA1(Sec-WebSocket-Key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
,客戶端連接失敗 - 返回的響應頭部Sec-WebSocket-Extensions中的值並不是客戶端發送的Sec-WebSocket-Extensions中的值,客戶端連接失敗
- 返回的響應頭部Sec-WebSocket-Protocol中的值並不是客戶端發送的Sec-WebSocket-Protocol中的值,客戶端連接失敗
服務端接收握手請求
如果服務端在處理請求過程中不滿足一下任何一點,服務端都會終止處理該請求
- 必須是HTTP1.1+的GET請求
- 包含Host請求頭
- 包含Upgrade值為WebSocket的請求頭
- 包含Connection值為Upgrade的請求頭
- 包含Sec-WebSocket-Key值為16字節長度的Base64字符串
- 包含Sec-WebSocket-Version值為13的請求頭
- 非必須:Origin
- 非必須:Sec-WebSocket-Protocol
- 非必須:Sec-WebSocket-Extensions
當服務端確定這是一個正常的握手請求並且願意處理此請求,那么服務端需要回應一個HTTP響應:
- 狀態碼必須為 101 Switching Protocol
- Upgrade:WebSocket
- Connection:Upgrade
- Sec-WebSocket-Accept,如上文所說,值為:
Base64(SHA1(Sec-WebSocket-Key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
- Sec-WebSocket-Protocol,根據客戶端傳的值
- Sec-WebSocket-Extensions,根據客戶端傳的值
至此,握手結束。連接狀態由CONNECTING
進入OPEN
狀態
協議幀
WebSocket的協議幀格式如下:
- FIN 1bit
包結束標志,1 代表最后一個消息包,0代表某一段消息包 - RSV1, RSV2, RSV3: 每個1bit,共3bit
值為0,除非協議擴展(Extensions)聲明了非0的值的含義。如果服務端收到非0的值,並且沒有相應的定義,那么服務端將直接終止連接。 - Opcode 4bit
x0 后續幀
x1 文本幀
x2 二進制幀
x3-X7 非控制幀預留
x8 關閉連接
x9 PING
xA PONG
xB-xF 控制幀預留 - Mask 1 bit 是否掩碼。客戶端向服務器發送,必須掩碼。服務端向客戶端發送不需掩碼
- PayLoad Length 7bits,7+16bits,7+64bits,如果值為 0-125,則數據包長度為0-125.如果值為126,則后2個字節為數據包長度:16bit。如果值為127,則后8個字節為數據包長度:64bit。
- Masking-Key, 0-4bits.是否有值取決於 Mask 標識位是否為1.
- Extension data X bytes 如果在握手時協商了擴展,會有值,否則為0
- Application data y bytes 剩余消息包
- PayLoad data (x+y)bytes 總消息包=Extension data + Application data.如果有掩碼,解碼公式如下:
body[i] = body[i] ^ body[i % 4]
代碼解析
下面我用tio網絡通訊框架
代碼來解釋一下上文中的內容,不必糾結具體代碼,只要大概理解代碼功能即可。
具體協議升級代碼如下:
以上就是握手部分Http協議升級過程的代碼部分。沒有什么難理解的地方,只要對着文檔要求去實現即可。不過要注意的是,這里是升級協議的過程,如果有其他業務處理,比如訪問權限校驗失敗等,可以直接返回 HttpStatusCode 401.
協議幀解析:
總結
大致過了一遍RFC-6455文檔,發現還是官方文檔中解釋的更詳細的也更清楚一些,但是苦於英語水平不過關,有些部分理解起來比較困難。