• Spring WebSocket实现实时通信的详细教程


    简介

    WebSocket 是基于TCP/IP协议,独立于HTTP协议的通信协议。WebSocket 连接允许客户端和服务器之间的全双工通信,以便任何一方都可以通过已建立的连接将数据推送到另一方。

    我们常用的HTTP是客户端通过「请求-响应」的方式与服务器建立通信的,必须是客户端主动触发的行为,服务端只是做好接口被动等待请求。而在某些场景下的动作,是需要服务端主动触发的,比如向客户端发送消息、实时通讯、远程控制等。客户端是不知道这些动作几时触发的,假如用HTTP的方式,那么设备端需要不断轮询服务端,这样的方式对服务器压力太大,同时产生很多无效请求,且具有延迟性。于是才采用可以建立双向通讯的长连接协议。通过握手建立连接后,服务端可以实时发送数据与指令到设备端,服务器压力小。

    Spring WebSocket是Spring框架的一部分,提供了在Web应用程序中实现实时双向通信的能力。本教程将引导你通过一个简单的例子,演示如何使用Spring WebSocket建立一个实时通信应用。

    准备工作

    确保你的项目中已经引入了Spring框架的WebSocket模块。你可以通过Maven添加以下依赖:

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-websocketartifactId>
    dependency>
    

    创建WebSocket配置类(实现WebSocketConfigurer接口)

    首先,创建一个配置类,用于配置WebSocket的相关设置。

    package com.ci.erp.human.config;
    
    import com.ci.erp.human.handler.WebSocketHandler;
    import com.ci.erp.human.interceptor.WebSocketHandleInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.config.annotation.EnableWebSocket;
    import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
    import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
    import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    
    /**
     *
     * Websocket配置类
     *
     * @author lucky_fd
     * @since 2024-01-17
     */
    @Configuration
    @EnableWebSocket
    public class WebSocketConfig implements WebSocketConfigurer {
    
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            // 注册websocket处理器和拦截器
            registry.addHandler(webSocketHandler(), "/websocket/server")
                    .addInterceptors(webSocketHandleInterceptor()).setAllowedOrigins("*");
            registry.addHandler(webSocketHandler(), "/sockjs/server").setAllowedOrigins("*")
                    .addInterceptors(webSocketHandleInterceptor()).withSockJS();
        }
    
        @Bean
        public WebSocketHandler webSocketHandler() {
            return new WebSocketHandler();
        }
    
        @Bean
        public WebSocketHandleInterceptor webSocketHandleInterceptor() {
            return new WebSocketHandleInterceptor();
        }
    }
    
    

    上面的配置类使用@EnableWebSocket注解启用WebSocket,并通过registerWebSocketHandlers方法注册WebSocket处理器。

    • registerWebSocketHandlers:这个方法是向spring容器注册一个handler处理器及对应映射地址,可以理解成MVC的Handler(控制器方法),websocket客户端通过请求的url查找处理器进行处理

    • addInterceptors:拦截器,当建立websocket连接的时候,我们可以通过继承spring的HttpSessionHandshakeInterceptor来做一些事情。

    • setAllowedOrigins:跨域设置,*表示所有域名都可以,不限制, 域包括ip:port, 指定*可以是任意的域名,不加的话默认localhost+本服务端口

    • withSockJS: 这个是应对浏览器不支持websocket协议的时候降级为轮询的处理。

    创建WebSocket消息处理器(实现TextWebSocketHandler 接口)

    接下来,创建一个消息处理器,处理客户端发送的消息。

    package com.ci.erp.human.handler;
    
    import cn.hutool.core.util.ObjectUtil;
    import com.ci.erp.common.core.utils.JsonUtils;
    import com.ci.erp.human.domain.thirdVo.YYHeartbeat;
    import org.springframework.web.socket.CloseStatus;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.socket.handler.TextWebSocketHandler;
    
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     *
     * websocket处理类
     * 实现WebSocketHandler接口
     *
     * - websocket建立连接后执行afterConnectionEstablished回调接口
     * - websocket关闭连接后执行afterConnectionClosed回调接口
     * - websocket接收客户端消息执行handleTextMessage接口
     * - websocket传输异常时执行handleTransportError接口
     *
     * @author lucky_fd
     * @since 2024-01-17
     */
    
    public class WebSocketHandler extends TextWebSocketHandler {
    
        /**
         * 存储websocket客户端连接
         * */
        private static final Map connections = new HashMap<>();
    
        /**
         * 建立连接后触发
         * */
        @Override
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {
            System.out.println("成功建立websocket连接");
            // 建立连接后将连接以键值对方式存储,便于后期向客户端发送消息
            // 以客户端连接的唯一标识为key,可以通过客户端发送唯一标识
            connections.put(session.getRemoteAddress().getHostName(), session);
            System.out.println("当前客户端连接数:" + connections.size());
        }
    
        /**
         * 接收消息
         * */
        @Override
        protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
            System.out.println("收到消息: " + message.getPayload());
    		
    		// 收到客户端请求消息后进行相应业务处理,返回结果
            this.sendMessage(session.getRemoteAddress().getHostName(),new TextMessage("收到消息: " + message.getPayload()));
        }
    
        /**
         * 传输异常处理
         * */
        @Override
        public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
            super.handleTransportError(session, exception);
        }
    
        /**
         * 关闭连接时触发
         * */
        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
            System.out.println("触发关闭websocket连接");
            // 移除连接
            connections.remove(session.getRemoteAddress().getHostName());
        }
    
        @Override
        public boolean supportsPartialMessages() {
            return super.supportsPartialMessages();
        }
    
        /**
         * 向连接的客户端发送消息
         *
         * @author lucky_fd
         * @param clientId 客户端标识
         * @param message 消息体
         **/
        public void sendMessage(String clientId, TextMessage message) {
            for (String client : connections.keySet()) {
                if (client.equals(clientId)) {
                    try {
                        WebSocketSession session = connections.get(client);
                        // 判断连接是否正常
                        if (session.isOpen()) {
                            session.sendMessage(message);
                        }
                    } catch (IOException e) {
                        System.out.println(e.getMessage());
                    }
                    break;
                }
            }
        }
    }
    
    

    通过消息处理器,在开发中我们就可以实现向指定客户端或所有客户端发送消息,实现相应业务功能。

    创建拦截器

    拦截器会在握手时触发,可以用来进行权限验证

    package com.ci.erp.human.interceptor;
    
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.web.socket.WebSocketHandler;
    import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
    
    import java.util.Map;
    
    /**
     *
     * Websocket拦截器类
     *
     * @author lucky_fd
     * @since 2024-01-17
     */
    
    public class WebSocketHandleInterceptor extends HttpSessionHandshakeInterceptor {
    
        @Override
        public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
            System.out.println("拦截器前置触发");
            return super.beforeHandshake(request, response, wsHandler, attributes);
        }
    
        @Override
        public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
            System.out.println("拦截器后置触发");
            super.afterHandshake(request, response, wsHandler, ex);
        }
    }
    
    

    创建前端页面客户端

    最后,创建一个简单的HTML页面,用于接收用户输入并显示实时聊天信息。

    html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Spring WebSocket Chattitle>
        <script src="https://code.jquery.com/jquery-3.6.4.min.js">script>
        <script src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js">script>
    head>
    <body>
    
    请输入:<input type="text" id="message" placeholder="Type your message">
    <button onclick="sendMessage()">Sendbutton>
    <button onclick="websocketClose()">关闭连接button>
    <div id="chat">div>
    
    <script>
        var socket = null;
        if ('WebSocket' in window) {
        	// 后端服务port为22900
            socket = new WebSocket("ws://localhost:22900/websocket/server");
        } else if ('MozWebSocket' in window) {
            socket = new MozWebSocket("ws://localhost:22900/websocket/server");
        } else {
            socket = new SockJS("http://localhost:22900/sockjs/server");
        }
    
        // 接收消息触发
        socket.onmessage = function (event) {
            showMessage(event.data);
        };
        // 创建连接触发
        socket.onopen = function (event) {
            console.log(event.type);
        };
        // 连接异常触发
        socket.onerror = function (event) {
            console.log(event)
        };
        // 关闭连接触发
        socket.onclose = function (closeEvent) {
            console.log(closeEvent.reason);
        };
    
        //发送消息
        function sendMessage() {
            if (socket.readyState === socket.OPEN) {
                var message = document.getElementById('message').value;
                socket.send(message);
                console.log("发送成功!");
            } else {
                console.log("连接失败!");
            }
    
        }
    
        function showMessage(message) {
            document.getElementById('chat').innerHTML += '

    ' + message + '

    '
    ; } function websocketClose() { socket.close(); console.log("连接关闭"); } window.close = function () { socket.onclose(); };
    script> body> html>

    这个页面使用了WebSocket对象来建立连接,并通过onmessage监听收到的消息。通过输入框发送消息,将会在页面上显示。

    测试结果:

    后端日志:

    image

    前端界面:

    image

    Java客户端

    添加依赖

    <dependency>
          <groupId>org.java-websocketgroupId>
          <artifactId>Java-WebSocketartifactId>
          <version>1.4.0version>
    dependency>
    

    创建客户端类(继承WebsocketClient)

    package com.river.websocket;
     
    import org.java_websocket.client.WebSocketClient;
    import org.java_websocket.handshake.ServerHandshake;
     
    import java.net.URI;
    import java.net.URISyntaxException;
     
    public class MyWebSocketClient extends WebSocketClient {
     
        MyWebSocketClient(String url) throws URISyntaxException {
            super(new URI(url));
        }
     	// 建立连接
        @Override
        public void onOpen(ServerHandshake shake) {
            System.out.println(shake.getHttpStatusMessage());
        }
     	// 接收消息
        @Override
        public void onMessage(String paramString) {
            System.out.println(paramString);
        }
     	// 关闭连接
        @Override
        public void onClose(int paramInt, String paramString, boolean paramBoolean) {
            System.out.println("关闭");
        }
     	// 连接异常
        @Override
        public void onError(Exception e) {
            System.out.println("发生错误");
        }
    }
    

    测试websocket

    package com.river.websocket;
     
    import org.java_websocket.enums.ReadyState;
     
    import java.net.URISyntaxException;
     
    /**
     * @author lucky_fd
     * @date 2024-1-17
     */
    public class Client {
        public static void main(String[] args) throws URISyntaxException, InterruptedException {
            MyWebSocketClient client = new MyWebSocketClient("ws://localhost:22900/websocket/server");
            client.connect();
            while (client.getReadyState() != ReadyState.OPEN) {
                System.out.println("连接状态:" + client.getReadyState());
                Thread.sleep(100);
            }
            client.send("测试数据!");
            client.close();
        }
    }
    

    参考链接:

  • 相关阅读:
    参数方程求导
    《网络建设与运维》样题
    Python操作Excel常用方法汇总
    C++: 类和对象(下) (初始化列表, 隐式类型转换, static成员, 友元, 内部类, 匿名对象)
    Logstash、Mysql、Elasticsearch实现数据互通
    知识图谱应用---智慧医疗
    基于衰减因子和动态学习的改进樽海鞘群算法-附代码
    移动硬盘误删除要如何恢复呢?
    【Python项目】毕业设计必备——Python实现一个GUI版本的学生信息管理系统 | 附源码
    JavaScript实现选择排序
  • 原文地址:https://www.cnblogs.com/lucky-fd/p/17994082