微信小程序、小遊戲的火爆,都讓WebSocket的應用變得無處不在。針對這個主題,筆者打算作一個系列博客,旨在由淺入深的介紹WebSocket以及在Springboot和JS中如何快速構建和使用WebSocket提供的能力。前端
本系列計劃包含以下幾篇文章:nginx
第一篇,什麼是WebSocket以及它的用途。
第二篇,Spring中如何利用STOMP快速構建WebSocket廣播式消息模式
第三篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的消息模式(1)
第四篇,Springboot中,如何利用WebSocket和STOMP快速構建點對點的消息模式(2)
第五篇,Springboot中,實現網頁聊天室之自定義WebSocket消息代理
第六篇,Springboot中,實現更靈活的WebSocketweb
首先由一個典型場景引出WebSocket的需求場景,進而闡述WebSocket協議自己。包括其定義,特色以及握手過程報文的解讀。最後,再次從協議維度和實現長鏈接的方法兩個方面,對比了HTTP與WebSocket的異同,讓讀者對WebSocket有更深的認識和理解。ajax
爲了照顧到剛接觸前/後端開發的新手,做爲系列的開篇文章,本着由淺入深的目的,本文采用了較爲詳盡的解讀方式,老鳥亦歡迎收藏參考。後續篇章也會陸續更新上線,敬請期待。算法
小銘購買了一張機票,在出發前的幾個小時,他但願經過航班動態查詢軟件,實時的瞭解航班動態,如是否有延誤,取消等信息。小程序
那麼這時查詢軟件與服務器交互以下圖:後端
很容易理解,每一次航班動態查詢,client都須要向server發起請求,而後等待server端的響應結果。當client收到響應後,本次通訊的生命週期即宣告結束。但是小銘說: 我但願只查詢一次航班動態,當航班有更新時,服務器能夠主動把最新的航班動態信息推送給我!微信小程序
怎麼辦?聰明的程序猿想到了以下的辦法:瀏覽器
即程序內部在小銘第一次請求時,記錄下這個請求信息和響應信息,每隔固定時間(例如1分鐘)請求一次服務器,服務器返回當前最新狀態,對比以前收到的信息,若是相比有變動,則通知小銘;緩存
客戶端:有沒有新動態(Request)
服務端:正常起飛(Response)
客戶端:啦啦啦,有沒有新動態(Request)
服務端:正常起飛。。(Response)
客戶端:有沒有新動態(Request)
服務端:你好煩啊,正常起飛。。(Response)
客戶端:有沒有新動態(Request)
服務端:好啦好啦,有啦給你,延誤30分鐘。。(Response)
客戶端:有沒有新動態(Request)
服務端:沒有。。。(Response)
即程序內部依然採用輪詢方式,不過比上一個方案相比,採起了阻塞方式。(一直打電話,沒收到就不掛電話),也就是說,客戶端發起鏈接後,若是服務端沒消息,就一直不返回Response給客戶端。直到有消息才通知小銘,以後客戶端再次創建鏈接,周而復始。
客戶端:有沒有新動態,沒有的話就等有了才返回給我吧(Request)
服務端:等到有動態的時候再告訴你。(過了一下子)來了,給你,延誤30分鐘(Response)
客戶端:有沒有新動態,沒有的話就等有了才返回給我吧(Request)
從整個交互的過程來看,這兩種都是很是消耗資源的。
因此它們都有可能發生下面這種狀況:
客戶端:有新動態麼?
服務端:問的人太多了,線路正忙,請稍後再試(503 Server Unavailable)
客戶端:。。。。好吧,有新動態麼?
服務端:問的人太多了,線路正忙,請稍後再試(503 Server Unavailable)
客戶端:。。。。服務端你到底行不行啊。。!@#$%$^&
經過上面這個例子,總結一下咱們能夠看出,這兩種採用HTTP的方式都不是最好的方式,體如今:
那如今想要達到小銘的要求,該怎麼辦呢?
說了這麼半天了,讓咱們言歸正傳。基於上述的需求和矛盾,WebSocket出現了。
讓咱們先來看看,使用了WebSocket之後,上面的場景會變成怎樣的流程:
客戶端:我要開始使用WebSocket協議,須要的服務:chat(查動態),WebSocket協議版本:13(HTTP Request)
服務端:沒問題,已升級爲WebSocket協議(HTTP Protocols Switched)
客戶端:麻煩航班動態有更新的時候推送通知給我。
服務端:沒問題。
(……過了10分鐘)
服務端:有動態啦,延誤30分鐘!
(……過了30分鐘)
服務端:有動態啦,如今開始登機!
因而可知,
以下圖所示:
WebSocket是HTML5提出的一個協議規範(2011年)附上協議連接:
The WebSocket Protocol RFC6455
WebSocket約定了一個通訊的規範,經過一個握手的機制,客戶端(如瀏覽器)和服務器(WebServer)之間能創建一個相似Tcp的鏈接,從而方便C-S之間的通訊。
用一張圖來描述各個協議的關係:
WebSocket的握手使用HTTP來實現,客戶端發送帶有Upgrade頭的HTTP Request消息。服務端根據請求,作Response。
GET wss://www.example.cn/webSocket HTTP/1.1
Host: www.example.cn
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Origin: http://example.cn
Sec-WebSocket-Key: afmbhhBRQuwCLmnWDRWHxw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
複製代碼
詳細解釋一下:
注:若是對壓縮擴展協商的細節感興趣,可參考下面的RFC7692瞭解更多細節。 Compression Extensions for WebSocket RFC7692
HTTP/1.1 101
Server: nginx/1.12.2
Date: Sat, 11 Aug 2018 13:21:27 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: sLMyWetYOwus23qJyUD/fa1hztc=
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Extensions: permessage-deflate;client_max_window_bits=15
複製代碼
詳細解釋一下:
Sec-WebSocket-Accept字段生成步驟:
- 將Sec-WebSocket-Key與協議中已定義的一個GUID 「258EAFA5-E914-47DA-95CA-C5AB0DC85B11」進行拼接。
- 將步驟1中生成的字符串進行SHA1編碼。
- 將步驟2中生成的字符串進行Base64編碼。
客戶端經過驗證服務端返回的Sec-WebSocket-Accept的值, 來肯定兩件事情:
- 服務端是否理解WebSocket協議, 若是服務端不理解,那麼它就不會返回正確的Sec-WebSocket-Accept,則創建WebSocket鏈接失敗。
- 服務端返回的Response是對於客戶端的這次請求的,而不是以前的緩存。 主要是防止有些緩存服務器返回緩存的Response.
至此,握手過程就完成了,此時的TCP鏈接不會釋放。客戶端和服務端能夠互相通訊了。
最後,做爲總結,讓咱們再來回顧一下HTTP1.1與WebSocket的相同與不一樣。加深對WebSocket的理解。
即在必定的期限內保持連接,客戶端會須要在短期內向服務端請求大量的資源,保持TCP鏈接不斷開。客戶端與服務器通訊,必需要有客戶端發起而後服務器返回結果。客戶端是主動的,服務器是被動的。在一個TCP鏈接上能夠傳輸多個Request/Response消息對,因此本質上仍是Request/Response消息對,仍然會形成資源的浪費、實時性不強等問題。若是不是持續鏈接,即短鏈接,那麼每一個資源都要創建一個新的鏈接,HTTP底層使用的是TCP,那麼每次都要使用三次握手創建TCP鏈接,即每個request對應一個response,將形成極大的資源浪費。
即客戶端發送一個超時時間很長的Request,服務器保持住這個鏈接,在有新數據到達時返回Response
只需創建一次Request/Response消息對,以後都是TCP鏈接,避免了須要屢次創建Request/Response消息對而產生的冗餘頭部信息。節省了大量流量和服務器資源。所以被普遍應用於線上WEB遊戲和線上聊天室的開發。
下一篇中,筆者將使用JS(前端)和Springboot(後端),詳細介紹如何利用Springboot框架,快速構建一個基於STOMP的簡單WebSocket通訊系統。敬請關注。
小銘出品,必屬精品
歡迎關注xNPE技術論壇,更多原創乾貨每日推送。