什麼是WebSokcet?
WebSocket是一種協議,而且是各大主流瀏覽器做爲客戶端支持的協議。它的目標就是用來替代基於 XMLHTTPRequest和長輪詢的解決方案。應用在時時彈幕,消息推送,棋牌遊戲等須要及時通信的業務場景。html
握手
WebSocket鏈接有兩個階段:握手(handshake)和數據傳輸(data transfer)。此握手非TCP三次握手,可是目的差很少,就是客戶端告訴瀏覽器我想要使用WebSocket協議進行通信。客戶端須要發送以下請求,它是一個 HTTP Upgrade
請求:web
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等
當客戶端將握手請求發出去以後,就要等待服務端的響應了。當服務端成功響應以後,客戶端還須要作以下校驗:websocket
- 返回的響應碼非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
狀態socket
協議幀
WebSocket的協議幀格式以下:
3d
- 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.code
協議幀解析:
總結
大體過了一遍RFC-6455文檔,發現仍是官方文檔中解釋的更詳細的也更清楚一些,可是苦於英語水平不過關,有些部分理解起來比較困難。
參考資料
RFC-6455