• 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

  • 相关阅读:
    Class文件和JVM的恩怨情仇
    详解及对比创建线程的三种方式
    浅析Java中线程组(ThreadGroup类)
    简单定义多线程!
    五分钟看懂UML类图与类的关系详解
    LeetCode刷题--14.最长公共前缀(简单)
    LeetCode刷题--13.罗马数字转整数(简答)
    动态规划算法详解及经典例题
    LeetCode--9.回文数(简单)
    LeetCode刷题--7.整数反转(简单)
  • 原文地址:https://www.cnblogs.com/itrena/p/9004397.html
Copyright © 2020-2023  润新知