websocket(三)–基於sockjs和stomp實現點對點通信

websocket(三)–基於sockjs和stomp實現點對點通信

一、簡介

這裏將介紹基於sockjs和stomp實現的點對點通信,即客戶端發消息到服務端,服務端處理後返回消息給原客戶端(或者發送消息給指定客戶端),點對點通信,不是廣播。由於點對點通信和廣播有類似,這裏僅對需要使用到的新內容進行介紹,以及完整的點對點通信示例。

二、新知識點

2.1 org.springframework.messaging.simp.SimpMessagingTemplate

提供給用戶發送消息的方法。

2.2 @SendToUser

點對點通信中,客戶端訂閱的地址。

2.3 java.security.Principal

用戶身份標識,需要在WebSocketMessageBrokerConfigurer.configureClientInboundChannel方法設置用戶信息。

2.4 org.springframework.messaging.simp.stomp.StompHeaderAccessor

請求消息頭信息。

三、完整示例

3.1 添加maven依賴

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

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

3.2 spring mvc配置

package com.dragon.single_talk.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    /
     * 添加靜態文件
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/").addResourceLocations("classpath:/static/");
    }
}

3.3 websocket配置

package com.dragon.single_talk.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.config.ChannelRegistration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

import java.security.Principal;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        //addEndpoint客戶端連接地址,setAllowedOrigins允許連接的域名,withSockJS支持以SockJS連接服務器
        registry.addEndpoint("/endpoint").setAllowedOrigins("*").withSockJS();
    }

    /
     * 設置消息代理,默認會配置一個簡單的內存消息代理
     *
     * @param registry
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        //表示在/singleTalkClient地址前綴上,服務端可以向客戶端發送消息(也是客戶端訂閱地址前綴),即是@SendToUser中地址對應
        registry.enableSimpleBroker("/singleTalkClient");
        //點對點推送時,客戶端訂閱消息的前綴,/user爲默認值
        registry.setUserDestinationPrefix("/user");
    }

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new ChannelInterceptor() {
            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    String uid = accessor.getNativeHeader("uid").get(0);
                    Principal principal = () -> uid;
                    //設置用戶信息
                    accessor.setUser(principal);
                    return message;
                }
                return message;
            }
        });
    }
}

3.4 定義消息載體類

package com.dragon.single_talk.bean;

import lombok.Data;

/
 * 消息載體
 */
@Data
public class MsgBean {
    private String uid;
    private String msg;
}

3.5 定義轉發頁面和消息的controller

package com.dragon.single_talk.controller;

import com.dragon.single_talk.bean.MsgBean;
import org.springframework.messaging.Message;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import java.security.Principal;
import java.util.Map;

@Controller
public class SingleWebSocketController {

    //提供給用戶發送消息處理
    @Resource
    private SimpMessagingTemplate simpMessagingTemplate;

    @RequestMapping("/singleTalk/{uid}")
    public String simpleTalk(Model model, @PathVariable String uid){
        model.addAttribute("uid", uid);
        return "singleTalk";
    }

    @MessageMapping("/singleTalkServer")  //客戶端發到服務端
    @SendToUser(value = "/singleTalkClient", broadcast = true)  //服務端發到客戶端,客戶端訂閱, broadcast是否推送到同一session的不同終端頁面中
    public MsgBean singleTalk(MsgBean msg,
                              StompHeaderAccessor accessor, //所有消息頭信息
                              @Headers Map<String, Object> headers, //所有頭部值
                              Principal principal ,  //登錄驗證信息
                              @Header(name="simpSessionId") String sessionId, //指定頭部的值 ,這裏指sessionId
                              Message message,   //完整消息,包含消息頭和消息體(即header和body)
                              @Payload  String body){ //消息體內容

        //給指定客戶端發消息
//        simpMessagingTemplate.convertAndSendToUser("1", "/singleTalkClient", msg);

        //消息直接返回
        return msg;
    }
}

3.6 spring boot配置文件application.properties

server.port=8080

3.7 spring boot啓動類

package com.dragon.single_talk;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SingleTalkApplication {

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

}

3.8 添加前端靜態文件

添加前端靜態文件jquery-3.1.1.min.js、sockjs.js、stomp.min.js

3.9 前端頁面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <style>
        #historyId {
            border: 1px black solid;
            height: 370px;
            width: 500px;
            overflow-y: scroll;
            word-break: break-all;
        }

        #msgId {
            width: 500px;
            height: 130px;
        }

        #talkId {
            /*text-align: center;*/
        }
    </style>

</head>
<body>
<section id="talkId">
    <header>
        tip:<span id="tipId" th:text="${uid}"></span>
    </header>

    <article id="historyId">

    </article>
    <article>
        <textarea id="msgId"></textarea>
    </article>
    <footer>
        <button id="sendId">send</button>
        <button id="closeId">close</button>
        <button id="clearId">clear</button>
        <button id="connectId">connect</button>
    </footer>
</section>

<script src="../static/js/jquery-3.1.1.min.js"></script>
<script src="../static/js/sockjs.js"></script>
<script src="../static/js/stomp.min.js"></script>
<script type="text/javascript" th:inline="javascript">
    $(function () {
        var client;
        var headers = {
            uid: [[${uid}]]
        };
        var connect = function () {
            var socket = new SockJS("http://localhost:8080/endpoint");
            client = Stomp.over(socket);
            client.connect(headers, function (frame) {
                //訂閱地址要以/user爲前綴,標識是點對點通信,只有當前用戶纔會收到
                client.subscribe("/user/singleTalkClient",
                    function (response) {
                        var obj = JSON.parse(response.body);
                        console.log("receive: " + JSON.stringify(response));
                        $("#historyId").append("<p>" + obj.uid + ":" + obj.msg + "</p>");
                        $("#historyId").scrollTop($("#historyId")[0].scrollHeight);
                    },
                    function (err) {
                        console.log("err: " + err);
                    }
                );
            });
            $("#tipId").html("connect success");
        }

        var sendMsg = function () {
            var msg = $("#msgId").val();
            client.send("/singleTalkServer", headers, JSON.stringify({'uid': [[${uid}]], 'msg': msg}));
            $("#msgId").val("")
        };

        //點按鈕發送消息
        $("#sendId").click(function () {
            sendMsg();
        });

        //回車發消息
        $(document).keyup(function (e) {
            if (e.keyCode == 13) {
                sendMsg();
            }
        });

        //清空記錄
        $("#clearId").click(function () {
            $("#historyId").empty();
        });

        //連接
        $("#connectId").click(function () {
            connect();
        });

        //關閉連接
        $("#closeId").click(function () {
            client.disconnect();
            $("#tipId").html("connect close");
        });

    })
</script>
</body>
</html>

至此,點對點通信示例完成。
http://localhost:8080/singleTalk/1
http://localhost:8080/singleTalk/
2
使用上面地址測試,可驗證只有當前客戶端纔會收到發出的消息。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章