【websocket】spring boot 集成 websocket 的四種方式

集成 websocket 的四種方案

1. 原生注解

pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

WebSocketConfig

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.config;

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.server.standard.ServerEndpointExporter;

/**
 * @author buhao
 * @version WebSocketConfig.java, v 0.1 2019-10-18 15:45 buhao
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpoint() {
        return new ServerEndpointExporter();
    }
}
說明:

這個配置類很簡單惫确,通過這個配置 spring boot 才能去掃描后面的關(guān)于 websocket 的注解

WsServerEndpoint

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.ws;

import org.springframework.stereotype.Component;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author buhao
 * @version WsServerEndpoint.java, v 0.1 2019-10-18 16:06 buhao
 */
@ServerEndpoint("/myWs")
@Component
public class WsServerEndpoint {

    /**
     * 連接成功
     *
     * @param session
     */
    @OnOpen
    public void onOpen(Session session) {
        System.out.println("連接成功");
    }

    /**
     * 連接關(guān)閉
     *
     * @param session
     */
    @OnClose
    public void onClose(Session session) {
        System.out.println("連接關(guān)閉");
    }

    /**
     * 接收到消息
     *
     * @param text
     */
    @OnMessage
    public String onMsg(String text) throws IOException {
        return "servet 發(fā)送:" + text;
    }
}
說明

這里有幾個注解需要注意一下改化,首先是他們的包都在 **javax.websocket **下陈肛。并不是 spring 提供的,而 jdk 自帶的阳藻,下面是他們的具體作用谈撒。

  1. @ServerEndpoint
  2. 通過這個 spring boot 就可以知道你暴露出去的 ws 應(yīng)用的路徑,有點類似我們經(jīng)常用的@RequestMapping蛔外。比如你的啟動端口是8080夹厌,而這個注解的值是ws裆悄,那我們就可以通過 ws://127.0.0.1:8080/ws 來連接你的應(yīng)用
  3. @OnOpen
  4. 當 websocket 建立連接成功后會觸發(fā)這個注解修飾的方法,注意它有一個 Session 參數(shù)
  5. @OnClose
  6. 當 websocket 建立的連接斷開后會觸發(fā)這個注解修飾的方法或南,注意它有一個 Session 參數(shù)
  7. @OnMessage
  8. 當客戶端發(fā)送消息到服務(wù)端時采够,會觸發(fā)這個注解修改的方法腻贰,它有一個 String 入?yún)⒈砻骺蛻舳藗魅氲闹?/li>
  9. @OnError
  10. 當 websocket 建立連接時出現(xiàn)異常會觸發(fā)這個注解修飾的方法,注意它有一個 Session 參數(shù)

另外一點就是服務(wù)端如何發(fā)送消息給客戶端冀瓦,服務(wù)端發(fā)送消息必須通過上面說的 Session 類写烤,通常是在@OnOpen 方法中洲炊,當連接成功后把 session 存入 Map 的 value尼啡,key 是與 session 對應(yīng)的用戶標識崖瞭,當要發(fā)送的時候通過 key 獲得 session 再發(fā)送撑毛,這里可以通過 session.getBasicRemote_().sendText(_) 來對客戶端發(fā)送消息藻雌。

2. Spring封裝

pom.xml

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

HttpAuthHandler

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.handler;

import cn.coder4j.study.example.websocket.config.WsSessionManager;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.time.LocalDateTime;

/**
 * @author buhao
 * @version MyWSHandler.java, v 0.1 2019-10-17 17:10 buhao
 */
@Component
public class HttpAuthHandler extends TextWebSocketHandler {

    /**
     * socket 建立成功事件
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        Object token = session.getAttributes().get("token");
        if (token != null) {
            // 用戶連接成功胯杭,放入在線用戶緩存
            WsSessionManager.add(token.toString(), session);
        } else {
            throw new RuntimeException("用戶登錄已經(jīng)失效!");
        }
    }

    /**
     * 接收消息事件
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 獲得客戶端傳來的消息
        String payload = message.getPayload();
        Object token = session.getAttributes().get("token");
        System.out.println("server 接收到 " + token + " 發(fā)送的 " + payload);
        session.sendMessage(new TextMessage("server 發(fā)送給 " + token + " 消息 " + payload + " " + LocalDateTime.now().toString()));
    }

    /**
     * socket 斷開連接時
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        Object token = session.getAttributes().get("token");
        if (token != null) {
            // 用戶退出,移除緩存
            WsSessionManager.remove(token.toString());
        }
    }


}
說明

通過繼承 TextWebSocketHandler 類并覆蓋相應(yīng)方法鸽心,可以對 websocket 的事件進行處理再悼,這里可以同原生注解的那幾個注解連起來看

  1. afterConnectionEstablished 方法是在 socket 連接成功后被觸發(fā)膝但,同原生注解里的 @OnOpen 功能
  2. **afterConnectionClosed **方法是在 socket 連接關(guān)閉后被觸發(fā)跟束,同原生注解里的 @OnClose 功能
  3. **handleTextMessage **方法是在客戶端發(fā)送信息時觸發(fā)丑孩,同原生注解里的 @OnMessage 功能

WsSessionManager

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author buhao
 * @version WsSessionManager.java, v 0.1 2019-10-22 10:24 buhao
 */
@Slf4j
public class WsSessionManager {
    /**
     * 保存連接 session 的地方
     */
    private static ConcurrentHashMap<String, WebSocketSession> SESSION_POOL = new ConcurrentHashMap<>();

    /**
     * 添加 session
     *
     * @param key
     */
    public static void add(String key, WebSocketSession session) {
        // 添加 session
        SESSION_POOL.put(key, session);
    }

    /**
     * 刪除 session,會返回刪除的 session
     *
     * @param key
     * @return
     */
    public static WebSocketSession remove(String key) {
        // 刪除 session
        return SESSION_POOL.remove(key);
    }

    /**
     * 刪除并同步關(guān)閉連接
     *
     * @param key
     */
    public static void removeAndClose(String key) {
        WebSocketSession session = remove(key);
        if (session != null) {
            try {
                // 關(guān)閉連接
                session.close();
            } catch (IOException e) {
                // todo: 關(guān)閉出現(xiàn)異常處理
                e.printStackTrace();
            }
        }
    }

    /**
     * 獲得 session
     *
     * @param key
     * @return
     */
    public static WebSocketSession get(String key) {
        // 獲得 session
        return SESSION_POOL.get(key);
    }
}
說明

這里簡單通過 **ConcurrentHashMap **來實現(xiàn)了一個 session 池温学,用來保存已經(jīng)登錄的 web socket 的 session仗岖。前文提過,服務(wù)端發(fā)送消息給客戶端必須要通過這個 session揽祥。

MyInterceptor

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.interceptor;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.HashMap;
import java.util.Map;

/**
 * @author buhao
 * @version MyInterceptor.java, v 0.1 2019-10-17 19:21 buhao
 */
@Component
public class MyInterceptor implements HandshakeInterceptor {

    /**
     * 握手前
     *
     * @param request
     * @param response
     * @param wsHandler
     * @param attributes
     * @return
     * @throws Exception
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        System.out.println("握手開始");
        // 獲得請求參數(shù)
        HashMap<String, String> paramMap = HttpUtil.decodeParamMap(request.getURI().getQuery(), "utf-8");
        String uid = paramMap.get("token");
        if (StrUtil.isNotBlank(uid)) {
            // 放入屬性域
            attributes.put("token", uid);
            System.out.println("用戶 token " + uid + " 握手成功拄丰!");
            return true;
        }
        System.out.println("用戶登錄已失效");
        return false;
    }

    /**
     * 握手后
     *
     * @param request
     * @param response
     * @param wsHandler
     * @param exception
     */
    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
        System.out.println("握手完成");
    }

}
說明

通過實現(xiàn) HandshakeInterceptor 接口來定義握手攔截器,注意這里與上面 Handler 的事件是不同的奄侠,這里是建立握手時的事件载矿,分為握手前與握手后恢准,而 Handler 的事件是在握手成功后的基礎(chǔ)上建立 socket 的連接。所以在如果把認證放在這個步驟相對來說最節(jié)省服務(wù)器資源涂召。它主要有兩個方法 beforeHandshake 與 **afterHandshake **敏沉,顧名思義一個在握手前觸發(fā)盟迟,一個在握手后觸發(fā)。

WebSocketConfig

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.config;

import cn.coder4j.study.example.websocket.handler.HttpAuthHandler;
import cn.coder4j.study.example.websocket.interceptor.MyInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
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;

/**
 * @author buhao
 * @version WebSocketConfig.java, v 0.1 2019-10-17 15:43 buhao
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private HttpAuthHandler httpAuthHandler;
    @Autowired
    private MyInterceptor myInterceptor;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry
                .addHandler(httpAuthHandler, "myWS")
                .addInterceptors(myInterceptor)
                .setAllowedOrigins("*");
    }
}
說明

通過實現(xiàn) WebSocketConfigurer 類并覆蓋相應(yīng)的方法進行 websocket 的配置迫皱。我們主要覆蓋 registerWebSocketHandlers 這個方法卓起。通過向 WebSocketHandlerRegistry 設(shè)置不同參數(shù)來進行配置凹炸。其中 *addHandler 方法添加我們上面的寫的 ws 的 handler 處理類,第二個參數(shù)是你暴露出的 ws 路徑奕筐。addInterceptors 添加我們寫的握手過濾器变骡。setAllowedOrigins("") **這個是關(guān)閉跨域校驗锣光,方便本地調(diào)試,線上推薦打開蹬刷。

3. TIO

pom.xml

 <dependency>
     <groupId>org.t-io</groupId>
     <artifactId>tio-websocket-spring-boot-starter</artifactId>
     <version>3.5.5.v20191010-RELEASE</version>
</dependency>

application.xml

tio:
  websocket:
    server:
      port: 8989
說明

這里只配置了 ws 的啟動端口,還有很多配置泡态,可以通過結(jié)尾給的鏈接去尋找

MyHandler

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.handler;

import org.springframework.stereotype.Component;
import org.tio.core.ChannelContext;
import org.tio.http.common.HttpRequest;
import org.tio.http.common.HttpResponse;
import org.tio.websocket.common.WsRequest;
import org.tio.websocket.server.handler.IWsMsgHandler;

/**
 * @author buhao
 * @version MyHandler.java, v 0.1 2019-10-21 14:39 buhao
 */
@Component
public class MyHandler implements IWsMsgHandler {
    /**
     * 握手
     *
     * @param httpRequest
     * @param httpResponse
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public HttpResponse handshake(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
        return httpResponse;
    }

    /**
     * 握手成功
     *
     * @param httpRequest
     * @param httpResponse
     * @param channelContext
     * @throws Exception
     */
    @Override
    public void onAfterHandshaked(HttpRequest httpRequest, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
        System.out.println("握手成功");
    }

    /**
     * 接收二進制文件
     *
     * @param wsRequest
     * @param bytes
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
        return null;
    }

    /**
     * 斷開連接
     *
     * @param wsRequest
     * @param bytes
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
        System.out.println("關(guān)閉連接");
        return null;
    }

    /**
     * 接收消息
     *
     * @param wsRequest
     * @param s
     * @param channelContext
     * @return
     * @throws Exception
     */
    @Override
    public Object onText(WsRequest wsRequest, String s, ChannelContext channelContext) throws Exception {
        System.out.println("接收文本消息:" + s);
        return "success";
    }
}
說明

這個同上個例子中的 handler 很像,也是通過實現(xiàn)接口覆蓋方法來進行事件處理而克,實現(xiàn)的接口是IWsMsgHandler员萍,它的方法功能如下

  1. handshake
  2. 在握手的時候觸發(fā)
  3. onAfterHandshaked
  4. 在握手成功后觸發(fā)
  5. onBytes
  6. 客戶端發(fā)送二進制消息觸發(fā)
  7. onClose
  8. 客戶端關(guān)閉連接時觸發(fā)
  9. onText
  10. 客戶端發(fā)送文本消息觸發(fā)

StudyWebsocketExampleApplication

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */

package cn.coder4j.study.example.websocket;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.tio.websocket.starter.EnableTioWebSocketServer;

@SpringBootApplication
@EnableTioWebSocketServer
public class StudyWebsocketExampleApplication {

    public static void main(String[] args) {
        SpringApplication.run(StudyWebsocketExampleApplication.class, args);
    }
}

說明

這個類的名稱不重要,它其實是你的 spring boot 啟動類螃壤,只要記得加上@EnableTioWebSocketServer注解就可以了

STOMP

pom.xml

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

WebSocketConfig

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

/**
 * @author buhao
 * @version WebSocketConfig.java, v 0.1 2019-10-21 16:32 buhao
 */
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 配置客戶端嘗試連接地址
        registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 設(shè)置廣播節(jié)點
        registry.enableSimpleBroker("/topic", "/user");
        // 客戶端向服務(wù)端發(fā)送消息需有/app 前綴
        registry.setApplicationDestinationPrefixes("/app");
        // 指定用戶發(fā)送(一對一)的前綴 /user/
        registry.setUserDestinationPrefix("/user/");
    }
}
說明
  1. 通過實現(xiàn) WebSocketMessageBrokerConfigurer 接口和加上@EnableWebSocketMessageBroker來進行 stomp 的配置與注解掃描奸晴。
  2. 其中覆蓋 registerStompEndpoints 方法來設(shè)置暴露的 stomp 的路徑寄啼,其它一些跨域赘淮、客戶端之類的設(shè)置睦霎。
  3. 覆蓋 **configureMessageBroker **方法來進行節(jié)點的配置。
  4. 其中 **enableSimpleBroker **配置的廣播節(jié)點蛤高,也就是服務(wù)端發(fā)送消息碑幅,客戶端訂閱就能接收消息的節(jié)點。
  5. 覆蓋**setApplicationDestinationPrefixes **方法沟涨,設(shè)置客戶端向服務(wù)端發(fā)送消息的節(jié)點裹赴。
  6. 覆蓋 setUserDestinationPrefix 方法诀浪,設(shè)置一對一通信的節(jié)點延都。

WSController

/*
 * *
 *  * blog.coder4j.cn
 *  * Copyright (C) 2016-2019 All Rights Reserved.
 *
 */
package cn.coder4j.study.example.websocket.controller;

import cn.coder4j.study.example.websocket.model.RequestMessage;
import cn.coder4j.study.example.websocket.model.ResponseMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author buhao
 * @version WSController.java, v 0.1 2019-10-21 17:22 buhao
 */
@Controller
public class WSController {

    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

    @MessageMapping("/hello")
    @SendTo("/topic/hello")
    public ResponseMessage hello(RequestMessage requestMessage) {
        System.out.println("接收消息:" + requestMessage);
        return new ResponseMessage("服務(wù)端接收到你發(fā)的:" + requestMessage);
    }

    @GetMapping("/sendMsgByUser")
    public @ResponseBody
    Object sendMsgByUser(String token, String msg) {
        simpMessagingTemplate.convertAndSendToUser(token, "/msg", msg);
        return "success";
    }

    @GetMapping("/sendMsgByAll")
    public @ResponseBody
    Object sendMsgByAll(String msg) {
        simpMessagingTemplate.convertAndSend("/topic", msg);
        return "success";
    }

    @GetMapping("/test")
    public String test() {
        return "test-stomp.html";
    }
}
說明
  1. 通過 @MessageMapping 來暴露節(jié)點路徑求摇,有點類似 @RequestMapping殊者。注意這里雖然寫的是 hello ,但是我們客戶端調(diào)用的真正地址是** /app/hello嚷辅。 因為我們在上面的 config 里配置了registry.setApplicationDestinationPrefixes("/app")**距误。
  2. @SendTo這個注解會把返回值的內(nèi)容發(fā)送給訂閱了 /topic/hello 的客戶端准潭,與之類似的還有一個@SendToUser 只不過他是發(fā)送給用戶端一對一通信的。這兩個注解一般是應(yīng)答時響應(yīng)的寺擂,如果服務(wù)端主動發(fā)送消息可以通過 simpMessagingTemplate類的convertAndSend方法泼掠。注意 simpMessagingTemplate.convertAndSendToUser(token, "/msg", msg) ,聯(lián)系到我們上文配置的 registry.setUserDestinationPrefix("/user/"),這里客戶端訂閱的是/user/{token}/msg,千萬不要搞錯挡逼。

Session 共享的問題

上面反復(fù)提到一個問題就是家坎,服務(wù)端如果要主動發(fā)送消息給客戶端一定要用到 session吝梅。而大家都知道的是 session 這個東西是不跨 jvm 的。如果有多臺服務(wù)器做瞪,在 http 請求的情況下右冻,我們可以通過把 session 放入緩存中間件中來共享解決這個問題衩侥,通過 spring session 幾條配置就解決了茫死。但是 web socket 不可以履羞。他的 session 是不能序列化的,當然這樣設(shè)計的目的不是為了為難你爱榔,而是出于對 http 與 web socket 請求的差異導(dǎo)致的糙及。
目前網(wǎng)上找到的最簡單方案就是通過 redis 訂閱廣播的形式浸锨,主要代碼跟第二種方式差不多,你要在本地放個 map 保存請求的 session迟郎。也就是說每臺服務(wù)器都會保存與他連接的 session 于本地聪蘸。然后發(fā)消息的地方要修改,并不是現(xiàn)在這樣直接發(fā)送控乾,而通過 redis 的訂閱機制娜遵。服務(wù)器要發(fā)消息的時候,你通過 redis 廣播這條消息衷咽,所有訂閱的服務(wù)端都會收到這個消息蒜绽,然后本地嘗試發(fā)送躲雅。最后肯定只有有這個對應(yīng)用戶 session 的那臺才能發(fā)送出去骡和。

如何選擇

  1. 如果你在使用 tio,那推薦使用 tio 的集成钮科。因為它已經(jīng)實現(xiàn)了很多功能绵脯,包括上面說的通過 redis 的 session 共享,只要加幾個配置就可以了蛆挫。但是 tio 是半開源悴侵,文檔是需要收費的。如果沒有使用抓于,那就忘了他浇借。
  2. 如果你的業(yè)務(wù)要求比較靈活多變逮刨,推薦使用前兩種,更推薦第二種 Spring 封裝的形式修己。
  3. 如果只是簡單的服務(wù)器雙向通信睬愤,推薦 stomp 的形式,因為他更容易規(guī)范使用砂豌。

其它

  1. websocket 在線驗證

寫完服務(wù)端代碼后想調(diào)試光督,但是不會前端代碼怎么辦,點這里筐摘,這是一個在線的 websocket 客戶端,功能完全夠我們調(diào)試了圃酵。

  1. stomp 驗證

這個沒找到在線版的郭赐,但是網(wǎng)上有很多 demo 可以下載到本地進行調(diào)試确沸,也可以通過后文的連接找到。

  1. 另外由于篇幅有限舀锨,并不能放上所有代碼宛逗,但是測試代碼全都上傳 gitlab雷激,保證可以正常運行,可以在 這里 找到

參考鏈接

  1. SpringBoot 系統(tǒng) - 集成 WebSocket 實時通信
  2. WebSocket 的故事(二)—— Spring 中如何利用 STOMP 快速構(gòu)建 WebSocket 廣播式消息模式
  3. SpringBoot集成WebSocket【基于純H5】進行點對點[一對一]和廣播[一對多]實時推送
  4. Spring Framework 參考文檔(WebSocket STOMP)
  5. Spring Boot中使用WebSocket總結(jié)(一):幾種實現(xiàn)方式詳解
  6. Spring Boot 系列 - WebSocket 簡單使用
  7. tio-websocket-spring-boot-starter
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市根悼,隨后出現(xiàn)的幾起案子挤巡,更是在濱河造成了極大的恐慌,老刑警劉巖喉恋,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件轻黑,死亡現(xiàn)場離奇詭異琴昆,居然都是意外死亡,警方通過查閱死者的電腦和手機玖详,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門蟋座,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脚牍,“玉大人,你說我怎么就攤上這事券膀∏郾颍” “怎么了叉庐?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵陡叠,是天一觀的道長。 經(jīng)常有香客問我译红,道長兴溜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮斋攀,結(jié)果婚禮上淳蔼,老公的妹妹穿的比我還像新娘。我一直安慰自己讳癌,他們只是感情好存皂,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著骤菠,像睡著了一般商乎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鲜戒,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天抹凳,我揣著相機與錄音,去河邊找鬼失都。 笑死颖系,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的信粮。 我是一名探鬼主播趁啸,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼强缘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了不傅?” 一聲冷哼從身側(cè)響起旅掂,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎访娶,沒想到半個月后商虐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡崖疤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了劫哼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叮趴。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖权烧,靈堂內(nèi)的尸體忽然破棺而出眯亦,到底是詐尸還是另有隱情伤溉,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布妻率,位于F島的核電站乱顾,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏舌涨。R本人自食惡果不足惜糯耍,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一扔字、第九天 我趴在偏房一處隱蔽的房頂上張望囊嘉。 院中可真熱鬧,春花似錦革为、人聲如沸扭粱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽琢蛤。三九已至,卻和暖如春抛虏,著一層夾襖步出監(jiān)牢的瞬間博其,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工迂猴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留慕淡,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓沸毁,卻偏偏與公主長得像峰髓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子息尺,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容