• 【小笔记】基于SpringBoot使用WebSocket进行前后端通信


    前言

    前端向后端发送消息请求相应的接口就可以了。但是有些场景:比如聊天室互相发送消息或者进度条跟踪。如果仅靠前端主动发起通信获得消息的话就需要不断的轮询后端获得最新数据。这种方式会造成带宽等资源的浪费(因为请求http请求可能包含较长的请求头,而真正有用的可能只是其中的一小部分),而且还会增加后端的压力。如果后端能主动向前端推送消息就能很简单的解决这个问题。

    一种比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源。
    在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
    WebSocket是一种在单个TCP连接上进行全双工通信的协议,WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket
    API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 - 百度百科[WebSocket]

    使用

    后端

    1:引入依赖
    springboot集成了WebSocket模块,直接以下引入依赖即可:

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-websocketartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    2:启用WebSocket
    创建配置类启用WebSocket支持,用@Configuration和@Bean纳入spring

    @Configuration
    public class WebSocketConfig {
    
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2:定义接口

    @Component
    // ServerEndpoint就是一个url,和Controller的@RequestMapping差不多
    @ServerEndpoint("/ws/test/{id}")
    public class WebSocketServer {
    
    
        /*-----------------注入Bean:start---------------*/
        /**注入bean的方式,因为spring管理的都是单例。WebSocket不是单例,所以要用这种方式注入Bean,直接将@Resource放到属性上注入会报错*/
        // 定义成静态的一次注入大家就都能访问到了
        private static TestResourceBean bean;
        @Resource
        public void setTestResourceBean(TestResourceBean bean) {
            WebSocketServer.bean = bean;
        }
        /*-----------------注入Bean:end---------------*/
    
    
        /**与某个客户端的连接会话,需要通过它来给客户端发送数据,
         * 也可以通过session().close();主动关闭连接
         * */
        private Session session;
    
        /**
         * 业务标识,用的到的话就加,用不到就不要
         */
        private String id;
    
        /**
         * 一个Map,其实就是一个容器,用来让你找到对应的session。
         * 你可以用任何方式实现,只要能满足你的业务就行。比如我不需要指定发送的话甚至可以直接定义为List
         * 定义为static是因为大家都要访问这个容器,要数据共通。或者你把这个容器放在另外一个类里面维护也行
         */
        public static Map<String, WebSocketServer> socketServerMap = new ConcurrentHashMap<>();
    
        /**
         * 连接建立成功调用的方法
         */
        @OnOpen
        public void onOpen(Session session, @PathParam("id") String id) {
            this.session = session;
            this.id = id;
            socketServerMap.put(id,this);
        }
    
        /**
         * 连接关闭,调用的方法。用isEnd做懒模式的结束
         */
        @OnClose
        public void onClose() {
            // 如果你用容器保存了实例,那么在关闭时记得移除,不然就内存溢出了
            socketServerMap.remove(this.id);
            /*...*/
        }
    
        /**
         * 错误回调
         */
        @OnError
        public void onError(Session session, Throwable error) {
            /*...*/
        }
    
        public Session getSession() {
            return this.session;
        }
    
        /**
         * 服务器主动推送消息,你可以把这个方法写成静态的,然后入参加一个id,这样就能通过静态方法定向推送消息:注意不要把session定义成静态哦,而是把容器定义为静态
         */
        public void sendMessage(String message) {
            try {
                this.session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    前端

    DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>websocket使用title>
    head>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">script>
    <script>
        var socket;
        function openSocket() {
            if(typeof(WebSocket) == "undefined") {
                console.log("您的浏览器不支持WebSocket");
            }else{
                console.log("您的浏览器支持WebSocket");
                //指定要连接的服务器地址与端口
                var socketUrl="http://localhost:8888/ws/test/2";
    			// 替换为ws,这里url直接写ws也是一样的
                socketUrl=socketUrl.replace("https","wss").replace("http","ws");
                console.log(socketUrl);
                if(socket!=null){
                    socket.close();
                    socket=null;
                }
    			// 实例化WebSocket对象,建立连接
                socket = new WebSocket(socketUrl);
                //打开事件
                socket.onopen = function() {
                    console.log("websocket已打开");
                };
                //获得消息事件
                socket.onmessage = function(msg) {
                    console.log(msg.data);
                    //消息进入后的处理逻辑
                };
                //关闭事件
                socket.onclose = function() {
                    console.log("websocket已关闭");
                };
                //发生了错误事件
                socket.onerror = function() {
                    console.log("websocket发生了错误");
                }
            }
        }
        function sendMessage() {
            if(typeof(WebSocket) == "undefined") {
                console.log("您的浏览器不支持WebSocket");
            }else {
                console.log("您的浏览器支持WebSocket");
                socket.send('这是一条测试消息');
            }
        }
    script>
    <body>
    <div><a onclick="openSocket()">连接socketa>div>
    <div><a onclick="sendMessage()">发送消息a>div>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    现在就能使用WebSocket进行前后端通信了。

    运维-nginx

    如果请求通过nginx做负载均衡,那么通过上述工作会发现还是连接不上,这是因为websocket监听的是端口,经过nginx转发后就失效了,此时需要在nginx上配置反向代理。

    
    
    #下面这一块表示的是:
    #如果 $http_upgrade 不为 '' (空),则 $connection_upgrade 为 upgrade 。
    #如果 $http_upgrade 为 '' (空),则 $connection_upgrade 为 close。
    map $http_upgrade $connection_upgrade {
            default upgrade;
            ''      close;
        }
    
    
    
    #这一块表示的是 nginx负载均衡,就是你想提供websocket服务的列表
    upstream websocket {    
    	server ip1:port1;
    	server ip2:port2;
    }
    
    
    
    #如果这台nginx负责多个server,记得要放到对应的那个里面
    
    server {
    
    ​	# 这里会有listen,server_name之类的,
        # /websocket/是指你要识别的链接,websocket这个字母你可以自定义
        # http://websocket;就是指向上面你配置的服务器,websocket也可以自定义,和上面upstream 对的上就行
    location /websocket/ {
      proxy_pass http://websocket;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection $connection_upgrade;
    }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    这一块可以看官方文档:WebSocket proxying
    这些配好就可以访问了。访问时注意,你的域名要和你的配置所在的server对应上。
    同时如果是https访问则需要用wss,如果是http则需要用ws。之前已经在前端工作这一块写出了:

    socketUrl=socketUrl.replace("https","wss").replace("http","ws");
    
    • 1

    参考资料:
    SpringBoot2.0集成WebSocket,实现后台向前端推送信息
    Nginx配置WebSocket

  • 相关阅读:
    【分布式事务】
    ROS多设备组网(WSL+miniPC+Nv Orin)
    springboot求职招聘系统java-php-python
    【昇思MindSpore】MindSpore的安装
    【USRP】软件无线电基础篇:长波、中波、短波
    ImageJ的单细胞荧光强度分析
    国际土壤模型协会 International Soil Modeling Consortium-ISMC
    Linux的tomcat的shutdown.sh关闭不了进程程
    导致爬虫无法使用的原因有哪些?
    PHP抓取远程文件到本地目录
  • 原文地址:https://blog.csdn.net/yue_hu/article/details/126181498