zoukankan      html  css  js  c++  java
  • Spring消息之WebSocket

    一、WebSocket简介

        WebSocket 的定义?WebSocket是HTML5下一种全双工通信协议。在建立连接后,WebSocket服务器端和客户端都能主动的向对方发送和接收数据,就像Socket一样。

        WebSocket 的由来?众所周知,HTTP协议有“无连接”、“不可靠”、“尽最大努力”的特点。WebSocket的出现可以看成是HTTP协议为了支持长连接所打的一个大补丁。首先,由 IETF 制定发布了WebSocket 协议。后来,HTML5为了在Web端支持WebSocket协议,由W3C 发布了一整套WebSocket API。其次,WebSocket主要用于Web端,对于非Web部分的意义不大(毕竟直接使用TCP就好了)。因此,在广义上,Websocket 也常常被人称为是HTML5 下的通信协议。

        HTTP 和 WebSocket 的区别?

    1、HTTP 和 WebSocket 是两种不同的协议,但是HTTP负责了建立WebSocket的连接。

    2、HTTP 请求以 http:// 或 https:// 开始,WebSocket 请求一般以ws:// 或 wss:// 开始。

    3、所有浏览器都支持 HTTP 协议,WebScoket 可以会遇到不支持的浏览器(可通过SockJS解决)

    4、HTTP长连接中,每次数据交换除了真正的数据部分外,服务器和客户端还要大量交换HTTP header,信息交换效率很低。Websocket协议通过第一个request建立了TCP连接之后,之后交换的数据都不需要发送 HTTP header就能交换数据。

        可以看看知乎上的这个回答,解释的挺生动的:https://www.zhihu.com/question/20215561

    二、使用Spring的低层级WebSocket API

        先来看看客户端如何建立起WebSocket 的连接。首先,我们使用 new WebSocket(url) 创建一个WebSocket 的实例对象;然后,使用这个实例对象建立WebSocket的事件处理功能,onopen、onmessage、onclose 和 onerror 事件,分别对应着 打开连接、接收消息、关闭连接 和 异常处理 事件。

    复制代码
    /*WebSocket*/
    var url = 'ws://localhost:8080/marco2';
    var sock = new WebSocket(url);
    
    
    sock.onopen = function (ev) {
        console.log("正在建立连接...");
        sayMarco();
    };
    
    sock.onmessage = function (ev) {
        console.log("接收并处理消息:" + ev.data);
        if (count == 10) {
            sock.close();
        }
        setTimeout(
            function () {
                sayMarco();
                count++;
            }, 2000);
    };
    
    sock.onclose = function (ev) {
        console.log("连接关闭...");
    };
    
    sock.error = function (ev) {
        console.log("连接异常");
    };
    
    function sayMarco() {
        console.log('Sending Marco !');
        sock.send("Marco!")
    }
    复制代码

        接下来看看服务端这边如何建立起WebSocket的服务:

    1、pom 依赖

    复制代码
        <!--WebSocket-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-websocket</artifactId>
          <version>4.3.13.RELEASE</version>
        </dependency>
        <!--辅助包-->
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-core</artifactId>
          <version>2.8.10</version>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
          <version>2.8.10</version>
        </dependency>
        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-annotations</artifactId>
          <version>2.8.10</version>
        </dependency>
    复制代码

    2、WebSocket 服务

        有两种方案可以建立起WebSocket服务,一种是基于Spring 的 spring-websocket,一种是基于 java 的 websocket-api。

    • spring-websocket

        WebSocketHandler 接口定义了服务端处理WebSocket消息要做的一系列事情。相比直接实现WebSocketHandler,更为简单的方法是扩展AbstractWebSocketHandler,这是WebSocketHandler的一个抽象实现。当然根据处理消息的类型,还可以选择继承TextWebSocketHandler(文本类消息)、BinaryWebSocketHandler(二进制消息)等...

    复制代码
    public class MarcoHandler_2 extends AbstractWebSocketHandler {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(MarcoHandler_2.class);
    
        @Override
        public void afterConnectionEstablished(WebSocketSession session) {
            LOGGER.info("WebSocket 连接建立......");
        }
    
        @Override
        protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
    
            LOGGER.info("接收到消息:" + message.getPayload());
            Thread.sleep(2000);
            //发送文本消息
            session.sendMessage(new TextMessage("Polo!"));
    
        }
    
        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus){
            LOGGER.info("WebSocket 连接关闭......");
        }
    
    }
    复制代码
    • websocket-api

        websocket-api 提供了一种基于注解、更为简单明了的方式处理WebSocket消息。美中不足的是它需要依赖 javax.websocket-api.jar。

     pom依赖

    复制代码
        <dependency>
          <groupId>javax.websocket</groupId>
          <artifactId>javax.websocket-api</artifactId>
          <version>1.1</version>
          <scope>provided</scope>
        </dependency>
    复制代码

    ② WebSocket服务

    复制代码
    /**
     * Created by XiuYin.Cui on 2018/5/2.
     *
     * 基于注解方式的WebSocket 控制器
     */
    
    @ServerEndpoint("/ws")
    public class WsController {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(WsController.class);
    
        @OnOpen
        public void onOpen(){
            LOGGER.info("连接建立");
        }
    
        @OnClose
        public void onClose(){
            LOGGER.info("连接关闭");
        }
    
        @OnMessage
        public void onMessage(String message, Session session){
            try {
                LOGGER.info("接收到消息:" + message);
                Thread.sleep(2000);
                session.getBasicRemote().sendText("polo"); //发送消息
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        @OnError
        public void onError(Session session, Throwable throwable){
            throw new IllegalArgumentException(throwable);
        }
    
    
    }
    复制代码

    3、建立映射

        现在,已经有了消息处理器类,我们必须要对其进行配置,这样Spring才能将消息转发给它。在Spring的Java配置中,这需要在一个配置类上使用@EnableWebSocket,并实现WebSocketConfigurer接口。

        像所有HTTP请求一样,我们需要将WebSocket服务暴露成一个供客户端访问的url 地址。依旧有两种方式,配置类方式 和 XML方式:

    3.1、配置类方式
    复制代码
    @Configuration
    @EnableWebSocket
    public class WebSocketConfig implements WebSocketConfigurer {
    
        /**
         *
         * @param registry 该对象可以调用addHandler()来注册信息处理器。
         */
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
              registry.addHandler(marcoHandler_2(),"/marco2")
                      .addInterceptors(webSocketHandshakeInterceptor()) //声明拦截器
                      .setAllowedOrigins("*"); //声明允许访问的主机列表
        }
    
    
        @Bean
        public MarcoHandler_2 marcoHandler_2(){
            return new MarcoHandler_2();
        }
    
        @Bean
        public WebSocketHandshakeInterceptor webSocketHandshakeInterceptor(){
            return new WebSocketHandshakeInterceptor();
        }
    }
    复制代码
    3.2、xml 方式
        <websocket:handlers>
            <websocket:mapping handler="marcoHandler_1" path="/marco1"/>
        </websocket:handlers>

    三、使用SockJS支持WebSocket 

        既然已经有了WebSocket API 为什么还要有SockJS呢?

    1、WebSocket 是一个较新的协议规范,在Web浏览器和应用服务器上可能没有得到一致的支持。

    2、防火墙代理通常会限制所有除HTTP以外的流量。它们可能不支持或者还没有配置允许进行WebSocket 通信。

        SockJS 又是什么呢?

        SockJS是WebSocket技术的一种模拟,在表面上,它尽可能对应WebSocket API,但是在底层它非常智能。SockJS会优先选用WebSocket,但是如果WebSocket不可用的话,它将会从如下的方案中挑选最优的可行方案:

    • XHR流。
    • XDR流。
    • iFrame事件源。
    • iFrame HTML文件。
    • XHR轮询。
    • XDR轮询。
    • iFrame XHR轮询。
    • JSONP轮询。

        接下来让我们看看SockJS 的使用和WebSocket 有什么差异?

    • 客户端
    1、SockJS客户端库

    要在客户端使用SockJS,需要确保加载了SockJS客户端库。

                <script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
    2、修改URL,构建SockJS实例

    SockJS所处理的URL是“http://”或“https://”模式,而不是“ws://”和“wss://”。

                var url = 'http://localhost:8080/marcoSockJS';
                var sock = new SockJS(url);
    • 服务端

        服务端这边只要在建立映射的时候加上SockJS的支持即可:

    1、配置类方式
    复制代码
    @Configuration
    @EnableWebSocket
    public class WebSocketConfig implements WebSocketConfigurer {
    
        /**
         *
         * @param registry 该对象可以调用addHandler()来注册信息处理器。
         */
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
              //声明启用SockJS连接,如果前端还用 new WebSocket(url); 会报:Error during WebSocket handshake: Unexpected response code: 200
              registry.addHandler(marcoHandler_2(), "/marcoSockJS")
                      .setAllowedOrigins("*") ////声明允许访问的主机列表
                      .withSockJS();
        }
    
    
        @Bean
        public MarcoHandler_2 marcoHandler_2(){
            return new MarcoHandler_2();
        }
        
    }
    复制代码
    2、XML方式
        <websocket:handlers>
            <websocket:mapping handler="marcoHandler_1" path="/marco1"/>
            <websocket:sockjs/> <!--声明启用SockJS功能-->
        </websocket:handlers>

    效果展示:

    演示源码下载链接:https://github.com/JMCuixy/SpringWebSocket/tree/developer

  • 相关阅读:
    Codeforces 845E Fire in the City 线段树
    Codeforces 542D Superhero's Job dp (看题解)
    Codeforces 797F Mice and Holes dp
    Codeforces 408D Parcels dp (看题解)
    Codeforces 464D World of Darkraft
    Codeforces 215E Periodical Numbers 容斥原理
    Codeforces 285E Positions in Permutations dp + 容斥原理
    Codeforces 875E Delivery Club dp
    Codeforces 888F Connecting Vertices 区间dp (看题解)
    Codeforces 946F Fibonacci String Subsequences dp (看题解)
  • 原文地址:https://www.cnblogs.com/itrena/p/9004397.html
Copyright © 2011-2022 走看看