Springboot 技術(shù)整合--筆記6--集成websocket

WebSocket的ping與pong的java實(shí)現(xiàn)…

理解幾個(gè)知識(shí)點(diǎn)

  • 訂閱地址 :如stompEndpointRegistry.addEndpoint("/endpointWechat") 或使用@ServerEndpoint創(chuàng)建
  • 容許跨域
  • 是否開啟SockJS支持
  • 推送:聲明SimpMessagingTemplate ,調(diào)用convertAndSend方法(或者使用@SendTo和@SendToUser注解)
  • 一對(duì)一發(fā)送消息
  • Principal:身份驗(yàn)證和授權(quán)
  • stomp協(xié)議:STOMP即Simple (or Streaming) Text Orientated Messaging Protocol药版,簡單(流)文本定向消息協(xié)議劫瞳,它提供了一個(gè)可互操作的連接格式呀酸,允許STOMP客戶端與任意STOMP消息代理(Broker)進(jìn)行交互坤学。
  • WebSocket(stomp服務(wù)端)
websocket.jpg

WebSocketSession 發(fā)送的消息類型(sendMessage)

前端涉及js

  • jquery
  • sockJs :
  • stomp.min.js(stomp客戶端)
  • STOMP Simple (or Streaming) Text Orientated Messaging Protocol扣唱,簡單(流)文本定向消息協(xié)議茄厘,它提供了一個(gè)可互操作的連接格式蚁阳,允許STOMP客戶端與任意STOMP消息代理(Broker)進(jìn)行交互

js 前端鏈接websocket的方式

STOMP 鏈接成功夕凝、失敗宝穗、主動(dòng)斷開等參閱
STOMP 客戶端 API 整理:https://blog.csdn.net/jqsad/article/details/77745379

html頁面,連接stomp服務(wù)端,訂閱/topic/myTop的消息(訂閱topic或myTop)


方式1:
var socket = new SockJS('/endpointWechat'); 
方式2
    //前臺(tái) js 中 new SockJS 的時(shí)候,一起是3個(gè)參數(shù)項(xiàng)的烁挟,另外2個(gè)參數(shù)項(xiàng)可以傳遞目前是哪個(gè)用戶的標(biāo)識(shí)寇荧,這樣就可以在后臺(tái)區(qū)分出來連接是哪個(gè)用戶建立的
    var socket = new SockJS(url, undefined, {protocols_whitelist: ['websocket']});

方式3:直接攜帶參數(shù)
var socket = new SockJS('/endpointWechat'+ '?token='+str_token); //'?token=token8888'

Android端的鏈接方式

SpringBoot 使用的websocket 協(xié)議,不是標(biāo)準(zhǔn)的websocket協(xié)議齐疙,使用的是名稱叫做STOMP的協(xié)議障陶。
要想與js方式調(diào)用:stompClient.send("/sendServer", {}, JSON.stringify({ 'name': message }));届榄,需要Android采用STOMP方式調(diào)用

更多細(xì)節(jié)參考
stomp協(xié)議 官方:http://stomp.github.io/
csdn 大神博客:http://blog.csdn.net/chszs/article/details/5200554
iteye 大神博客 http://diaocow.iteye.com/blog/1725186 (務(wù)必看一下莉兰,了解協(xié)議的一些使用)
SpringBoot webSocket 發(fā)送廣播挑围、點(diǎn)對(duì)點(diǎn)消息,Android接收

WebSocket可以應(yīng)用于即時(shí)通信等場景糖荒,比如現(xiàn)在直播很火熱杉辙,直播中的彈幕也可以使用WebSocket去實(shí)現(xiàn)。
WebSocket的協(xié)議內(nèi)容可以見 The WebSocket Protocol捶朵,講得最全面的官方說明蜘矢。簡單介紹可以見維基百科WebSocket
在Android客戶端,一般可以使用下面的庫完成WebSocket通信

  • okhttp综看,一般人我不告訴他okhttp還可以用來進(jìn)行WebSocket通信
  • Java-WebSocket品腹,純java實(shí)現(xiàn)的WebSocket客戶端和服務(wù)端實(shí)現(xiàn)
image.png

Android最佳實(shí)踐——深入淺出WebSocket協(xié)議:https://blog.csdn.net/blueangle17/article/details/80701152


springboot 的工程


image.png

pom.xml引入

        <!--添加websocket依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <!--添加alibaba 的fastjson引用-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.46</version>
        </dependency>

編寫MyHandShakeInterceptor類(主要作用用于對(duì)握手前檢查合法性)

// 初始化對(duì)象,攔截握手红碑,發(fā)生在鏈接之前
@Component
public class MyHandShakeInterceptor implements HandshakeInterceptor {

    private static final Logger log = LoggerFactory.getLogger(MyHandShakeInterceptor.class);
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {


        //http://localhost:8080/endpointWechat/948/dtdzvrrs/websocket?token=token8888
        //js調(diào)用:
        //        var host="http://localhost:8080";
        //        var socket = new SockJS(host+'/endpointWechat' + '?token=token8888');

        log.info("this.getClass().getCanonicalName() = {},在這里決定是否允許鏈接,http協(xié)議轉(zhuǎn)換websoket協(xié)議進(jìn)行前, 握手前Url = {}",this.getClass().getCanonicalName(),request.getURI());


        //System.out.println(this.getClass().getCanonicalName() + " 在這里決定是否允許鏈接,http協(xié)議轉(zhuǎn)換websoket協(xié)議進(jìn)行前, 握手前"+request.getURI() );


        // http協(xié)議轉(zhuǎn)換websoket協(xié)議進(jìn)行前舞吭,可以在這里通過session信息判斷用戶登錄是否合法
        //request.getURI().getPath(); //   /endpointWechat/896/mdoqjqia/websocket
        //request.getURI().getHost();//localhost
        //request.getURI().string  ; //  http://localhost:8080/endpointWechat/896/mdoqjqia/websocket?token=token8888
        //request.getURI().getQuery() ;  // token=token8888
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
            HttpServletRequest httpRequest = servletServerHttpRequest.getServletRequest();

            String myToken = httpRequest.getParameter("token");
            if (null != myToken && !StringUtils.isEmpty(myToken)){
                WebSocketSession webSocketSession = SocketManager.get(myToken);
                if (webSocketSession != null){
                    log.info("token = {},已經(jīng)在建立鏈接列表,不允許重復(fù)鏈接",myToken);

                }else {
                    return true;

                }
            }



        }

        return false;  //不允許建立鏈接

        //return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
        //握手成功后,
        System.out.println(this.getClass().getCanonicalName() + "握手成功后...");
    }
}

不允許創(chuàng)建連接的情況

成功連接的情況


成功連接的情況

配置類WebSocketConfig

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Autowired
    private MyHandShakeInterceptor myHandShakeInterceptor;
    @Override
    public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
        //以 /endpointWechat端點(diǎn)析珊,客戶端就可以通過這個(gè)端點(diǎn)來進(jìn)行連接
        stompEndpointRegistry.addEndpoint("/endpointWechat").setAllowedOrigins("*")
                .withSockJS();//開啟SockJS支持
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic","/user"); //客戶端訂閱服務(wù)的前綴
        registry.setUserDestinationPrefix("/user");  //開啟一對(duì)一發(fā)送消息
    }

}

控制器WebScoketController


/**
 * 創(chuàng)建人:牽手生活
 * 創(chuàng)建時(shí)間:2019-01-14 17:17
 */

@Controller

public class WebSocketController {
    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;  //聲明SimpMessagingTemplate (或者使用@SendTo和@SendToUser注解)羡鸥,SimpMessagingTemplate可以在需要用到推送的地方如Controller,service忠寻,Component等地方

    private static final Logger log = LoggerFactory.getLogger(WebSocketController.class);



    // 收到消息記數(shù)
    private AtomicInteger count = new AtomicInteger(0);




    /**
     * @MessageMapping 指定要接收消息的地址惧浴,類似@RequestMapping。除了注解到方法上奕剃,也可以注解到類上
     * @MessageMapping("/receive") 對(duì)應(yīng)html中的  stompClient.send("/app/receive", {}, JSON.stringify({ 'name': name }));
     * 多出來的“/app"是WebSocKetConfig中定義的,如不定義衷旅,則HTML中對(duì)應(yīng)改為stompClient.send("/receive")
     * @SendTo默認(rèn) 消息將被發(fā)送到與傳入消息相同的目的地
     * 消息的返回值是通過{@link org.springframework.messaging.converter.MessageConverter}進(jìn)行轉(zhuǎn)換
     * @SendTo("/topic/getResponse") 指定訂閱路徑,對(duì)應(yīng)HTML中的stompClient.subscribe('/topic/getResponse', ……)
     * 意味將信息推送給所有訂閱了"/topic/getResponse"的用戶
     * @param requestMessage
     * @return
     */
    @MessageMapping("/receive")
    @SendTo("/topic/getResponse")  //topic是廣播全局通訊
    public ResponseMessage receive(RequestMessage requestMessage){
        log.info("receive message = {}" , JSONObject.toJSONString(requestMessage));
        ResponseMessage responseMessage = new ResponseMessage();
        responseMessage.setResponseMessage("響應(yīng)消息WebSocketController receive [" + count.incrementAndGet() + "] records:"+JSONObject.toJSONString(requestMessage));
        return responseMessage;
    }



    /**
     * 客戶端發(fā)消息纵朋,服務(wù)端接收
     *
     * @param requestMessage
     */
    // 相當(dāng)于RequestMapping
    @MessageMapping("/sendServer")
    public void sendServer(RequestMessage requestMessage) {
        log.info("sendServer 客服端發(fā)送的芜茵,不需要發(fā)回給客戶端message:{}", JSONObject.toJSONString(requestMessage));
    }

    @MessageMapping("/sendServer_str")
    public void sendServer_str(String message) {
        log.info("sendServer 客服端發(fā)送的,不需要發(fā)回給客戶端message:{}", message);
    }



    /**
     * 客戶端發(fā)消息倡蝙,大家都接收九串,相當(dāng)于直播說話
     *
     * @param message
     * @return
     */
    @MessageMapping("/sendAllUser_str")
    @SendTo("/topic/sendTopic_str")
    public String sendAllUser_str(String message) {
        // 也可以采用template方式
        return "服務(wù)的處理后的:"+message;
    }

    @MessageMapping("/sendAllUser")
    @SendTo("/topic/sendTopic")
    public ResponseMessage sendAllUser(RequestMessage requestMessage) {
        log.info("sendTopic 請(qǐng)求message = {}" , JSONObject.toJSONString(requestMessage));
        ResponseMessage responseMessage = new ResponseMessage();
        responseMessage.setResponseMessage(JSONObject.toJSONString(requestMessage));
        return responseMessage;
    }



    /**
     * 點(diǎn)對(duì)點(diǎn)用戶聊天,這邊需要注意寺鸥,由于前端傳過來json數(shù)據(jù)猪钮,所以使用@RequestBody
     * 這邊需要前端開通var socket = new SockJS(host+'/myUrl' + '?token=token8888');   token為指定name
     * @param map
     */
    @MessageMapping("/sendMyUser")
    public void sendMyUser(@RequestBody Map<String, String> map) {
        log.info("sendMyUser 請(qǐng)求 map = {}", map);
        WebSocketSession webSocketSession = SocketManager.get(map.get("name"));
        if (webSocketSession != null) {
            log.info("sendMyUser sessionId = {}", webSocketSession.getId());

            //生成IJSONResult對(duì)象的data數(shù)據(jù)
            ResponseMessage responseMessage = new ResponseMessage();
            responseMessage.setResponseMessage("響應(yīng)消息WebSocketController sendMyUser  records:"+map.get("message"));
            simpMessagingTemplate.convertAndSendToUser(map.get("name"), "/queue/sendUser", IJSONResult.ok(responseMessage));

            //simpMessagingTemplate.convertAndSendToUser(map.get("name"), "/queue/sendUser", JSONObject.toJSONString(responseMessage));  //ok
        }
    }

    @MessageMapping("/sendMyUser_obj")
    //@SendToUser("/user/queue/sendUser_obj")  //添加看看
    public ResponseMessage sendMyUser_obj(RequestMessage requestMessage) {
        log.info("sendMyUser message = {}" , JSONObject.toJSONString(requestMessage));
        ResponseMessage responseMessage = new ResponseMessage();
        responseMessage.setResponseMessage("響應(yīng)消息WebSocketController sendMyUser [" + count.incrementAndGet() + "] records:"+JSONObject.toJSONString(requestMessage));

        return responseMessage;
    }



    //http://localhost:8080//wechatTask/websocket/index 轉(zhuǎn)發(fā)到頁面
    @RequestMapping(value="/wechatTask/websocket/index")
    public String websocketIndex(HttpServletRequest req){

        log.info("websocketIndex接口的 req.getRemoteHost(){}" , req.getRemoteHost());
        return "websocket/simple/websocket-index";
    }
}


添加thymeleaf的模板-websocket-index.html

說明
客戶端可以通過使用Stomp.js和sockjs-client連接

var socket = new SockJS('/endpointWechat'+ '?token='+str_token);  


socket連接對(duì)象也可通過WebSocket(不通過SockJS)連接

var socket=new WebSocket('/endpointWechat'+ '?token='+str_token);

stompClient.connect()方法簽名:

client.connect(headers, connectCallback, errorCallback);

websocket-index.html如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Spring Boot+WebSocket例子</title>
    <script src="https://cdn.bootcss.com/sockjs-client/1.3.0/sockjs.min.js"></script>
    <script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
    <link  rel="stylesheet">
    <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
    <!--引入jqurey庫-->
    <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<body  onload="disconnect()" >

<div>
    <p> 創(chuàng)建鏈接:var socket = new SockJS(host+'/myUrl' + '?token=token8888')</p>
    <p>     token 是你上面輸入的token 或username</p>


    <p>點(diǎn)擊鏈接,如果鏈接成功胆建,則可以與后臺(tái)通過websocket進(jìn)行通信 </p>
    <p>輸入你要發(fā)送的內(nèi)容(這里是你的名字--以后改為json對(duì)象)</p>
    <p>點(diǎn)擊發(fā)送烤低,會(huì)把輸入的內(nèi)容通過websocket發(fā)送到后臺(tái)</p>

    <p>查看log 更多細(xì)節(jié)查看chrome的開發(fā)者選項(xiàng)中的控制臺(tái)</p>

</div>


<div >
    <label >輸入你新建鏈接是的token或name(用于點(diǎn)對(duì)點(diǎn)通信)?</label>
    <input type="text" id="mytoken"  value="wxid_on8oksh88zo22" placeholder="Your token/name here...如:4567"></input>
</div>
<div >
    <label id = "state-info" >***連接狀態(tài)-未連接***</label>

</div>
</div>

<div>
    <div>
        <button id="connect" onclick="connect();">連接</button>
        <button id="disconnect" disabled="disabled" onclick="disconnect();">斷開連接</button>
    </div>
    <div id="conversationDiv">
        <label>輸入你要發(fā)送的內(nèi)容</label><input type="text" id="message"  value="fdsfs"/>
        <button id="sendName" onclick="sendName();">發(fā)送</button>
        <button id="sendServer" onclick="sendServer();">發(fā)送sendServer</button>
        <button id="sendTopic" onclick="sendTopic();">發(fā)送sendTopic</button>
        <button id="sendMyUser" onclick="sendMyUser();">發(fā)送點(diǎn)對(duì)點(diǎn)</button>
        <!--用于顯示通過websocket的響應(yīng)數(shù)據(jù)-->
        <p id="response"></p>
    </div>
</div>


<script type="text/javascript">
    var stompClient = null;

    function setConnected(connected) {
        document.getElementById('connect').disabled = connected;
        document.getElementById('disconnect').disabled = !connected;
        document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
        $('#response').html();
    }

    function connect() {
        //判斷是否輸入token
        var val = $('#mytoken').val();
        var str_token = val.replace(/(^\s*)|(\s*$)/g, '');//去除空格;
        if (str_token == '' || str_token == undefined || str_token == null){
            alert('建立鏈接前笆载,需要輸入token');
            return
        }


        // websocket的連接地址扑馁,此值等于WebSocketMessageBrokerConfigurer中registry.addEndpoint("/endpointWechat").withSockJS()配置的地址
        //var socket = new SockJS('/endpointWechat'); //
        //建立連接對(duì)象(還未發(fā)起連接)
        var socket = new SockJS('/endpointWechat'+ '?token='+str_token); //'?token=token8888'
        //利用Stomp協(xié)議創(chuàng)建socket客戶端
        stompClient = Stomp.over(socket);

        /**
         * 調(diào)用stompClient中的connect方法來連接服務(wù)端涯呻,
         * 連接成功之后調(diào)用setConnected方法,該隱藏的隱藏腻要,該顯示的顯示
         */
        stompClient.connect({},
            function(frame) {
            setConnected(true);
            console.log('連接成功Connected: ' + frame);
            document.getElementById("state-info").innerHTML = "***連接成功***";
            // 客戶端訂閱消息的目的地址:此值WebSocketController中被@SendTo("/topic/getResponse")注解的里配置的值
            stompClient.subscribe('/topic/getResponse', function(respnose){ //2
                showResponse(JSON.parse(respnose.body).responseMessage);
            });

            //訂閱queue===接收廣播的
            stompClient.subscribe('/topic/sendTopic', function(response) {
                showResponse(JSON.parse(response.body).responseMessage);
                //showResponse((respnose.body).responseMessage);//string返回的處理
            });

            //接收發(fā)個(gè)單個(gè)人的===點(diǎn)對(duì)點(diǎn)===????????為什么會(huì)收不到
            var mytoken = $('#mytoken').val();
            stompClient.subscribe('/user/queue/sendUser', function(response) {  //  '/user/queue/sendUser'===/'+mytoken+'
                //showResponse(JSON.parse(response.body).responseMessage);
                showResponseBody(response.body);//直接顯示response.body

            });


        },
            function errorCallBack (error) {
                // 連接失敗時(shí)(服務(wù)器響應(yīng) ERROR 幀)的回調(diào)方法
                document.getElementById("state-info").innerHTML = "***連接失敗***";
                console.log('連接失敗【' + error + '】');
            }


        );//end for connected


    }


    function disconnect() {
        if (stompClient != null) {
            stompClient.disconnect();
        }
        setConnected(false);
        document.getElementById("state-info").innerHTML = "***連接未連接***";
        console.log("Disconnected");
    }


    //后臺(tái)采用@SendTo("/topic/getResponse")注解
    function sendName() {
        var message = $('#message').val();
        // 客戶端消息發(fā)送的目的:服務(wù)端使用WebSocketController中@MessageMapping("/receive")注解的方法來處理發(fā)送過來的消息
        stompClient.send("/receive", {}, JSON.stringify({ 'name': message }));
    }


    //后臺(tái)采用@SendTo("/topic/getResponse")注解
    function sendName() {
        var message = $('#message').val();
        // 客戶端消息發(fā)送的目的:服務(wù)端使用WebSocketController中@MessageMapping("/receive")注解的方法來處理發(fā)送過來的消息
        stompClient.send("/receive", {}, JSON.stringify({ 'name': message }));
    }

    function sendTopic() {  //發(fā)公告===類似發(fā)群公告
        var message = $('#message').val();
        // 客戶端消息發(fā)送的目的:服務(wù)端使用WebSocketController中@MessageMapping("/receive")注解的方法來處理發(fā)送過來的消息
        stompClient.send("/sendAllUser", {}, JSON.stringify({ 'name': message }));
    }

    function sendServer() {  //發(fā)送到服務(wù)端复罐,server不返回?cái)?shù)據(jù)
        var message = $('#message').val();
        // 客戶端消息發(fā)送的目的:服務(wù)端使用WebSocketController中@MessageMapping("/receive")注解的方法來處理發(fā)送過來的消息
        stompClient.send("/sendServer", {}, JSON.stringify({ 'name': message }));
    }

    function sendAllUser() {  //發(fā)公告===類直接發(fā)到群中,其實(shí)跟公告一樣
        var message = $('#message').val();
        // 客戶端消息發(fā)送的目的:服務(wù)端使用WebSocketController中@MessageMapping("/receive")注解的方法來處理發(fā)送過來的消息
        stompClient.send("/sendAllUser", {}, JSON.stringify({ 'name': message }));
    }

    //發(fā)送點(diǎn)對(duì)點(diǎn)通信---后臺(tái)對(duì)應(yīng)的采用的是simpMessagingTemplate.convertAndSendToUser
    function sendMyUser() {
        var message = $('#message').val();
        var mytoken = $('#mytoken').val();
        // 客戶端消息發(fā)送的目的:服務(wù)端使用WebSocketController中@MessageMapping("/receive")注解的方法來處理發(fā)送過來的消息
        //stompClient.send("/sendMyUser", {}, JSON.stringify({ 'name': message }));

        stompClient.send("/sendMyUser", {}, JSON.stringify({name:mytoken,message:message}));

    }

    //顯示消息
    function showResponse(message) {
        var response = $("#response");
        response.html("按ResponseMessage對(duì)象返回的responseMessage字段數(shù)據(jù):\""+message + "<br\>" + response.html());
    }

    function showResponseBody(response_body) {
        var response = $("#response");
        response.html("按websocket api 返回格式的response.body數(shù)據(jù):"+response_body + "<br\>" + response.html());
    }

</script>
</body>
</html>

通過controller轉(zhuǎn)發(fā)到html訪問websocket


http://localhost:8080//wechatTask/websocket/index

websocket鏈接情況

添加Socket鏈接的管理器SocketManager

image.png
public class SocketManager {
    private static final Logger log = LoggerFactory.getLogger(SocketManager.class);
    private static ConcurrentHashMap<String, WebSocketSession> manager = new ConcurrentHashMap<String, WebSocketSession>();

    public static void add(String key, WebSocketSession webSocketSession) {
        log.info("新添加webSocket連接 {} ", key);
        manager.put(key, webSocketSession);
    }

    public static void remove(String key) {
        log.info("移除webSocket連接 {} ", key);
        manager.remove(key);
    }

    public static WebSocketSession get(String key) {
        log.info("獲取webSocket連接 {}", key);
        return manager.get(key);
    }

    public static int connectedCount(){
        return manager.size();
    }


}

添加WebSocketDecoratorFactory(用于管理websocket的鏈接與斷開)


/**
 * 服務(wù)端和客戶端在進(jìn)行握手時(shí)會(huì)被執(zhí)行
 */
@Component
public class WebSocketDecoratorFactory implements WebSocketHandlerDecoratorFactory {
    private static final Logger log = LoggerFactory.getLogger(WebSocketDecoratorFactory.class);
    @Override
    public WebSocketHandler decorate(WebSocketHandler handler) {
        return new WebSocketHandlerDecorator(handler) {
            @Override
            public void afterConnectionEstablished(WebSocketSession session) throws Exception {
                log.info("有人連接啦  sessionId = {}", session.getId()+"鏈接數(shù)量"+SocketManager.connectedCount()+"****連接數(shù)一直為0則是校驗(yàn)了taken");

                Principal principal = session.getPrincipal();
                if (principal != null) {
                    log.info("key = {} 存入", principal.getName());
                    // 身份校驗(yàn)成功雄家,緩存socket連接
                    SocketManager.add(principal.getName(), session);
                }


                super.afterConnectionEstablished(session);
            }

            @Override
            public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
                log.info("有人退出連接啦  sessionId = {}", session.getId()+SocketManager.connectedCount());
                Principal principal = session.getPrincipal();
                if (principal != null) {
                    // 身份校驗(yàn)成功效诅,移除socket連接
                    SocketManager.remove(principal.getName());
                }
                super.afterConnectionClosed(session, closeStatus);
            }
        };
    }
}

WebSocketConfig中注入WebSocketDecoratorFactory,并重寫configureWebSocketTransport方法


image.png
java spring 后臺(tái)日志
2個(gè)瀏覽器連接后收到不同的信息

關(guān)于websocket的文章收錄

JMeter測試WebSocket的經(jīng)驗(yàn)總結(jié)
springboot集成websocket需要的都在這里

image.png

效果圖

Spring Boot系列十六 WebSocket簡介和spring boot集成簡單消息代理

image.png

使用spring boot +WebSocket實(shí)現(xiàn)(后臺(tái)主動(dòng))消息推送-@ServerEndpoint創(chuàng)立websocket endpoint--實(shí)現(xiàn)onOpen、onError趟济、onClose

//socket = new WebSocket("ws://localhost:9094/starManager/websocket/張三");
        var socket;
        if(typeof(WebSocket) == "undefined") {
            console.log("您的瀏覽器不支持WebSocket");
        }else{
            console.log("您的瀏覽器支持WebSocket");
            
            //實(shí)現(xiàn)化WebSocket對(duì)象乱投,指定要連接的服務(wù)器地址與端口  建立連接
            //socket = new WebSocket("ws://localhost:9094/starManager/websocket/張三")

            socket = new WebSocket("ws://localhost:9094/starManager/websocket");
            //打開事件
            socket.onopen = function() {
                console.log("Socket 已打開");
                //socket.send("這是來自客戶端的消息" + location.href + new Date());
            };
            //獲得消息事件
            socket.onmessage = function(msg) {
                console.log(msg.data);
                //發(fā)現(xiàn)消息進(jìn)入    調(diào)后臺(tái)獲取
                getCallingList();
            };
            //關(guān)閉事件
            socket.onclose = function() {
                console.log("Socket已關(guān)閉");
            };
            //發(fā)生了錯(cuò)誤事件
            socket.onerror = function() {
                alert("Socket發(fā)生了錯(cuò)誤");
            }
             $(window).unload(function(){
                  socket.close();
                });
 
//                                  $("#btnSend").click(function() {
//                                      socket.send("這是來自客戶端的消息" + location.href + new Date());
//                                  });
//
//                                  $("#btnClose").click(function() {
//                                      socket.close();
//                                  });
        }

SpringBoot使用WebSocket--SimpMessagingTemplate
聲明SimpMessagingTemplate (或者使用@SendTo和@SendToUser注解)
在需要用到推送的地方如Controller,service顷编,Component等地方聲明SimpMessagingTemplate
當(dāng)需要向客戶端推送消息時(shí)戚炫,調(diào)用convertAndSend方法,即可推送消息媳纬,此處“/topic/send”可隨意設(shè)置双肤,所有前端訂閱該url的客戶端都可以收到推送的消息。

messagingTemplate.convertAndSend("/topic/send", result);

springboot+websocket层宫,一篇足夠了--管理Socket的類-SocketManager
springboot+websocket杨伙,一篇足夠了--管理Socket的類-SocketManager -簡書
Spring-boot2 WebFlux WebSockit實(shí)現(xiàn)-實(shí)現(xiàn)心跳
websocket消息推送實(shí)現(xiàn)
[java+websocket實(shí)現(xiàn)網(wǎng)頁聊天室-今日頭條](https://www.toutiao.com/a6700358112806699528
spring websocket之sockjs超簡單現(xiàn)實(shí)
客戶端接收服務(wù)端消息推送sockjs-client的使用--new SockJS(url, _reserved, options)
利用Spring_Boot WebSocKet實(shí)現(xiàn)一個(gè)推送的小Demo--全局推送&點(diǎn)對(duì)點(diǎn)推動(dòng)-spring-boot-starter-security
Spring Boot通信之STOMP協(xié)議:后臺(tái)不發(fā)送心跳的問題

image.png

spring boot集成WebSocket實(shí)時(shí)輸出日志到web頁面--采用阻塞隊(duì)列
Spring消息之STOMP--留意參數(shù)Principal principal
什么是stomp其监?spring-boot websocket stomp服務(wù)構(gòu)建-@MessageMapping參數(shù)詳情

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末萌腿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子抖苦,更是在濱河造成了極大的恐慌毁菱,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锌历,死亡現(xiàn)場離奇詭異贮庞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)究西,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門窗慎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卤材,你說我怎么就攤上這事遮斥。” “怎么了扇丛?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵术吗,是天一觀的道長。 經(jīng)常有香客問我帆精,道長较屿,這世上最難降的妖魔是什么隧魄? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮隘蝎,結(jié)果婚禮上购啄,老公的妹妹穿的比我還像新娘。我一直安慰自己末贾,他們只是感情好闸溃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拱撵,像睡著了一般辉川。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拴测,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天乓旗,我揣著相機(jī)與錄音,去河邊找鬼集索。 笑死屿愚,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的务荆。 我是一名探鬼主播妆距,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼函匕!你這毒婦竟也來了娱据?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤盅惜,失蹤者是張志新(化名)和其女友劉穎中剩,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抒寂,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡结啼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屈芜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片郊愧。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖井佑,靈堂內(nèi)的尸體忽然破棺而出属铁,到底是詐尸還是另有隱情,我是刑警寧澤毅糟,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布红选,位于F島的核電站,受9級(jí)特大地震影響姆另,放射性物質(zhì)發(fā)生泄漏喇肋。R本人自食惡果不足惜坟乾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蝶防。 院中可真熱鬧甚侣,春花似錦、人聲如沸间学。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽低葫。三九已至详羡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嘿悬,已是汗流浹背实柠。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留善涨,地道東北人窒盐。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像钢拧,于是被迫代替她去往敵國和親蟹漓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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