zoukankan      html  css  js  c++  java
  • springboot之websocket,STOMP协议

      一、WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

      WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

    在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

      二、STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。

      三、首先,我们先理解一下为什么需要STOMP。

      1)常规的websocket连接和普通的TCP基本上没有什么差别的。

      2)那我们如果像http一样加入一些响应和请求层。

      3)所以STOMP在websocket上提供了一中基于帧线路格式(frame-based wire format)。

      4)简单一点,就是在我们的websocket(TCP)上面加了一层协议,使双方遵循这种协议来发送消息。

      四、STOMP

      1)Frame

      

      例如:

      

      command:CONNECT

      其他部分都是headers的一部分。

      2)command类别

        CONNECT

        SEND

        SUBSCRIBE

        UNSUBSCRIBE

        BEGIN

        COMMIT

        ABORT

        ACK

        NACK

        DISCONNECT

       3)客户端常用连接方式

      a、ws  

      var url = "ws://localhost:8080/websocket";
      var client = Stomp.client(url);

      b、sockJs

      <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
      <script>
        // use SockJS implementation instead of the browser's native implementation
        var ws = new SockJS(url);
        var client = Stomp.over(ws);
        [...]
      </script>

      说明:使用ws协议需要浏览器的支持,但是一些老版本的浏览器不一定支持。Stomp.over(ws)的凡是就是用来定义服务websocket的协议。

      4)服务端的实现过程

      

      a、服务端:/app,这里访问服务端,前缀通过设定的方式访问。

      b、用户:/user,这里针对的是用户消息的传递,针对于当前用户进行传递。

      c、其他消息:/topic、/queue,这两种方式。都是定义出来用于订阅。并且消息只能从这里通过并处理

       五、springboot的简单例子

      1)目录结构

      

      2)依赖包(pom.xml)

        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.0.RELEASE</version>
        </parent>
    
        <dependencies>
            <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.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
        </dependencies>

      3)websocket配置(WebSocketConfiguration、SecurityConfiguration

    /**
     * webSocket配置
     */
    @Configuration
    @EnableWebSocketMessageBroker
    public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
    
        /**
         * 注册stomp端点,主要是起到连接作用
         * @param stompEndpointRegistry
         */
        @Override
        public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
            stompEndpointRegistry
                    .addEndpoint("/webSocket")  //端点名称
                    //.setHandshakeHandler() 握手处理,主要是连接的时候认证获取其他数据验证等
                    //.addInterceptors() 拦截处理,和http拦截类似
                    .setAllowedOrigins("*") //跨域
                    .withSockJS(); //使用sockJS
    
        }
    
        /**
         * 注册相关服务
         * @param registry
         */
        @Override
        public void configureMessageBroker(MessageBrokerRegistry registry) {
            //这里使用的是内存模式,生产环境可以使用rabbitmq或者其他mq。
            //这里注册两个,主要是目的是将广播和队列分开。
            //registry.enableStompBrokerRelay().setRelayHost().setRelayPort() 其他方式
            registry.enableSimpleBroker("/topic", "/queue");
            //客户端名称前缀
            registry.setApplicationDestinationPrefixes("/app");
            //用户名称前
            registry.setUserDestinationPrefix("/user");
        }
    }

      认证配置:

    /**
     * 配置基本登录
     */
    @Configuration
    @EnableWebSecurity
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
    
        /**
         * 加密方式
         */
        @Autowired
        private BCryptPasswordEncoder passwordEncoder;
    
        /**
         * 所有请求过滤,包含webSocket
         * @param http
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests().anyRequest().authenticated()
            .and()
                .httpBasic();
        }
    
        /**
         * 加入两个用户测试不同用的接受情况
         * @param auth
         * @throws Exception
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication()
                .withUser("admin").password(passwordEncoder.encode("admin")).roles("ADMIN")
            .and()
                .withUser("user").password(passwordEncoder.encode("user")).roles("USER");
        }
    
        @Bean
        public BCryptPasswordEncoder bCryptPasswordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }

       4)服务端

    /**
     * 消息接收处理
     */
    @RestController
    public class MessageResource {
    
        //spring提供的推送方式
        @Autowired
        private SimpMessagingTemplate messagingTemplate;
    
        /**
         * 广播模式
         * @param requestMsg
         * @return
         */
        @MessageMapping("/broadcast")
        @SendTo("/topic/broadcast")
        public String broadcast(RequestMsg requestMsg) {
            //这里是有return,如果不写@SendTo默认和/topic/broadcast一样
            return "server:" + requestMsg.getBody().toString();
        }
    
        /**
         * 订阅模式,只是在订阅的时候触发,可以理解为:访问——>返回数据
         * @param id
         * @return
         */
        @SubscribeMapping("/subscribe/{id}")
        public String subscribe(@DestinationVariable Long id) {
            return "success";
        }
    
        /**
         * 用户模式
         * @param requestMsg
         * @param principal
         */
        @MessageMapping("/one")
        //@SendToUser("/queue/one") 如果存在return,可以使用这种方式
        public void one(RequestMsg requestMsg, Principal principal) {
            //这里使用的是spring的security的认证体系,所以直接使用Principal获取用户信息即可。
            //注意为什么使用queue,主要目的是为了区分广播和队列的方式。实际采用topic,也没有关系。但是为了好理解
            messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/one", requestMsg.getBody());
        }
    }

      客户端(JavaScript):

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>webSocket</title>
        <script src="js/jquery.js"></script>
        <script src="js/sockjs.min.js"></script>
        <script src="js/stomp.js"></script>
    </head>
    <body>
        <div>
            <button id="connect">连接</button>
            <button id="disconnect" disabled="disabled">断开</button>
        </div>
        <div>
            <h3>广播形式</h3>
            <button id="broadcastButton">发送</button><input id="broadcastText" type="text">
            <label>广播消息:</label><input id="broadcastMsg" type="text" disabled="disabled">
        </div>
        <div>
            <h3>订阅形式</h3>
            <label>订阅消息:</label><input id="subscribeMsg" type="text" disabled="disabled">
        </div>
        <div>
            <h3>角色形式</h3>
            <button id="userButton">发送</button><input id="userText" type="text">
            <label>用户消息:</label><input id="userMsg" type="text" disabled="disabled">
        </div>
        <div>
            <h3>无APP</h3>
            <button id="appButton">发送</button><input id="appText" type="text">
            <label>前端消息:</label><input id="appMsg" type="text" disabled="disabled">
        </div>
    </body>
    <script>
        var stomp = null;
        $("#connect").click(function () {
            var url = "http://localhost:8080/webSocket"
            var socket = new SockJS(url);
            stomp = Stomp.over(socket);
            //连接
            stomp.connect({}, function (frame) {
                //订阅广播
                stomp.subscribe("/topic/broadcast", function (res) {
                    $("#broadcastMsg").val(res.body);
                });
                //订阅,一般只有订阅的时候在返回
                stomp.subscribe("/app/subscribe/1", function (res) {
                    $("#subscribeMsg").val(res.body);
                });
                //用户模式
                stomp.subscribe("/user/queue/one", function (res) {
                    $("#userMsg").val(res.body);
                });
                //无APP
                stomp.subscribe("/topic/app", function (res) {
                    $("#appMsg").val(res.body);
                });
                setConnect(true);
            });
        });
    
        $("#disconnect").click(function () {
            if (stomp != null) {
                stomp.disconnect();
            }
            setConnect(false);
        });
        //设置按钮
        function setConnect(connectStatus) {
            $("#connect").attr("disabled", connectStatus);
            $("#disconnect").attr("disabled", !connectStatus);
        }
    
        //发送广播消息
        $("#broadcastButton").click(function () {
            stomp.send("/app/broadcast", {}, JSON.stringify({"body":$("#broadcastText").val()}))
        });
    
        //发送用户消息
        $("#userButton").click(function () {
            stomp.send("/app/one", {}, JSON.stringify({"body":$("#userText").val()}))
        });
    
        //发送web消息
        $("#appButton").click(function () {
            stomp.send("/topic/app", {}, JSON.stringify({"body":$("#appText").val()}))
        });
    </script>
    </html>

      5)普通测试

      角色测试:

       六、相关资料

      http://jmesnil.net/stomp-websocket/doc/

       七、源码:https://github.com/lilin409546297/springboot-websocket

  • 相关阅读:
    用mathematica求六元一次方程组且方程个数比变量个数少一个
    abaqus学习笔记-abaqus与umat调用基本原理
    abaqus UMAT二次开发能用fortran90吗?
    vba遗传算法之非一致性突变
    学习刘伟择优excel视频
    EXCEL中R1C1样式引用
    Oracle常见问题
    Oracle数据库 —— DML完结
    Bootstrap前端框架
    Jsoup学习笔记
  • 原文地址:https://www.cnblogs.com/ll409546297/p/10655392.html
Copyright © 2011-2022 走看看