springboot之websocket,STOMP協議


  一、WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。

  WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。

在 WebSocket API 中,瀏覽器和服務器只需要做一個握手的動作,然后,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。

  二、STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,簡單(流)文本定向消息協議,它提供了一個可互操作的連接格式,允許STOMP客戶端與任意STOMP消息代理(Broker)進行交互。STOMP協議由於設計簡單,易於開發客戶端,因此在多種語言和多種平台上得到廣泛地應用。

  三、首先,我們先理解一下為什么需要STOMP。

  1)常規的websocket連接和普通的TCP基本上沒有什么差別的。

  2)那我們如果像http一樣加入一些響應和請求層。

  3)所以STOMP在websocket上提供了一中基於幀線路格式(frame-based wire format)。

  4)簡單一點,就是在我們的websocket(TCP)上面加了一層協議,使雙方遵循這種協議來發送消息。

  四、STOMP

  1)Frame

  

  例如:

  

  command:CONNECT

  其他部分都是headers的一部分。

  2)command類別

    CONNECT

    SEND

    SUBSCRIBE

    UNSUBSCRIBE

    BEGIN

    COMMIT

    ABORT

    ACK

    NACK

    DISCONNECT

   3)客戶端常用連接方式

  a、ws  

  var url = "ws://localhost:8080/websocket";
  var client = Stomp.client(url);

  b、sockJs

  <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
  <script>
    // use SockJS implementation instead of the browser's native implementation
    var ws = new SockJS(url);
    var client = Stomp.over(ws);
    [...]
  </script>

  說明:使用ws協議需要瀏覽器的支持,但是一些老版本的瀏覽器不一定支持。Stomp.over(ws)的凡是就是用來定義服務websocket的協議。

  4)服務端的實現過程

  

  a、服務端:/app,這里訪問服務端,前綴通過設定的方式訪問。

  b、用戶:/user,這里針對的是用戶消息的傳遞,針對於當前用戶進行傳遞。

  c、其他消息:/topic、/queue,這兩種方式。都是定義出來用於訂閱。並且消息只能從這里通過並處理

   五、springboot的簡單例子

  1)目錄結構

  

 

  2)依賴包(pom.xml)

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
    </dependencies>

  3)websocket配置(WebSocketConfiguration、SecurityConfiguration

/**
 * webSocket配置
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {

    /**
     * 注冊stomp端點,主要是起到連接作用
     * @param stompEndpointRegistry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
        stompEndpointRegistry
                .addEndpoint("/webSocket")  //端點名稱
                //.setHandshakeHandler() 握手處理,主要是連接的時候認證獲取其他數據驗證等
                //.addInterceptors() 攔截處理,和http攔截類似
                .setAllowedOrigins("*") //跨域
                .withSockJS(); //使用sockJS

    }

    /**
     * 注冊相關服務
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //這里使用的是內存模式,生產環境可以使用rabbitmq或者其他mq。
        //這里注冊兩個,主要是目的是將廣播和隊列分開。
        //registry.enableStompBrokerRelay().setRelayHost().setRelayPort() 其他方式
        registry.enableSimpleBroker("/topic", "/queue");
        //客戶端名稱前綴
        registry.setApplicationDestinationPrefixes("/app");
        //用戶名稱前
        registry.setUserDestinationPrefix("/user");
    }
}

  認證配置:

/**
 * 配置基本登錄
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{

    /**
     * 加密方式
     */
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    /**
     * 所有請求過濾,包含webSocket
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests().anyRequest().authenticated()
        .and()
            .httpBasic();
    }

    /**
     * 加入兩個用戶測試不同用的接受情況
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin").password(passwordEncoder.encode("admin")).roles("ADMIN")
        .and()
            .withUser("user").password(passwordEncoder.encode("user")).roles("USER");
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

   4)服務端

/**
 * 消息接收處理
 */
@RestController
public class MessageResource {

    //spring提供的推送方式
    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    /**
     * 廣播模式
     * @param requestMsg
     * @return
     */
    @MessageMapping("/broadcast")
    @SendTo("/topic/broadcast")
    public String broadcast(RequestMsg requestMsg) {
        //這里是有return,如果不寫@SendTo默認和/topic/broadcast一樣
        return "server:" + requestMsg.getBody().toString();
    }

    /**
     * 訂閱模式,只是在訂閱的時候觸發,可以理解為:訪問——>返回數據
     * @param id
     * @return
     */
    @SubscribeMapping("/subscribe/{id}")
    public String subscribe(@DestinationVariable Long id) {
        return "success";
    }

    /**
     * 用戶模式
     * @param requestMsg
     * @param principal
     */
    @MessageMapping("/one")
    //@SendToUser("/queue/one") 如果存在return,可以使用這種方式
    public void one(RequestMsg requestMsg, Principal principal) {
        //這里使用的是spring的security的認證體系,所以直接使用Principal獲取用戶信息即可。
        //注意為什么使用queue,主要目的是為了區分廣播和隊列的方式。實際采用topic,也沒有關系。但是為了好理解
        messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/one", requestMsg.getBody());
    }
}

  客戶端(JavaScript):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>webSocket</title>
    <script src="js/jquery.js"></script>
    <script src="js/sockjs.min.js"></script>
    <script src="js/stomp.js"></script>
</head>
<body>
    <div>
        <button id="connect">連接</button>
        <button id="disconnect" disabled="disabled">斷開</button>
    </div>
    <div>
        <h3>廣播形式</h3>
        <button id="broadcastButton">發送</button><input id="broadcastText" type="text">
        <label>廣播消息:</label><input id="broadcastMsg" type="text" disabled="disabled">
    </div>
    <div>
        <h3>訂閱形式</h3>
        <label>訂閱消息:</label><input id="subscribeMsg" type="text" disabled="disabled">
    </div>
    <div>
        <h3>角色形式</h3>
        <button id="userButton">發送</button><input id="userText" type="text">
        <label>用戶消息:</label><input id="userMsg" type="text" disabled="disabled">
    </div>
    <div>
        <h3>無APP</h3>
        <button id="appButton">發送</button><input id="appText" type="text">
        <label>前端消息:</label><input id="appMsg" type="text" disabled="disabled">
    </div>
</body>
<script>
    var stomp = null;
    $("#connect").click(function () {
        var url = "http://localhost:8080/webSocket"
        var socket = new SockJS(url);
        stomp = Stomp.over(socket);
        //連接
        stomp.connect({}, function (frame) {
            //訂閱廣播
            stomp.subscribe("/topic/broadcast", function (res) {
                $("#broadcastMsg").val(res.body);
            });
            //訂閱,一般只有訂閱的時候在返回
            stomp.subscribe("/app/subscribe/1", function (res) {
                $("#subscribeMsg").val(res.body);
            });
            //用戶模式
            stomp.subscribe("/user/queue/one", function (res) {
                $("#userMsg").val(res.body);
            });
            //無APP
            stomp.subscribe("/topic/app", function (res) {
                $("#appMsg").val(res.body);
            });
            setConnect(true);
        });
    });

    $("#disconnect").click(function () {
        if (stomp != null) {
            stomp.disconnect();
        }
        setConnect(false);
    });
    //設置按鈕
    function setConnect(connectStatus) {
        $("#connect").attr("disabled", connectStatus);
        $("#disconnect").attr("disabled", !connectStatus);
    }

    //發送廣播消息
    $("#broadcastButton").click(function () {
        stomp.send("/app/broadcast", {}, JSON.stringify({"body":$("#broadcastText").val()}))
    });

    //發送用戶消息
    $("#userButton").click(function () {
        stomp.send("/app/one", {}, JSON.stringify({"body":$("#userText").val()}))
    });

    //發送web消息
    $("#appButton").click(function () {
        stomp.send("/topic/app", {}, JSON.stringify({"body":$("#appText").val()}))
    });
</script>
</html>

  5)普通測試

  角色測試:

   六、相關資料

  http://jmesnil.net/stomp-websocket/doc/

   七、源碼:https://github.com/lilin409546297/springboot-websocket


免責聲明!

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



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