1 websocket簡介
websocket是一種網絡傳輸協議,可在單個tcp鏈接上進行全雙工通信,位於OSI模型的應用層。
WebSocket 與 HTTP/2 一樣,都是爲了解決 HTTP 某方面的缺陷而誕生的。HTTP/2 針對的是“隊頭阻塞”,而 WebSocket 針對的是“請求 - 應答”通信模式。
WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就可以創建持久性的連接,並進行雙向數據傳輸。
WebSocket協議在2011年由IETF標準化爲RFC 6455,後由RFC 7936補充規範。
- RFC6455
- RFC7936
WebSocket是一種與HTTP不同的協議。兩者都位於OSI模型的應用層,並且都依賴於傳輸層的TCP協議。 雖然它們不同,但RFC 6455規定:“WebSocket設計爲通過80和443端口工作,以及支持HTTP代理和中介”,從而使其與HTTP協議兼容。 爲了實現兼容性,WebSocket握手使用HTTP Upgrade頭[1]從HTTP協議更改爲WebSocket協議。
WebSocket協議支持Web瀏覽器(或其他客戶端應用程序)與Web服務器之間的交互,具有較低的開銷,便於實現客戶端與服務器的實時數據傳輸。 服務器可以通過標準化的方式來實現,而無需客戶端首先請求內容,並允許消息在保持連接打開的同時來回傳遞。通過這種方式,可以在客戶端和服務器之間進行雙向持續對話。 通信通過TCP端口80或443完成,這在防火牆阻止非Web網絡連接的環境下是有益的。另外,Comet之類的技術以非標準化的方式實現了類似的雙向通信。
1.1 websocket特點
websocket的特點如下所示:
- 提供全雙工通信;
- 還可以在TCP之上啓用消息流;
- WebSocket協議規範將
ws
(WebSocket)和wss
(WebSocket Secure)定義爲兩個新的統一資源標識符(URI)方案,分別對應明文和加密連接。 - WebSocket 的默認端口也選擇了 80 和 443,因爲現在互聯網上的防火牆屏蔽了絕大多數的端口,只對 HTTP 的 80、443 端口“放行”,所以 WebSocket 就可以“僞裝”成 HTTP 協議,比較容易地“穿透”防火牆,與服務器建立連接。
- WebSocket 更側重於“實時通信”,而 HTTP/2 更側重於提高傳輸效率。
1.2 服務器支持框架
在服務器方面,網上都有不同對websocket支持的服務器:
- php - http://code.google.com/p/phpwebsocket/
- jetty - https://www.eclipse.org/jetty/(版本7開始支持websocket)
- netty - http://www.jboss.org/netty
- ruby - http://github.com/gimite/web-socket-ruby
- Kaazing - https://web.archive.org/web/20100923224709/http://www.kaazing.org/confluence/display/KAAZING/Home
- Tomcat - http://tomcat.apache.org/(7.0.27支持websocket,建議用tomcat8,7.0.27中的接口已經過時)
- WebLogic - http://www.oracle.com/us/products/middleware/cloud-app-foundation/weblogic/overview/index.html(12.1.2開始支持)
- node.js - https://github.com/Worlize/WebSocket-Node
- node.js - http://socket.io
- nginx - http://nginx.com/
- mojolicious - http://mojolicio.us/
- python - https://github.com/abourget/gevent-socketio
- Django - https://github.com/stephenmcd/django-socketio
- erlang - https://github.com/ninenines/cowboy.git
1.3 websocket 幀結構
“結束標誌位 + 操作碼 + 幀長度 + 掩碼”
- 第一位“FIN”:相當於 HTTP/2 裏的“END_STREAM”,表示數據發送完畢。一個消息可以拆成多個幀,接收方看到“FIN”後,就可以把前面的幀拼起來,組成完整的消息。
- “FIN”後面的三個位是保留位,目前沒有任何意義,但必須是 0。
- “Opcode”,操作碼:其實就是幀類型,比如 1 表示幀內容是純文本,2 表示幀內容是二進制數據,8 是關閉連接,9 和 10 分別是連接保活的 PING 和 PONG。
- 掩碼標誌位“MASK”:表示幀內容是否使用異或操作(xor)做簡單的加密。目前的 WebSocket 標準規定,客戶端發送數據必須使用掩碼,而服務器發送則必須不使用掩碼。
- “Payload len”:表示幀內容的長度。它是另一種變長編碼,最少 7 位,最多是 7+64 位,也就是額外增加 8 個字節,所以一個 WebSocket 幀最大是 2^64。
- “Masking-key”:掩碼密鑰,它是由上面的標誌位“MASK”決定的,如果使用掩碼就是 4 個字節的隨機數,否則就不存在。
2 websocket握手過程
如下所示是筆者根據netty ws樣例進行瀏覽器瀏覽的一個用例,可以看到這個websocket鏈接可以分爲三個階段:
- 建立tcp鏈接;
- 客戶單採用http頭進行ws握手;
- 協議由http切換成ws傳輸數據;
2.1 客戶端的第一個GET
如下圖所示是客戶端websocket鏈接的第一個報文。
WebSocket 的握手是一個標準的 HTTP GET 請求,但要帶上兩個協議升級的專用頭字段:
- “Connection: Upgrade”,表示要求協議“升級”;
- “Upgrade: websocket”,表示要“升級”成 WebSocket 協議。
另外,爲了防止普通的 HTTP 消息被“意外”識別成 WebSocket,握手消息還增加了兩個額外的認證用頭字段(所謂的“挑戰”,Challenge):
- Sec-WebSocket-Key:一個 Base64 編碼的 16 字節隨機數,作爲簡單的認證密鑰;
- Sec-WebSocket-Version:協議的版本號,當前必須是 13。
2.2 服務端響應 SwitchingProtocol
服務器收到 HTTP 請求報文,看到上面的四個字段,就知道這不是一個普通的 GET 請求,而是 WebSocket 的升級請求,於是就不走普通的 HTTP 處理流程,而是構造一個特殊的“101 Switching Protocols”響應報文,通知客戶端,接下來就不用 HTTP 了,全改用 WebSocket 協議通信。(有點像 TLS 的“Change Cipher Spec”)
WebSocket 的握手響應報文也是有特殊格式的,要用字段“Sec-WebSocket-Accept”驗證客戶端請求報文,同樣也是爲了防止誤連接。
具體的做法是把請求頭裏“Sec-WebSocket-Key”的值,加上一個專用的 UUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,再計算 SHA-1 摘要。
客戶端收到響應報文,就可以用同樣的算法,比對值是否相等,如果相等,就說明返回的報文確實是剛纔握手時連接的服務器,認證成功。
握手完成,後續傳輸的數據就不再是 HTTP 報文,而是 WebSocket 格式的二進制幀了。
2.3 客戶端ws報文
客戶端和服務端編碼則更好驗證了之前開頭的總結:
WebSocket 的幀頭就四個部分:“結束標誌位 + 操作碼 + 幀長度 + 掩碼”;