STOMP協議——基於Websocket實現

玉獅子發表於2019-04-27

今天繼續Websocket之STOMP協議,由於其設計簡單,在開發客戶端方面使用簡便,在很多種語言上都可以見到其身影,並非websocket“獨享”。

定義

STOMP(Simple/Streaming Text Orientated Messaging Protocol),即簡單(流)文字定向訊息協議。屬於訊息佇列的一種協議,有點類似於jms。

作用

提供訊息體的格式,允許STOMP客戶端(Endpoints)與任意STOMP訊息代理(message broker)進行互動,實現客戶端之間進行非同步訊息傳送。

角色介紹

STOMP協議——基於Websocket實現

圖片來源《spring in action》

  • 生產者客戶端: 給某destination傳送訊息;
  • 消費者客戶端: 接收所訂閱的destination所推送過來的訊息;
  • 請求通道: 接收生產者推送過來的訊息的執行緒池;
  • 相應通道: 推送訊息給消費者的執行緒池;
  • 代理: 訊息佇列管理者. 記錄哪些client訂閱了哪個destination.
  • 應用目的地址: 傳送到這類目的地址的訊息在到達broker之前, 會先路由到由應用寫的某個方法. 相當於對進入broker的訊息進行一次攔截, 目的是針對訊息做一些業務處理————圖中的”/app”
  • 非應用目的地址: 傳送到這類目的地址的訊息會直接轉到broker. 不會被應用攔截————圖中的”/topic”

處理流程

  • 生產者通過傳送訊息到某個destination
  • 請求通道接受訊息
  • 如果目的地址是應用目的地址(/app)則轉到相應的由應用自己寫的業務方法做處理, 再轉到broker
  • 如果目的地址是非應用目的地址(/topic)則直接轉到broker.broker構建訊息後再通過相應通道推送訊息到所有訂閱此目的地址的消費者

程式碼實現

下面使用SpringSecurity和WebSocket-STOMP實現“點對點”訊息傳送功能:

啟用STOMP功能

@Configuration
@EnableWebSocketMessageBroker//開啟訊息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /**
     * 建立連線點資訊
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws/ep").withSockJS();
         registry.setApplicationDestinationPrefixes("/app");
    }

    /**
     * 配置訊息佇列
     * 基於記憶體的STOMP訊息代理
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/queue");
    }
}
複製程式碼
  • 將 "/ws/ep" 註冊為一個 STOMP 端點。客戶端在訂閱或釋出訊息到目的地路徑前,要連線到該端點
  • 以 /app 開頭的訊息都會被路由到帶有@MessageMapping 或 @SubscribeMapping 註解的方法中;
  • 以 /queue 開頭的訊息都會傳送到STOMP代理中,根據所選擇的STOMP代理不同,目的地的可選字首也會有所限制;
  • 以/user開頭的訊息會將訊息重路由到某個使用者獨有的目的地上。

處理STOMP訊息

自定義通訊協議

@Controller
public class WScontroller {

    @Autowired//訊息傳送模板
    SimpMessagingTemplate simpMessagingTemplate;

    @MessageMapping("/ws/chat")
    public void receiveMessage(String message, Principal principal) {
        String[] split = message.split(";");
        HashMap<String, Object> map = new HashMap<>();
        map.put("username",split[1]);
        map.put("msg",split[0]);
        simpMessagingTemplate.convertAndSendToUser(split[1], "/queue/msg",map);
    }
複製程式碼
  • 接收客戶端發來的訊息,引數就是訊息本身message
  • @MessageMapping 或者 @SubscribeMapping 註解可以處理客戶端傳送過來的訊息,並選擇方法是否有返回值。
  • @MessageMapping 指定目的地是“/app/ws/chat”(“/app”字首是隱含的,因為我們將其配置為應用的目的地字首)。
  • 通訊協議可以自定義——可自定義引數的格式
  • 可以接收json格式的資料,傳遞josn資料時不需要新增額外註解@Requestbody
  • 訊息傳送者不是從前端傳遞過來的,而是從springsecurity中獲取的,防止前端冒充
  • 如果 @MessageMapping 註解的控制器方法有返回值的話,返回值會被髮送到訊息代理,只不過會新增上"/topic"字首。
  • 通過為方法新增@SendTo註解,過載目的地

客戶端實現

客戶端程式碼(UVE)

<template>
  <div>
    <div>
      <div v-for="(m,index) in ms">{{m.username}}:{{m.msg}}</div>

    </div>
    <el-input v-model="msg"></el-input>
    <el-button @click="sendMsg"></el-button>
  </div>
</template>

<script>
  import "../../lib/sockjs"
  import "../../lib/stomp"
    export default {
        name: "FriendChat",
      data() {
        return {
          msg: '',
          ms: [],
          stomp: null
        };
      },
      mounted() {
        this.intCon();
      },
      methods: {
        // 建立連線
        initCon() {
          let _this = this;
           this.stomp = Stomp.over(new SockJS("/ws/ep"));
            this.stomp.connect({},success=>{
              _this.stomp.subscribe("/user/queue/msg",msg=>{
                _this.ms.push(JSON.parse(msg.body));
              })
            },fail=>{

            })
        },
        // 傳送訊息
        sendMsg() {
          this.stomp.send("/ws/chat",{},this.msg)
        }
      }
    }
</script>
<style scoped>
</style> 
複製程式碼

相關文章