前言
記得大概半年前就產生了疑惑,即後臺如何主動向前端推送資料。問了下專業老師,知道了原來有一個叫WebSocket的技術可以用於推送資料。於是,當時我就找了個教程,用的是Spring WebSocket。照著敲了一遍,也就搭起來了,依葫蘆畫瓢而已。當時有其他東西要學,也沒有相關的需求,就沒再接觸過。前陣子剛好要用這個框架,但是API早忘了,就又搜了一遍,發現網上各種各樣的案例都有,API都不一樣。以下是我這階段對各種與WebSocket掛鉤的知識點的梳理。
資料推送的應用場景
資料推送的應用場景很多,如傳送公告,郵件提示啊等等。那麼問題是如何推送,換句話說,前端如何獲得最新的資料?
推和拉
假如後臺更新了一些資料,如新增了一個公告,想要讓所有瀏覽主頁的人都看到。那麼我要做的,就是把伺服器的資料推送給瀏覽器。我們知道,雙方通訊必須建立連線。但是問題就來了,HTTP連線的發起方只能是瀏覽器,而且請求一旦結束,連線就斷了。去哪裡找連線呢?所以,單純依賴HTTP來推資料是行不通的。那既然推不動,我客戶端主動去拉不就行了。
拉當然是可以的,一種解決方法就是輪詢,即每隔一段時間用Ajax請求一遍。這樣確實可以達到目的。但是存在兩個問題,一個是開銷問題,如果你輪詢了一個上午,都沒有最新訊息,那你這一上午發出的請求都是無效請求,都是相當於在騷擾伺服器。你可能也意識到了前一個問題,決定把時間間隔設大,來減少請求數。但是這樣就可能影響實時性了,也就是說如果在本次無效請求後,資料剛好更新,那麼你必須等待一個時間週期結束後,才能獲取到最新值。
--------------------------------2019年1月22日補充---------------------------------------------
頻繁發請求,就必須頻繁地握手,建立連線。這個過程的開銷不可忽視,websocket的話,一般只需要建立一次連線,然後不斷開,減少了建立連線的開銷。
WebSocket技術
WebSocket就是用來解決前面的連線問題的。使用WebSocket時,連線的發起方同樣是瀏覽器,但是不同的是,除非一方主動觸發,否則連線一旦建立就不會斷開。另外需要注意的是,建立連線,即握手過程,用的還是HTTP協議,這個過程如果順利的話,就會將HTTP協議升級到WebSocket協議。
WebSocket協議相對於HTTP有何優點?
除了讓後臺可以主動推資料外,WebSocket還具有以下優點:
-
- 連線狀態可保持,因為是長連線,就不需要像HTTP那樣每次都帶Cookie這樣。
- 更少的控制資訊,單純的WebSocket不需要HTTP那麼多的頭資訊,輕量級。
- 支援擴充套件,可以擴充套件協議,如STOMP
-----------------------------2019年2月10日補充--------------------------------
現在使用的Http協議基本都是1.1版本,1.1版本預設都是長連線。除非主動去關閉它,否則後續的請求都會複用這個socket連線。所以,在建立連線的次數方面,WebSocket不佔優勢。
後備選項 PLAN B
誒,有沒有注意到,我前面說的是"如果順利",難道HTTP升級到Websocket還會失敗嗎?答案是會。為什麼,因為不是所有瀏覽器都支援WebSocket協議的。那這問題不就出現了嗎,我當初是這麼想的,你們下個新的瀏覽器會死啊?!
後來,我冷靜思考了一下,我這樣想是不對的。因為如果是一個對外網站,一個遊客因為瀏覽器不支援,而不能得到良好的響應效果,你覺得他還會耐心地裝新瀏覽器,再次訪問嗎?所以,我們必須有後備選項實現推送功能。其實,所謂的後備選項就是輪詢。那這時候我們要做的就是前端的工作了,即判斷瀏覽器是否支援,如果支援就用WebSocket,如果不能就用Ajax輪詢。但是這樣其實工作量挺大的,特別是瀏覽器還要跟服務端互動,而不僅僅是接收資料的時候。
好在,有一個包含WebSocket和長輪詢的框架,SockJS。使用它的時候,如果瀏覽器支援就用WebSocket傳輸,如果不支援就用輪詢。判斷和切換對我們來說是透明的,我們使用它的方法,跟使用WebSocket的API差不多。Spring WebSocket就支援SockJS,服務端配置也方便,繫結連線點的時候加上.withSockJS()即可。
WebSocket的API
服務端釋出一個WebSocket連線點的過程:
1.建立連線點類
2.實現該連線點的各個生命週期的方法,如連線建立成功,來訊息,連線斷開這些回撥函式
3.在連線點類中新增業務邏輯,這主要看你的應用需求了
4.釋出連線點,其實就是繫結這個連線點類到一個URL,當客戶端嘗試連線這個URL後,所有的操作由這個類例項完成
JavaEE WebSocket API
JavaEE有相關的API規範,JSR 356。Tomcat實現了這套規範。
需要的包:如果是Spring Boot專案,你勾選了Web選項。那你就有了內建Tomcat,也就直接能用WebSocket了。注:不需要引入Spring-WebSocket。
API使用方式:這個API的使用方式是,建立一個連線點類,標註上@ServerEndPoint("指定的URL")註解,然後在類內部,使用@OnOpen, @OnMessage,@OnClose等註解標識對應的生命週期回撥方法。
Spring WebSocket API
Spring WebSocket 在原生基礎上,做了些補充,有自己的使用方法。
需要的包:如果是Spring Boot專案要使用Spring的WebSocket,就要勾選WebSocket選項,它就會引入相關的整合包。
API使用方式:注:這裡只講純WebSocket的使用,不講擴充套件內容。Spring把連線點的處理類叫作Handler,建立Handler的方式可以是實現WebSocketHandler介面,或繼承已有類TextWebSocketHandler等,你需要根據需要覆蓋已有的回撥方法。然後在實現了WebSocketConfigurer介面的配置類中,繫結URL。這裡需要使用@EnableWebSocket註解開啟WebSocket支援。
與原生API的對比:我覺得同樣是使用純WebSocketAPI,Spring因為用的是實現介面的形式,所以它各個回撥方法的引數都更明確。而且一看介面就很清楚有哪些回撥方法。而如果使用@OnOpen,@OnMessage等註解,你根本不清楚註解的方法,支援哪些引數,引數到底是什麼型別的。這些你看註解程式碼都看不出來,需要看官方的說明才清楚。
擴充套件內容:Spring除了讓你能使用純WebSocket外,還支援其他擴充套件。如支援SockJS使得瀏覽器.都能接受推送資料,支援WebSocket擴充套件協議STOMP,使得能夠像訊息機制那樣使用WebSocket。
使用程度:Spring WebSocket支援擴充套件,你可以自己決定用到什麼程度。
1.純WebSocket,部分瀏覽器不支援。如果你確定使用者使用的瀏覽器都沒問題,用到這層就夠了。
2.支援SockJS(WebSocket + 長輪詢),服務端改動的話,只需要加withSockJS即可。所有瀏覽器都支援
3.純WebSocket/SockJS + STOMP。這種時候是佇列使用的是In-Memory的MQ。訊息機制,可釋出訂閱,實現廣播。
4.純WebSocket/SockJS + STOMP + ActiveMQ/RabbitMQ。引入第三方訊息佇列。這時候訊息就能持久化,防止因伺服器當機,導致訊息丟失。
注意:使用STOMP,並不一定要配合SockJS。
Socket.IO
Socket.IO是單獨的一套WebSocketAPI。它使用的Engine.IO引擎也能夠切換傳輸方式,即所有瀏覽器都支援,相當於SockJS吧。它的分組機制,使得它也能夠廣播訊息,暫時不知道是否支援STOMP擴充套件。它官方提供的服務端實現是Node版,客戶端支援多種語言。Java如果要使用它的服務端的話,可以使用一個開源的用Netty實現的框架netty-socketio。
目前就Spring-WebSocket與Socket.IO的效能優劣還不甚瞭解。