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就能交換數據。

    可以看看知乎上的這個回答,解釋的挺生動的:https://www.zhihu.com/question/20215561

二、使用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客戶端

    接下來看看服務端這邊如何建立起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>
View Code

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 連接關閉......");
    }

}
View Code
  • 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>
View Code

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);
    }


}
View Code

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();
    }
}
View Code
3.2、xml 方式
    <websocket:handlers>
        <websocket:mapping handler="marcoHandler_1" path="/marco1"/>
    </websocket:handlers>
View Code

三、使用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();
    }
    
}
View Code
2、XML方式
    <websocket:handlers>
        <websocket:mapping handler="marcoHandler_1" path="/marco1"/>
        <websocket:sockjs/> <!--聲明啟用SockJS功能-->
    </websocket:handlers>
View Code

 

 

效果展示:

演示源碼下載鏈接:https://github.com/JMCuixy/SpringWebSocket/tree/developer


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM