1. 程式人生 > >Spring Websocket例子

Spring Websocket例子

基本概念

WebSocket協議提供了通過一個套接字實現全雙工通訊的功能。它能夠實現Web瀏覽器和伺服器之間的非同步通訊,全雙工意味著伺服器可以傳送訊息給瀏覽器,瀏覽器也可以傳送訊息給伺服器。Spring 4.0為WebSocket通訊提供了支援,主要是實現伺服器端和基於瀏覽器的應用(Web)之間的非同步通訊

建立WebSocket訊息處理器

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;

/**
 * WebSocket訊息處理器
 */
public class MarcoHandler extends AbstractWebSocketHandler {

	private static final Logger logger = LoggerFactory.getLogger(MarcoHandler.class);
	
	@Override
	protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
		logger.info("Received message: " + message.getPayload());
		Thread.sleep(2000);
		session.sendMessage(new TextMessage("Polo!"));
	}
	
}

一般情況下,建立WebSocket訊息處理器需要實現WebSocketHandler介面,該介面中有5個方法需要實現,不過,這裡我們不實現WebSocketHandler介面,我們繼承AbstractWebSocketHandler抽象類。該類實現了WebSocketHandler介面,並提供了預設的實現,因此繼承AbstractWebSocketHandler的一個好處是,只需要重寫我們關心的方法就行。這個例子中,我們只關心文字訊息的處理,因此我們只需要重寫handleTextMessage方法就可以,而不用實現WebSocketHandler的所有介面

啟用WebSocket並對映訊息處理器

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * WebSocket配置中心
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

	@Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		registry.addHandler(marcoHandler(), "/marco").withSockJS();
	}
  
	@Bean
	public MarcoHandler marcoHandler() {
		return new MarcoHandler();
	}

}

要啟用WebSocket並對映訊息處理器,需要實現WebSocketConfigurer介面,並通過@EnableWebSocket註解來啟用WebSocket支援。另外,還需要實現該介面的唯一方法,它用於註冊WebSocket處理器。可以看到,我們還呼叫了withSockJS(),該方法的含義是,如果服務端或客戶端對WebSocket不支援的話,就使用備用方案SockJS,它可以代替WebSocket實現服務端和客戶端的通訊

建立WebSocket的JS客戶端

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
  <title>WebSocket-Demo</title>
  <script src="https://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script>
  <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
</head>
<body>
<button id="stop">Stop</button>

<script>
    var url = 'marco';
    var sock = new SockJS(url);
    var isclosed = false;
    sock.onopen = function() {
        console.log('Opening');
        sayMarco();
    }

    sock.onmessage = function(e) {
        console.log('Received message: ', e.data);
        $('#output').append('Received "' + e.data + '"<br/>');
        setTimeout(function(){sayMarco()}, 2000);
    }

    sock.onclose = function() {
        console.log('Closing');
        isclosed = true;
    }

    function sayMarco() {
        if(isclosed) {
            console.log('Sock is Closed!');
        }else {
            console.log('Sending Marco!');
            $('#output').append('Sending "Marco!"<br/>');
            sock.send("Marco!");
        }
    }

    $('#stop').click(function() {sock.close()});
</script>

<div id="output"></div>
</body>
</html>

建立WebSocket客戶端,首先要建立WebSocket例項,在支援WebSocket的瀏覽器中,這個類是原生的,如果再不支援WebSocket的瀏覽器中,這個物件就不能使用了。聯想到上一個步驟,我們在服務端和客戶端不支援WebSocket的時候,使用了備用方案SockJS,同樣的,在客戶端,我們也使用SockJS來代替WebSocket。使用SockJS需要引入相應的JS,

https://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js

除了WebScoket訊息處理器、WebSocket配置中心、客戶端頁面外,還有兩個類。它是構成Spring MVC應用必不可少的。

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

import javax.servlet.ServletRegistration.Dynamic;

/**
 * DispatcherServlet配置
 */
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class<?>[] { };
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class<?>[] { WebConfig.class, WebSocketConfig.class };
	}

	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}

	@Override
	protected void customizeRegistration(Dynamic registration) {
		registration.setAsyncSupported(true);
	}
	
}

WebInitializer代替了原來需要在web.xml檔案中配置的DispatcherServlet,有了這個類,我們就不需要使用web.xml配置檔案了。

以下描述引用自,https://segmentfault.com/a/1190000004343063?_ea=575820

AbstractAnnotationConfigDispatcherServletInitializer這個類負責配置DispatcherServlet、初始化Spring MVC容器和Spring容器。getRootConfigClasses()方法用於獲取Spring應用容器的配置檔案,這裡我們給定預先定義的RootConfig.class;getServletConfigClasses負責獲取Spring MVC應用容器,這裡傳入預先定義好的WebConfig.class;getServletMappings()方法負責指定需要由DispatcherServlet對映的路徑,這裡給定的是"/",意思是由DispatcherServlet處理所有向該應用發起的請求。

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * Spring MVC配置
 */
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

}

WebConfig類中沒有什麼程式碼,他的作用主要是對應用啟用WebMVC功能。

接下來,將應用程式部署到tomcat中,就可以使用例子程式進行瀏覽器客戶端和服務端的訊息通訊了。

這個例子中,遇到了兩個問題:

  1. 在初始化sockJS的時候,報構造WebSocket失敗的錯誤。兩個物件使用的協議不同,sockJS使用HTTP協議,WebSocket使用ws協議。如果建立sockJS時,還會拿著HTTP協議的URL去建立WebSocket,那麼報這樣的錯誤也是必然了。建立sockJS的時候是不是會去建立WebSocket,目前還不太清楚。

Failed to complete request: Calling [asyncComplete()] is not valid for a request with Async state [MUST_COMPLETE]

  1. 就是上面這個錯誤資訊了,網上查了一下,說是瀏覽器請求伺服器,服務端響應有些慢,還沒等伺服器返回結果,瀏覽器就中斷請求了。但是我修改了下程式碼,客戶端收到服務端訊息後,馬上斷開連線。後臺還是會出這個錯誤,不知道是什麼原因

以上兩個問題就先記錄下來吧,回頭再解決