WebSocket消息推送,SockJS, STOMP


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

WebSocket協議是基於TCP的一種新的網絡協議,是一個應用層協議,是TCP/IP協議的子集。

它實現了瀏覽器與服務器全雙工(full-duplex)通信,客戶端和服務器都可以向對方主動發送和接收數據。在 WebSocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸。

在JS中創建WebSocket后,會有一個HTTP請求從瀏覽器發向服務器。在取得服務器響應后,建立的連接會使用HTTP升級將HTTP協議轉換為WebSocket協議。也就是說,使用標准的HTTP協議無法實現WebSocket,只有支持那些協議的專門瀏覽器才能正常工作。由於WebScoket使用了自定義協議,所以URL與HTTP協議略有不同。未加密的連接為ws://,而不是http://。加密的連接為wss://,而不是https://。

用途:

實時Web應用的解決方案,實現Web的實時通信。

說的再直白點, html的消息推送 。

假如你有一個頁面,數據不定期更改,通常的做法就是 Ajax輪詢,輪詢是指在特定的時間間隔(如每一秒),由瀏覽器對服務器發起HTTP請求,然后由服務器返回最新的數據給客戶端的瀏覽器。

由於HTTP協議是惰性的,只有客戶端發起請求,服務器才會返回數據。這種傳統的模式帶來很明顯的缺點,即瀏覽器需要不斷的向服務器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會浪費很多的帶寬等資源。。而WebSocket屬於服務端推送技術,能更好的節省服務器資源和帶寬,並且能夠更實時地進行通訊。

當有了web socket,數據變動時 讓服務器通知客戶端,啟不是很美妙? 

請求示例:

(1) 默認端口是80和443(ssl)。

(2) 協議標識符是ws和wss(ssl)。

(3) 請求報文示例

General
--------------------------------------------
Request URL:ws://localhost:8080/j2ee-websocket/websocket/1
Request Method:GET
Status Code:101 Switching Protocols
---------------------------------------------
Response Headers
---------------------------------------------
Connection:upgrade
Date:Tue, 05 Dec 2017 01:22:45 GMT
Sec-WebSocket-Accept:cRxT/XcOpnsleDb1KdydWXOw+us=
Sec-WebSocket-Extensions:permessage-deflate;client_max_window_bits=15
Server:Apache-Coyote/1.1
Upgrade:websocket

瀏覽器通過 JavaScript 向服務器發出建立 WebSocket 連接的請求,連接建立以后,客戶端和服務器端就可以通過 TCP 連接直接交換數據。

當你獲取 Web Socket 連接后,你可以通過 send() 方法來向服務器發送數據,並通過 onmessage 事件來接收服務器返回的數據。

客戶端JavaScript代碼:

<script type="text/javascript">  
      var websocket = null;  
      var principal = '1';
      var socketURL = 'ws://' + window.location.host
            + '/j2ee-websocket/websocket/' + principal;
      //判斷當前瀏覽器是否支持WebSocket,若支持創建一個websocket對象。  
      if('WebSocket' in window){  
          websocket = new WebSocket(socketURL);  
      }  
      else{  
          alert('Not support websocket');  
      }  
         
      //連接發生錯誤的回調方法  
      websocket.onerror = function(event){  
          alert("error");  
      };  
         
      //連接成功建立的回調方法  
      websocket.onopen = function(){  
          alert("open");  
      }  
         
      //接收到消息的回調方法  
      websocket.onmessage = function(event){  
          alert('recive : ' + event.data);  
      }  
         
      //連接關閉的回調方法  
      websocket.onclose = function(event){  
          alert("close");  
      }  
         
      //發送消息  
      function send(message){  
          websocket.send(message);  
      }  
  </script>

 WebSocket 對象的屬性:

var Socket = new WebSocket(socketURL)
Socket.readyState

只讀屬性readyState表示連接狀態,可以是以下值:

  • 0 - 表示連接尚未建立。

  • 1 - 表示連接已建立,可以進行通信。

  • 2 - 表示連接正在進行關閉。

  • 3 - 表示連接已經關閉或者連接不能打開。

websocket事件:

事件              事件處理程序                 描述
open            Socket.onopen           連接建立時觸發
message       Socket.onmessage      客戶端接收服務端數據時觸發
error            Socket.onerror            通信發生錯誤時觸發
close            Socket.onclose            連接關閉時觸發

websocket對象的方法

方法                       描述
Socket.send()        使用連接發送數據
Socket.close()        關閉連接

推薦:google/jquery-websocket代碼 (http://code.google.com/p/jquery-websocket)

google/jquery-websocket增加了消息的類型,將消息拆分為{"type":"","message":""}。

這樣更靈活,可以根據業務類型,定義type,如:通知,公告,廣播,發文等...

<script type="text/javascript">
        var principal = '1';
        var socketURL = 'ws://' + window.location.host
                + '/j2ee-websocket/websocket/' + principal;
        
        websocket = $.websocket(socketURL, {
            open : function() {
                // when the socket opens
                alert("open");
            },
            close : function() {
                // when the socket closes
                alert("close");
            },
            //收到服務端推送的消息處理
            events : {
                'radio' : function(event) {
                    console.info($.parseJSON(event.data));
                },
                'notice' : function(event) {
                    console.info($.parseJSON(event.data));
                },
                //... more custom type of message
            }
        });
        
        //發送消息  
        function send() {
            websocket.send('radio', " hello,this msg from client request");
        }
    </script> 

 

AJAX輪詢和Websockets: 

 

SockJS是一個瀏覽器JavaScript庫,它提供了一個連貫的、跨瀏覽器的Javascript API,在瀏覽器和web服務器之間建立一個低延遲、全雙工、跨域通信通道。SockJs的一大好處是提供了瀏覽器兼容性,優先使用原生的WebSocket,在不支持websocket的瀏覽器中,會自動降為輪詢的方式。

<script src="//cdn.jsdelivr.net/sockjs/1.0.0/sockjs.min.js"></script>

var sock = new SockJS('/coordination');  
sock.onopen = function() {
    console.log('open');
};
sock.onmessage = function(e) {
    console.log('message', e.data);
};
sock.onclose = function() {
    console.log('close');
};
sock.send('test');
sock.close();

以下內容轉載自(https://segmentfault.com/a/1190000006617344?utm_source=tag-newest

STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,簡單(流)文本定向消息協議,它提供了一個可互操作的連接格式,允許STOMP客戶端與任意STOMP消息代理(Broker)進行交互。

stomp幀(Frame)格式如下:

屬性。                類型                         描述
command          String                      name of the frame("CONNECT",'SEND',etc.)
headers             JavaScript object
body                 String

command和headers屬性始終會被定義,當這個frame沒有頭部時,headers可以為空。若這個frame沒有body, body的值可以為null。

創建stomp客戶端:

1、在web瀏覽器中使用普通的Web Socket

stomp javascript客戶端會使用ws://的URL與stomp服務端進行交互。為了創建一個stomp客戶端js對象,需要使用Stomp.client(url), 而這個URL連接着服務端的WebSocket的代理:

var url = "ws://localhost:61614/stomp";
var client = Stomp.client(url);

Stomp.client(url, protocols)也可以用來覆蓋默認的subprotocols, 第二個參數可以是一個字符串或一個字符串數組指定的多個subprotocols。

2、在web瀏覽器中使用定制的WebSocket

瀏覽器提供了不同的websocket協議,一些老的瀏覽器不支持WebSocket的腳本或者使用別的名字。默認下,stomp.js會使用瀏覽器原生的WebSocket class去創建WebSocket。利用Stomp.over(ws)這個方法可以使用其他類型的WebSockets.

<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>

連接服務器:

一旦Stomp客戶端建立了,必須調用它的connect()方法去連接,從而Stomp服務端進行驗證。這個方法需要兩個參數,用戶的登錄和密碼憑證。這種情況下,客戶端會使用Websocket打開連接,並發送一個CONNECT frame。這個連接是異步進行的,你不能保證當這個方法返回時是有效連接的,為了知道連接的結果,需要一個回調函數。

var connect_callback = function(){
    // called back after the client is connected and authenticated to the STOMP server
}

如果連接失敗connect()方法接受一個可選的參數(error_callback),當客戶端不能連接上服務端時,這個回調函數error_callback會被調用,該函數的參數為對應的錯誤對象。

var error_callback = function(error) {
    // display the error's message header:
    alert(error.headers.message);
};

在大多數情況下,connect()方法可接受不同數量的參數來提供簡單的API:

client.connect(login, passcode, connectCallback);
client.connect(login, passcode, connectCallback, errorCallback);
client.connect(login, passcode, connectCallback, errorCallback, host);

 如果你需要附加一個headers頭部,connect方法還可以接受其他兩種形式的參數:

client.connect(headers, connectCallback);
client.connect(headers, connectCallback, errorCallback);

header是map形式,需要自行在headers添加login, passcode(甚至host):

var headers = {
    login: 'my login',
    passcode: 'my passcode',
    // additional header
    'client-id': 'my-client-id'
}
client.connect(headers, connectCallback);

斷開連接時,調用disconnect方法,這個方法也是異步的,當斷開成功后會接受一個額外的回調函數的參數:

client.disconnect(function(){
    alert('See you next time!');
});

當客戶端與服務器斷開連接,就不會再發送或接收消息了。

客戶端和服務器連接過程中會每隔固定時間發送heart-beats, heat-beating也就是頻率,incoming時接收頻率,outgoing是發送頻率。通過改變incoming和outgoing可以更改客戶端的heart-beating(默認為10000ms

client.heartbeat.outgoing = 20000;    // client will send heartbeats every 20000ms
client.heartbeat.incoming = 0;           // client does not want to receive heartbeats from the server

heart-beating 是利用window.setInterval()去規律地發送heat-beats或者檢查服務端的heart-beats.

發送消息

當客戶端與服務端連接成功后,可以調用send()來發送STOMP消息。這個方法必須有一個參數,用來描述對應的STOMP的目的地。另兩個可選的參數:headers(object類型包含額外的信息頭部)和body(一個String類型的參數)

client.send("queue/test", {priority:9}, "Hello, STOMP");

如果你想發送一個有body的信息,也必須傳遞headers參數。如果沒有headers需要傳遞,那么就傳遞{}即可,如下所示:

client.send(destination, {}, body);

訂閱(Subscribe)和接收(receive)消息

為了在瀏覽器中接收消息,STOMP客戶端必須先訂閱一個目的地destination。你可以使用subscribe()去訂閱。這個方法有2個必需的參數:目的地(destination),回調函數(callback);還有一個可選的參數headers。其中destination是String類型,對應目的地,回調函數是伴隨着一個參數的function類型。

var subscription = client.subscribe("/queue/test/", callback);

subscribe()方法返回一個object,這個object包含一個id屬性,對應這個這個客戶端的訂閱ID。而unsubscribe()可以用來取消客戶端對這個目的地destination的訂閱。

 默認情況下,如果沒有在headers額外添加,這個庫會默認構建一個獨一無二的ID。在傳遞headers這個參數時,可以使用你自己的ID:

var mysubid = '...';
var subscription = client.subscribe(destination, callback, { id: mysubid });

這個客戶端會向服務端發送一個STOMP訂閱幀(SUBSCRIBE frame)並注冊回調事件。每次服務端向客戶端發送消息時,客戶端都會輪流調用回調函數,參數為對應消息的STOMP幀對象(Frame object)。如下所示:

callback = function(message) {
    // called when the client receives a STOMP message from the server
    if (message.body) {
        alert("got message with body " + message.body)
    } else {
        alert("got empty message");
    }
});

subscribe()方法,接受一個可選的headers參數用來標識附加的頭部。

var headers = {ack: 'client', 'selector': "location = 'Europe'"};
client.subscribe("/queue/test", message_callback, headers);

這個客戶端指定了它會確認接收的信息,只接收符合這個selector : location = 'Europe'的消息。

如果想讓客戶端訂閱多個目的地,你可以在接收所有信息的時候調用相同的回調函數:

onmessage = function(message) {
    // called every time the client receives a message
}
var sub1 = client.subscribe("queue/test", onmessage);
var sub2 = client.subscribe("queue/another", onmessage)

如果要中止接收消息,客戶端可以在subscribe()返回的object對象調用unsubscribe()來結束接收。

var subscription = client.subscribe(...);

...

subscription.unsubscribe();

支持JSON

STOMP消息的body必須為字符串。如果你需要發送/接收JSON對象,你可以使用JSON.stringify()JSON.parse()去轉換JSON對象。

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

Acknowledgment(確認)

默認情況,在消息發送給客戶端之前,服務端會自動確認(acknowledged)。

客戶端可以選擇通過訂閱一個目的地時設置一個ack headerclientclient-individual來處理消息確認。

在下面這個例子,客戶端必須調用message.ack()來通知服務端它已經接收了消息。

var subscription = client.subscribe("/queue/test",
    function(message) {
        // do something with the message
        ...
        // and acknowledge it
        message.ack();
    },
    {ack: 'client'}
);

ack()接受headers參數用來附加確認消息。例如,將消息作為事務(transaction)的一部分,當要求接收消息時其實代理(broker)已經將ACK STOMP frame處理了。

var tx = client.begin();
message.ack({ transaction: tx.id, receipt: 'my-receipt' });
tx.commit();

nack()也可以用來通知STOMP 1.1.brokers(代理):客戶端不能消費這個消息。與ack()方法的參數相同。

事務(Transactions)

可以在將消息的發送和確認接收放在一個事務中。

客戶端調用自身的begin()方法就可以開始啟動事務了,begin()有一個可選的參數transaction,一個唯一的可標識事務的字符串。如果沒有傳遞這個參數,那么庫會自動構建一個。

這個方法會返回一個object。這個對象有一個id屬性對應這個事務的ID,還有兩個方法:
commit()提交事務
abort()中止事務

在一個事務中,客戶端可以在發送/接受消息時指定transaction id來設置transaction。

// start the transaction
var tx = client.begin();
// send the message in a transaction
client.send("/queue/test", {transaction: tx.id}, "message in a transaction");
// commit the transaction to effectively send the message
tx.commit();

如果你在調用send()方法發送消息的時候忘記添加transction header,那么這不會稱為事務的一部分,這個消息會直接發送,不會等到事務完成后才發送。

var txid = "unique_transaction_identifier";
// start the transaction
var tx = client.begin();
// oops! send the message outside the transaction
client.send("/queue/test", {}, "I thought I was in a transaction!");
tx.abort(); // Too late! the message has been sent

 


免責聲明!

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



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