Spring訊息之WebSocket
閱讀目錄
一、WebSocket簡介
WebSocket 的定義?WebSocket是HTML5下一種全雙工通訊協議。在建立連線後,WebSocket伺服器端和客戶端都能主動的向對方傳送和接收資料,就像Socket一樣。
WebSocket 的由來?眾所周知,HTTP協議有“無連線”、“不可靠”、“盡最大努力”的特點。WebSocket的出現可以看成是HTTP協議為了支援長連線所打的一個大補丁。首先,由 IETF 制定釋出了WebSocket 協議。後來,HTML5為了在Web端支援WebSocket協議,由W3C 釋出了一整套WebSocket API。其次,WebSocket主要用於Web端,對於非Web部分的意義不大(畢竟直接使用TCP就好了)。因此,在廣義上,Websocket 也常常被人稱為是HTML5 下的通訊協議。
HTTP 和 WebSocket 的區別?
1、HTTP 和 WebSocket 是兩種不同的協議,但是HTTP負責了建立WebSocket的連線。
2、HTTP 請求以 http:// 或 https:// 開始,WebSocket 請求一般以ws:// 或 wss:// 開始。
3、所有瀏覽器都支援 HTTP 協議,WebScoket 可以會遇到不支援的瀏覽器(可通過SockJS解決)
4、HTTP長連線中,每次資料交換除了真正的資料部分外,伺服器和客戶端還要大量交換HTTP header,資訊交換效率很低。Websocket協議通過第一個request建立了TCP連線之後,之後交換的資料都不需要傳送 HTTP header就能交換資料。
二、使用Spring的低層級WebSocket API
先來看看客戶端如何建立起WebSocket 的連線。首先,我們使用 new WebSocket(url) 建立一個WebSocket 的例項物件;然後,使用這個例項物件建立WebSocket的事件處理功能,onopen、onmessage、onclose 和 onerror 事件,分別對應著 開啟連線、接收訊息、關閉連線 和 異常處理 事件。
/*WebSocket*/ var url = 'ws://localhost:8080/marco2'; var sock = new WebSocket(url); sock.onopen = function (ev) { console.log("正在建立連線..."); sayMarco(); }; sock.onmessage = function (ev) { console.log("接收並處理訊息:" + ev.data); if (count == 10) { sock.close(); } setTimeout( function () { sayMarco(); count++; }, 2000); }; sock.onclose = function (ev) { console.log("連線關閉..."); }; sock.error = function (ev) { console.log("連線異常"); }; function sayMarco() { console.log('Sending Marco !'); sock.send("Marco!") }
接下來看看服務端這邊如何建立起WebSocket的服務:
1、pom 依賴
<!--WebSocket--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.3.13.RELEASE</version> </dependency> <!--輔助包--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.8.10</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.10</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.8.10</version> </dependency>
2、WebSocket 服務
有兩種方案可以建立起WebSocket服務,一種是基於Spring 的 spring-websocket,一種是基於 java 的 websocket-api。
- spring-websocket
WebSocketHandler 介面定義了服務端處理WebSocket訊息要做的一系列事情。相比直接實現WebSocketHandler,更為簡單的方法是擴充套件AbstractWebSocketHandler,這是WebSocketHandler的一個抽象實現。當然根據處理訊息的型別,還可以選擇繼承TextWebSocketHandler(文字類訊息)、BinaryWebSocketHandler(二進位制訊息)等...
public class MarcoHandler_2 extends AbstractWebSocketHandler { private static final Logger LOGGER = LoggerFactory.getLogger(MarcoHandler_2.class); @Override public void afterConnectionEstablished(WebSocketSession session) { LOGGER.info("WebSocket 連線建立......"); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { LOGGER.info("接收到訊息:" + message.getPayload()); Thread.sleep(2000); //傳送文字訊息 session.sendMessage(new TextMessage("Polo!")); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus){ LOGGER.info("WebSocket 連線關閉......"); } }
- websocket-api
websocket-api 提供了一種基於註解、更為簡單明瞭的方式處理WebSocket訊息。美中不足的是它需要依賴 javax.websocket-api.jar。
① pom依賴
<dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.1</version> <scope>provided</scope> </dependency>
② WebSocket服務
/** * Created by XiuYin.Cui on 2018/5/2. * * 基於註解方式的WebSocket 控制器 */ @ServerEndpoint("/ws") public class WsController { private static final Logger LOGGER = LoggerFactory.getLogger(WsController.class); @OnOpen public void onOpen(){ LOGGER.info("連線建立"); } @OnClose public void onClose(){ LOGGER.info("連線關閉"); } @OnMessage public void onMessage(String message, Session session){ try { LOGGER.info("接收到訊息:" + message); Thread.sleep(2000); session.getBasicRemote().sendText("polo"); //傳送訊息 } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } @OnError public void onError(Session session, Throwable throwable){ throw new IllegalArgumentException(throwable); } }
3、建立對映
現在,已經有了訊息處理器類,我們必須要對其進行配置,這樣Spring才能將訊息轉發給它。在Spring的Java配置中,這需要在一個配置類上使用@EnableWebSocket,並實現WebSocketConfigurer介面。
像所有HTTP請求一樣,我們需要將WebSocket服務暴露成一個供客戶端訪問的url 地址。依舊有兩種方式,配置類方式 和 XML方式:
3.1、配置類方式
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { /** * * @param registry 該物件可以呼叫addHandler()來註冊資訊處理器。 */ @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(marcoHandler_2(),"/marco2") .addInterceptors(webSocketHandshakeInterceptor()) //宣告攔截器 .setAllowedOrigins("*"); //宣告允許訪問的主機列表 } @Bean public MarcoHandler_2 marcoHandler_2(){ return new MarcoHandler_2(); } @Bean public WebSocketHandshakeInterceptor webSocketHandshakeInterceptor(){ return new WebSocketHandshakeInterceptor(); } }
3.2、xml 方式
<websocket:handlers> <websocket:mapping handler="marcoHandler_1" path="/marco1"/> </websocket:handlers>
三、使用SockJS支援WebSocket
既然已經有了WebSocket API 為什麼還要有SockJS呢?
1、WebSocket 是一個較新的協議規範,在Web瀏覽器和應用伺服器上可能沒有得到一致的支援。
2、防火牆代理通常會限制所有除HTTP以外的流量。它們可能不支援或者還沒有配置允許進行WebSocket 通訊。
SockJS 又是什麼呢?
SockJS是WebSocket技術的一種模擬,在表面上,它儘可能對應WebSocket API,但是在底層它非常智慧。SockJS會優先選用WebSocket,但是如果WebSocket不可用的話,它將會從如下的方案中挑選最優的可行方案:
- XHR流。
- XDR流。
- iFrame事件源。
- iFrame HTML檔案。
- XHR輪詢。
- XDR輪詢。
- iFrame XHR輪詢。
- JSONP輪詢。
接下來讓我們看看SockJS 的使用和WebSocket 有什麼差異?
- 客戶端
1、SockJS客戶端庫
要在客戶端使用SockJS,需要確保載入了SockJS客戶端庫。
<script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
2、修改URL,構建SockJS例項
SockJS所處理的URL是“http://”或“https://”模式,而不是“ws://”和“wss://”。
var url = 'http://localhost:8080/marcoSockJS'; var sock = new SockJS(url);
- 服務端
服務端這邊只要在建立對映的時候加上SockJS的支援即可:
1、配置類方式
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { /** * * @param registry 該物件可以呼叫addHandler()來註冊資訊處理器。 */ @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { //宣告啟用SockJS連線,如果前端還用 new WebSocket(url); 會報:Error during WebSocket handshake: Unexpected response code: 200 registry.addHandler(marcoHandler_2(), "/marcoSockJS") .setAllowedOrigins("*") ////宣告允許訪問的主機列表 .withSockJS(); } @Bean public MarcoHandler_2 marcoHandler_2(){ return new MarcoHandler_2(); } }
2、XML方式
<websocket:handlers> <websocket:mapping handler="marcoHandler_1" path="/marco1"/> <websocket:sockjs/> <!--宣告啟用SockJS功能--> </websocket:handlers>
效果展示: