Spring4.0 構建websocket

傳統服務端推技術

  • 簡單輪詢
    這是最先的一種實現實時 Web 應用的方案。客戶端以必定的時間間隔向服務端發出請求,以頻繁請求的方式來保持客戶端和服務器端的同步。這種同步方案的最大問題是,當客戶端以固定頻率向服務器發起請求的時候,服務器端的數據可能並無更新,這樣會帶來不少無謂的網絡傳輸,因此這是一種很是低效的實時方案。javascript

  • 長輪詢
    長輪詢是對定時輪詢的改進和提升,目地是爲了下降無效的網絡傳輸。當服務器端沒有數據更新的時候,鏈接會保持一段時間週期直到數據或狀態改變或者時間過時,經過這種機制來減小無效的客戶端和服務器間的交互。固然,若是服務端的數據變動很是頻繁的話,這種機制和定時輪詢比較起來沒有本質上的性能的提升。java

  • 流:
    流技術方案一般就是在客戶端的頁面使用一個隱藏的窗口向服務端發出一個長鏈接的請求。服務器端接到這個請求後做出迴應並不斷更新鏈接狀態以保證客戶端和服務器端的鏈接不過時。經過這種機制能夠將服務器端的信息源源不斷地推向客戶端。這種機制在用戶體驗上有一點問題,須要針對不一樣的瀏覽器設計不一樣的方案來改進用戶體驗,同時這種機制在併發比較大的狀況下,對服務器端的資源是一個極大的考驗。web

綜合這幾種方案,您會發現這些目前咱們所使用的所謂的實時技術並非真正的實時技術,它們只是在用 Ajax 方式來模擬實時的效果,在每次客戶端和服務器端交互的時候都是一次 HTTP 的請求和應答的過程,而每一次的 HTTP 請求和應答都帶有完整的 HTTP 頭信息,這就增長了每次傳輸的數據量,並且這些方案中客戶端和服務器端的編程實現都比較複雜,在實際的應用中,爲了模擬比較真實的實時效果,開發人員每每須要構造兩個 HTTP 鏈接來模擬客戶端和服務器之間的雙向通信,一個鏈接用來處理客戶端到服務器端的數據傳輸,一個鏈接用來處理服務器端到客戶端的數據傳輸,這不可避免地增長了編程實現的複雜度,也增長了服務器端的負載,制約了應用系統的擴展性。spring

WebSocket優點

WebSocket 設計出來的目的就是要取代輪詢和 Comet 技術,使客戶端瀏覽器具有像 C/S 架構下桌面系統的實時通信能力。 瀏覽器經過 JavaScript 向服務器發出創建 WebSocket 鏈接的請求,鏈接創建之後,客戶端和服務器端就能夠經過 TCP 鏈接直接交換數據。由於 WebSocket 鏈接本質上就是一個 TCP 鏈接,因此在數據傳輸的穩定性和數據傳輸量的大小方面,和輪詢以及 Comet 技術比較,具備很大的性能優點。Websocket.org 網站對傳統的輪詢方式和 WebSocket 調用方式做了一個詳細的測試和比較,將一個簡單的 Web 應用分別用輪詢方式和 WebSocket 方式來實現,在這裏引用一下他們的測試結果圖:
p1chrome

經過這張圖能夠清楚的看出,在流量和負載增大的狀況下,WebSocket 方案相比傳統的 Ajax 輪詢方案有極大的性能優點。這也是爲何咱們認爲 WebSocket 是將來實時 Web 應用的首選方案的緣由。編程

WebSocket支持的版本

Spring - 4.0.1
Tomcat - 7.0.52
Web browser - chrome 和 firefox瀏覽器

實現

  • 增長依賴
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-websocket</artifactId>
   <version>4.0.1.RELEASE</version>
</dependency>
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-messaging</artifactId>
   <version>4.0.1.RELEASE</version>
</dependency>
  • 建立websocket處理類
    這個類處理來自瀏覽器(客戶端)的websocket請求。在這個例子中,咱們建立一個叫WebsocketEndPoint的類,並讓它繼承TextWebsocketHandler類。而後重寫父類方法handlerTextMessage(),每當客戶端發送消息過來,都會由這個函數接收並處理。
public class WebsocketEndPoint extends TextWebSocketHandler {

    @Override
    protected void handleTextMessage(WebSocketSession session,TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
        TextMessage returnMessage = new TextMessage(message.getPayload()+" received at server");
        session.sendMessage(returnMessage);
    }
}
  • 建立握手攔截器(optional)
    websocket握手攔截器用來攔截和處理客戶端和服務端分別在握手前和握手後的的事件,咱們在這裏添加這個攔截器是爲了能清晰知道何時以及是否成功握手
public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor{
    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Map<String, Object> attributes) throws Exception {
        System.out.println("Before Handshake");
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

    @Override
    public void afterHandshake(ServerHttpRequest request,
            ServerHttpResponse response, WebSocketHandler wsHandler,
            Exception ex) {
        System.out.println("After Handshake");
        super.afterHandshake(request, response, wsHandler, ex);
    }

}
  • 配置handler和interceptor
    有兩種方式:
    1)建立一個實現WebSocketConfigurer接口的類
@Configuration
@EnableWebSocket
public class WebsocketConfig implements WebSocketConfigurer{

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myhandler(), "/websocket")
        .addInterceptors(myInterceptors());
    }

    @Bean
    public WebSocketHandler myhandler() {
        return new WebsocketEndPoint();
    }

    @Bean
    public HandshakeInterceptor myInterceptors() {
        return new HandshakeInterceptor();
    }
}

2)配置xml服務器

<bean id="websocket" class="***.***.***.WebsocketEndPoint"/>
<websocket:handlers>
    <websocket:mapping path="/websocket" handler="websocket"/>
    <websocket:handshake-interceptors>
    <bean class="***.***.***.HandshakeInterceptor"/>
    </websocket:handshake-interceptors>
</websocket:handlers>

到這裏,服務端的代碼以及配置已經弄好websocket

  • 瀏覽器端javascript


<script type="text/javascript"> function setConnected(connected) { document.getElementById('connect').disabled = connected; document.getElementById('disconnect').disabled = !connected; document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden'; document.getElementById('response').innerHTML = ''; } function connect() { if ('WebSocket' in window){ console.log('Websocket supported'); socket = new WebSocket('ws://localhost:8080//websocket'); console.log('Connection attempted'); socket.onopen = function(){ console.log('Connection open!'); setConnected(true); } socket.onclose = function(){ console.log('Disconnecting connection'); } socket.onmessage = function (evt) { var received_msg = evt.data; console.log(received_msg); console.log('message received!'); showMessage(received_msg); } } else { console.log('Websocket not supported'); } } function disconnect() { setConnected(false); console.log("Disconnected"); } function sendName() { var message = document.getElementById('message').value; socket.send(JSON.stringify({ 'message': message })); } function showMessage(message) { var response = document.getElementById('response'); var p = document.createElement('p'); p.style.wordWrap = 'break-word'; p.appendChild(document.createTextNode(message)); response.appendChild(p); } /* 1. new WebSocket('ws://localhost:8080//websocket')嘗試與服務器創建鏈接; 2. 握手成功並創建鏈接後,socket.onopen被調用 3. 當接收來自服務器的消息,socket.onmessage被調用 4. socket.send()用來發送消息至服務端 */ </script>

完。網絡

參考資料: 使用 HTML5 WebSocket 構建實時 Web 應用

相關文章
相關標籤/搜索