STOMP(Simple Text-Orientated Messaging Protocol) 面向消息的簡單文本協議
WebSocket是一個消息架構,不強制使用任何特定的消息協議,它依賴於應用層解釋消息的含義;
與處在應用層的HTTP不同,WebSocket處在TCP上非常薄的一層,會將字節流轉換為文本/二進制消息,因此,對於實際應用來說,WebSocket的通信形式層級過低,因此,可以在 WebSocket 之上使用 STOMP協議,來為瀏覽器 和 server間的 通信增加適當的消息語義。
如何理解 STOMP 與 WebSocket 的關系:
1) HTTP協議解決了 web 瀏覽器發起請求以及 web 服務器響應請求的細節,假設 HTTP 協議 並不存在,只能使用 TCP 套接字來 編寫 web 應用,你可能認為這是一件瘋狂的事情;
2) 直接使用 WebSocket(SockJS) 就很類似於 使用 TCP 套接字來編寫 web 應用,因為沒有高層協議,就需要我們定義應用間所發送消息的語義,還需要確保連接的兩端都能遵循這些語義;
3) 同 HTTP 在 TCP 套接字上添加請求-響應模型層一樣,STOMP 在 WebSocket 之上提供了一個基於幀的線路格式層,用來定義消息語義;
STOMP幀
STOMP幀由命令,一個或多個頭信息、一個空行及負載(文本或字節)所組成;
其中可用的COMMAND 包括: CONNECT、SEND、SUBSCRIBE、UNSUBSCRIBE、BEGIN、COMMIT、ABORT、ACK、NACK、DISCONNECT;
例:發送消息
SEND destination:/queue/trade content-type:application/json content-length:44 {“action”:”BUY”,”ticker”:”MMM”,”shares”,44}^@
訂閱消息
SUBSCRIBE id:sub-1 destination:/topic/price.stock.* ^@
服務器進行廣播消息
MESSAGE message-id:nxahklf6-1 subscription:sub-1 destination:/topic/price.stock.MMM {“ticker”:”MMM”,”price”:129.45}^@
客戶端 API
引入stomp.js
<script type="application/javascript" src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
發起連接
客戶端可以通過使用Stomp.js和sockjs-client連接
// 建立連接對象(還未發起連接) var socket=new SockJS("/spring-websocket-portfolio/portfolio"); // 獲取 STOMP 子協議的客戶端對象 var stompClient = Stomp.over(socket); // 向服務器發起websocket連接並發送CONNECT幀 stompClient.connect({},function connectCallback (frame) { // 連接成功時(服務器響應 CONNECTED 幀)的回調方法 document.getElementById("state-info").innerHTML = "連接成功"; console.log('已連接【' + frame + '】'); stompClient.subscribe('/topic/getResponse', function (response) { showResponse(response.body); }); }, function errorCallBack (error) { // 連接失敗時(服務器響應 ERROR 幀)的回調方法 document.getElementById("state-info").innerHTML = "連接失敗"; console.log('連接失敗【' + error + '】'); } );
//1) socket連接對象也可通過WebSocket(不通過SockJS)連接 var socket=new WebSocket("/spring-websocket-portfolio/portfolio"); //2) stompClient.connect()方法簽名: client.connect(headers, connectCallback, errorCallback); //其中headers表示客戶端的認證信息,如: var headers = { login: 'mylogin', passcode: 'mypasscode', // additional header 'client-id': 'my-client-id' };若無需認證,直接使用空對象 “{}” 即可; connectCallback 表示連接成功時(服務器響應 CONNECTED 幀)的回調方法; errorCallback 表示連接失敗時(服務器響應 ERROR 幀)的回調方法,非必須; //斷開連接 若要從客戶端主動斷開連接,可調用 disconnect() 方法 client.disconnect(function () { alert("See you next time!"); } //該方法為異步進行,因此包含了回調參數,操作完成時自動回調;
心跳機制
若使用STOMP 1.1 版本,默認開啟了心跳檢測機制,可通過client對象的heartbeat field進行配置(默認值都是10000 ms):
client.heartbeat.outgoing = 20000; // client will send heartbeats every 20000ms
client.heartbeat.incoming = 0; // client does not want to receive heartbeats from the server
發送信息
//連接成功后,客戶端可使用 send() 方法向服務器發送信息: client.send(destination url[, headers[, body]]); /*其中destination url 為服務器 controller中 @MessageMapping 中匹配的URL,字符串,必須參數; headers 為發送信息的header,JavaScript 對象,可選參數; body 為發送信息的 body,字符串,可選參數;例:*/ client.send("/queue/test", {priority: 9}, "Hello, STOMP"); client.send("/queue/test", {}, "Hello, STOMP");
訂閱、接收信息
STOMP 客戶端要想接收來自服務器推送的消息,必須先訂閱相應的URL,即發送一個 SUBSCRIBE 幀,然后才能不斷接收來自服務器的推送消息;
訂閱和接收消息通過 subscribe() 方法實現:
subscribe(destination url, callback[, headers]) /*其中destination url 為服務器 @SendTo 匹配的 URL,字符串; callback 為每次收到服務器推送的消息時的回調方法,該方法包含參數 message; headers 為附加的headers該方法返回一個包含了id屬性的 JavaScript 對象,可作為 unsubscribe() 方法的參數;*/
取消訂閱
var subscription = client.subscribe(...); subscription.unsubscribe();
JSON 支持
STOMP 幀的 body 必須是 string 類型,若希望接收/發送 json 對象,可通過 JSON.stringify() and JSON.parse() 實現;
var quote = {symbol: 'APPL', value: 195.46}; client.send("/topic/stocks", {}, JSON.stringify(quote)); client.subcribe("/topic/stocks", function(message) { var quote = JSON.parse(message.body); alert(quote.symbol + " is at " + quote.value); });
事務支持
STOMP 客戶端支持在發送消息時用事務進行處理:
可以將消息的發送和確認接收放在一個事務中。客戶端調用自身的begin()
方法就可以開始啟動事務了,begin()
有一個可選的參數transaction
,一個唯一的可標識事務的字符串。如果沒有傳遞這個參數,那么庫會自動構建一個。這個方法會返回一個object。這個對象有一個id
屬性對應這個事務的ID,還有兩個方法:commit()
提交事務、abort()
中止事務。在一個事務中,客戶端可以在發送/接受消息時指定transaction id來設置transaction。
var tx = client.begin(); client.send("/queue/test", {transaction: tx.id}, "message in a transaction"); tx.commit();
如果你在調用send()
方法發送消息的時候忘記添加transction header,那么這不會稱為事務的一部分,這個消息會直接發送,不會等到事務完成后才發送。
調試
有一些測試代碼能有助於你知道庫發送或接收的是什么,從而來調試程序。客戶端可以將其debug
屬性設置為一個函數,傳遞一個字符串參數去觀察庫所有的debug語句。默認情況,debug消息會被記錄在在瀏覽器的控制台。
client.debug = function(str) { $("#debug").append(str + "\n"); };