1. 程式人生 > 其它 >WebSocket使用Spring boot整合方法

WebSocket使用Spring boot整合方法

springboot對websocket支援很友好,只需要繼承webSocketHandler類,重寫幾個方法就可以了

這個類是對訊息的一些處理,比如是發給一個人,還是發給所有人,並且前端連線時觸發的一些動作

/**
 * 建立一個WebSocket server
 * 
 * @ClassName: CustomWebSocketHandler
 * @Description: TODO
 * @author OnlyMate
 * @Date 2018年8月16日 下午3:17:34
 *
 */
@Service
public class CustomWebSocketHandler extends
TextWebSocketHandler implements WebSocketHandler { private Logger logger = LoggerFactory.getLogger(CustomWebSocketHandler.class); // 線上使用者列表 private static final Map<String, WebSocketSession> users; // 使用者標識 private static final aString CLIENT_ID = "mchNo"; static { users
= new HashMap<>(); } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { logger.info("成功建立websocket-spring連線"); String mchNo = getMchNo(session); if (StringUtils.isNotEmpty(mchNo)) { users.put(mchNo, session); session.sendMessage(
new TextMessage("成功建立websocket-spring連線")); logger.info("使用者標識:{},Session:{}", mchNo, session.toString()); } } @Override public void handleTextMessage(WebSocketSession session, TextMessage message) { logger.info("收到客戶端訊息:{}", message.getPayload()); JSONObject msgJson = JSONObject.parseObject(message.getPayload()); String to = msgJson.getString("to"); String msg = msgJson.getString("msg"); WebSocketMessage<?> webSocketMessageServer = new TextMessage("server:" +message); try { session.sendMessage(webSocketMessageServer); if("all".equals(to.toLowerCase())) { sendMessageToAllUsers(new TextMessage(getMchNo(session) + ":" +msg)); }else { sendMessageToUser(to, new TextMessage(getMchNo(session) + ":" +msg)); } } catch (IOException e) { logger.info("handleTextMessage method error:{}", e); } } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { if (session.isOpen()) { session.close(); } logger.info("連接出錯"); users.remove(getMchNo(session)); } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { logger.info("連線已關閉:" + status); users.remove(getMchNo(session)); } @Override public boolean supportsPartialMessages() { return false; } public void sendMessage(String jsonData) { logger.info("收到客戶端訊息sendMessage:{}", jsonData); JSONObject msgJson = JSONObject.parseObject(jsonData); String mchNo = StringUtils.isEmpty(msgJson.getString(CLIENT_ID)) ? "陌生人" : msgJson.getString(CLIENT_ID); String to = msgJson.getString("to"); String msg = msgJson.getString("msg"); if("all".equals(to.toLowerCase())) { sendMessageToAllUsers(new TextMessage(mchNo + ":" +msg)); }else { sendMessageToUser(to, new TextMessage(mchNo + ":" +msg)); } } /** * 傳送資訊給指定使用者 * @Title: sendMessageToUser * @Description: TODO * @Date 2018年8月21日 上午11:01:08 * @author OnlyMate * @param mchNo * @param message * @return */ public boolean sendMessageToUser(String mchNo, TextMessage message) { if (users.get(mchNo) == null) return false; WebSocketSession session = users.get(mchNo); logger.info("sendMessage:{} ,msg:{}", session, message.getPayload()); if (!session.isOpen()) { logger.info("客戶端:{},已斷開連線,傳送訊息失敗", mchNo); return false; } try { session.sendMessage(message); } catch (IOException e) { logger.info("sendMessageToUser method error:{}", e); return false; } return true; } /** * 廣播資訊 * @Title: sendMessageToAllUsers * @Description: TODO * @Date 2018年8月21日 上午11:01:14 * @author OnlyMate * @param message * @return */ public boolean sendMessageToAllUsers(TextMessage message) { boolean allSendSuccess = true; Set<String> mchNos = users.keySet(); WebSocketSession session = null; for (String mchNo : mchNos) { try { session = users.get(mchNo); if (session.isOpen()) { session.sendMessage(message); }else { logger.info("客戶端:{},已斷開連線,傳送訊息失敗", mchNo); } } catch (IOException e) { logger.info("sendMessageToAllUsers method error:{}", e); allSendSuccess = false; } } return allSendSuccess; } /** * 獲取使用者標識 * @Title: getMchNo * @Description: TODO * @Date 2018年8月21日 上午11:01:01 * @author OnlyMate * @param session * @return */ private String getMchNo(WebSocketSession session) { try { String mchNo = session.getAttributes().get(CLIENT_ID).toString(); return mchNo; } catch (Exception e) { return null; } } }

這個類的作用就是在連線成功前和成功後增加一些額外的功能

我們希望能夠把websocketSession和httpsession對應起來,這樣就能根據當前不同的session,定向對websocketSession進行資料返回;在查詢資料之後,發現spring中有一個攔截器介面,HandshakeInterceptor,可以實現這個介面,來攔截握手過程,向其中新增屬性

/**
 * WebSocket握手時的攔截器
 * @ClassName: CustomWebSocketInterceptor 
 * @Description: TODO
 * @author OnlyMate
 * @Date 2018年8月16日 下午3:17:04  
 *
 */
public class CustomWebSocketInterceptor implements HandshakeInterceptor {
    private Logger logger = LoggerFactory.getLogger(CustomWebSocketInterceptor.class);
    /**
     * 關聯HeepSession和WebSocketSession,
     * beforeHandShake方法中的Map引數 就是對應websocketSession裡的屬性
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Map<String, Object> map) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            logger.info("*****beforeHandshake******");
            HttpServletRequest httpServletRequest = ((ServletServerHttpRequest) request).getServletRequest();
            HttpSession session = httpServletRequest.getSession(true);
            
            logger.info("mchNo:{}", httpServletRequest.getParameter("mchNo"));
            if (session != null) {
                
                map.put("sessionId",session.getId());
                map.put("mchNo", httpServletRequest.getParameter("mchNo"));
            }
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
        logger.info("******afterHandshake******");
    }
}

這個類是配置類向Spring中注入handler

/**
 * websocket的配置類
 * @ClassName: CustomWebSocketConfig 
 * @Description: TODO
 * @author OnlyMate
 * @Date 2018年8月16日 下午3:17:26  
 *
 */
@Configuration
@EnableWebSocket
public class CustomWebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(customWebSocketHandler(), "/webSocketBySpring/customWebSocketHandler").addInterceptors(new CustomWebSocketInterceptor()).setAllowedOrigins("*");
        registry.addHandler(customWebSocketHandler(), "/sockjs/webSocketBySpring/customWebSocketHandler").addInterceptors(new CustomWebSocketInterceptor()).setAllowedOrigins("*").withSockJS();
    }

    @Bean
    public WebSocketHandler customWebSocketHandler() {
        return new CustomWebSocketHandler();
    }
}

補充說明:

setAllowedOrigins("*")一定要加上,不然只有訪問localhost,其他的不予許訪問

setAllowedOrigins(String[] domains),允許指定的域名或IP(含埠號)建立長連線,如果只允許自家域名訪問,這裡輕鬆設定。如果不限時使用"*"號,如果指定了域名,則必須要以http或https開頭

經查閱官方文件springwebsocket 4.1.5版本前預設支援跨域訪問,之後的版本預設不支援跨域,需要設定

使用withSockJS()的原因:

  一些瀏覽器中缺少對WebSocket的支援,因此,回退選項是必要的,而Spring框架提供了基於SockJS協議的透明的回退選項。

SockJS的一大好處在於提供了瀏覽器相容性。優先使用原生WebSocket,如果在不支援websocket的瀏覽器中,會自動降為輪詢的方式。
除此之外,spring也對socketJS提供了支援。

如果程式碼中添加了withSockJS()如下,伺服器也會自動降級為輪詢。

registry.addEndpoint("/coordination").withSockJS();

SockJS的目標是讓應用程式使用WebSocket API,但在執行時需要在必要時返回到非WebSocket替代,即無需更改應用程式程式碼。

客戶端與JAVA WebSocket建立連線

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<c:set var="ctx" value="${pageContext.request.contextPath}" />
<c:set var="ctxpath"
    value="${pageContext.request.scheme}${'://'}${pageContext.request.serverName}${':'}${pageContext.request.serverPort}${pageContext.request.contextPath}" />
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta charset=UTF-8">
<title>登入測試</title>
</head>
<body>
    <h2>Hello World! Web Socket by Spring</h2>
    <div>
        <span>sessionId:</span>
        <% 
            HttpSession s= request.getSession(); 
            out.println(s.getId());
        %>
    </div>
    
    <input id="sessionId" type="hidden" value="<%=session.getId() %>" />
    <input id="text" type="text" />
    <button onclick="send()">傳送訊息</button>
    <hr />
    <button onclick="closeWebSocket()">關閉WebSocket連線</button>
    <hr />
    <div id="message"></div>
</body>
<script type="text/javascript" src="http://localhost:8088/static/js/sockjs-0.3.min.js"></script>
<script type="text/javascript">  
        var websocket = null;  
        //判斷當前瀏覽器是否支援WebSocket  
        //判斷當前瀏覽器是否支援WebSocket  
        if('WebSocket' in window) {
            websocket = new WebSocket("ws://localhost:8088/websocket/webSocketBySpring/customWebSocketHandler?mchNo="+ 123);  
        } else if('MozWebSocket' in window) {
            websocket = new MozWebSocket("ws://localhost:8088/websocket/webSocketBySpring/customWebSocketHandler?mchNo="+ 123);
        } else {
            websocket = new SockJS("http://localhost:8088/websocket/sockjs/webSocketBySpring/customWebSocketHandler?mchNo="+ 123);
        }
        //連線發生錯誤的回撥方法  
        websocket.onerror = function () {  
            setMessageInnerHTML("WebSocket連線發生錯誤");  
        };  
      
        //連線成功建立的回撥方法  
        websocket.onopen = function () {  
            setMessageInnerHTML("WebSocket連線成功");  
        }  
      
        //接收到訊息的回撥方法  
        websocket.onmessage = function (event) {  
            setMessageInnerHTML(event.data);  
        }  
      
        //連線關閉的回撥方法  
        websocket.onclose = function () {  
            setMessageInnerHTML("WebSocket連線關閉");  
        }  
      
        //監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。  
        window.onbeforeunload = function () {  
            closeWebSocket();  
        }  
      
        //將訊息顯示在網頁上  
        function setMessageInnerHTML(innerHTML) {  
            document.getElementById('message').innerHTML += innerHTML + '<br/>';  
        }  
      
        //關閉WebSocket連線  
        function closeWebSocket() {  
            websocket.close();  
        }  
      
        //傳送訊息  
        function send() {  
            var message = document.getElementById('text').value;  
            websocket.send(message);  
        }  
    </script>
</html>