zoukankan      html  css  js  c++  java
  • 【springboot】【socket】spring boot整合socket,实现服务器端两种消息推送

    ========================前后台分别要做的事情========================

    一.前台 使用js【HTML页面】

    var websocket = new WebSocket("ws://localhost:8080/echo");

    获取socket连接。


    注意1:

      URL地址是"ws://后台服务IP:Port/路由地址"

    注意2:

      路由地址是任意自定义的。后台会有配置关于一个路由地址的【Handler处理器】和【Interceptor拦截器】

    注意3:

      一种路由地址之间,是可以相互通信的。

    例如:

      html1 中配置路由地址:echo html2 中也配置路由地址:echo
      在服务器配置了 这一种路由地址echo的【Handler处理器】和【Interceptor拦截器】后。

      这就代表,html1和html2都在服务器上注册了关于 路由地址echo 的webSocket服务。

      现在,只要html1使用echo注册的webSocket服务发送消息(websocket.send(json);),服务器端就会拦截到消息,并对其他注册了echo这个webSocket服务的页面发送信息。

      可以是对单点发送,也可以是对所有广播。

    二.后台服务器端 spring boot 集成 webSocket

    后台服务器端,需要做三件事情:


      1.配置webSocket的拦截器,实现HandshakeInterceptor接口

        (拦截和webSocket相关的信息)

      2.配置webSocket的处理器,实现WebSocketHandler接口

        (处理对一个路由地址的相关操作,例如 监听socket连接成功,socket关闭连接,处理信息等)

      3.完善webSocket的配置,实现WebSocketConfigurer接口

        (将处理器handler 和 一个路由地址 绑定,并对本地址设置拦截器Interceptor,最后设置访问域名的限制)

    =====================spring boot 集成 webSocket===================

    一.新建一个空的spring boot项目,项目依赖2.0.5 spring boot版本

    【完整项目结构在最下方可见】

    二.完善代码

    1.pom文件完整示例

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.5.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.sxd</groupId>
        <artifactId>socket-demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>socket-demo</name>
        <description>Demo project for Spring Boot And Socket</description>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <!--需要添加的jar 依赖-->
            <!-- json工具 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>LATEST</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-freemarker</artifactId>
            </dependency>
            <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.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <scope>compile</scope>
            </dependency>
    
    
        </dependencies>
    
    
    
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    View Code

    2.新建握手拦截器

    package com.sxd.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.http.server.ServletServerHttpRequest;
    import org.springframework.stereotype.Component;
    import org.springframework.stereotype.Service;
    import org.springframework.web.socket.WebSocketHandler;
    import org.springframework.web.socket.server.HandshakeInterceptor;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.Map;
    
    /**
     * 实现【握手拦截器】
     *
     * @Slf4j       注解使用解析 https://www.cnblogs.com/sxdcgaq8080/p/11288213.html
     * @Component   注解是将本拦截器注入为Bean给Spring容器管理
     *
     * @author sxd
     * @date 2019/8/2 13:47
     */
    @Slf4j
    @Component
    public class MyHandshakeInterceptor implements HandshakeInterceptor{
    
        /**
         * 重写方法 在握手之前做的事情
         * @param serverHttpRequest
         * @param serverHttpResponse
         * @param webSocketHandler
         * @param map
         * @return
         * @throws Exception
         */
        @Override
        public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
    
            if (serverHttpRequest instanceof ServletServerHttpRequest) {
                HttpServletRequest servletRequest = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();
                // 从session中获取到当前登录的用户信息. 作为socket的账号信息. session的的WEBSOCKET_USERNAME信息,在用户打开页面的时候设置.
                String userName = (String) servletRequest.getSession().getAttribute("WEBSOCKET_USERNAME");
                map.put("WEBSOCKET_USERNAME", userName);
            }
            return true;
        }
    
        /**
         * 重写方法 在握手之后做的事情
         * @param serverHttpRequest
         * @param serverHttpResponse
         * @param webSocketHandler
         * @param e
         */
        @Override
        public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
    
        }
    }
    View Code

    3.新建webSocket的handler处理器

    package com.sxd.config;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    import org.springframework.web.socket.*;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    /**
     * 实现WebSocketHandler接口
     *
     *
     * @author sxd
     * @date 2019/8/2 14:06
     */
    @Slf4j
    @Component
    public class MySocketHander implements WebSocketHandler {
    
    
        /**
         * 为了保存在线用户信息,在方法中新建一个list存储一下【实际项目依据复杂度,可以存储到数据库或者缓存】
         */
        private final static List<WebSocketSession> SESSIONS = Collections.synchronizedList(new ArrayList<>());
    
    
    
        @Override
        public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {
            log.info("链接成功......");
            SESSIONS.add(webSocketSession);
            String userName = (String) webSocketSession.getAttributes().get("WEBSOCKET_USERNAME");
            if (userName != null) {
                JSONObject obj = new JSONObject();
                // 统计一下当前登录系统的用户有多少个
                obj.put("count", SESSIONS.size());
                users(obj);
                webSocketSession.sendMessage(new TextMessage(obj.toJSONString()));
            }
        }
    
        @Override
        public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
            log.info("处理要发送的消息");
            JSONObject msg = JSON.parseObject(webSocketMessage.getPayload().toString());
            JSONObject obj = new JSONObject();
            if (msg.getInteger("type") == 1) {
                //给所有人
                obj.put("msg", msg.getString("msg"));
                sendMessageToUsers(new TextMessage(obj.toJSONString()));
            } else {
                //给个人
                String to = msg.getString("to");
                obj.put("msg", msg.getString("msg"));
                sendMessageToUser(to, new TextMessage(obj.toJSONString()));
            }
        }
    
        @Override
        public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
            if (webSocketSession.isOpen()) {
                webSocketSession.close();
            }
            log.info("链接出错,关闭链接......");
            SESSIONS.remove(webSocketSession);
        }
    
        @Override
        public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
            log.info("链接关闭......" + closeStatus.toString());
            SESSIONS.remove(webSocketSession);
        }
    
        @Override
        public boolean supportsPartialMessages() {
            return false;
        }
    
    
        /**
         * 给某个用户发送消息
         *
         * @param userName
         * @param message
         */
        public void sendMessageToUser(String userName, TextMessage message) {
    
            log.info("发消息给某个用户");
            for (WebSocketSession user : SESSIONS) {
                if (user.getAttributes().get("WEBSOCKET_USERNAME").equals(userName)) {
                    try {
                        if (user.isOpen()) {
                            user.sendMessage(message);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    break;
                }
            }
        }
    
        /**
         * 给所有在线用户发送消息
         *
         * @param message
         */
        public void sendMessageToUsers(TextMessage message) {
            log.info("发送消息给所用用户");
            for (WebSocketSession user : SESSIONS) {
                try {
                    if (user.isOpen()) {
                        user.sendMessage(message);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 将系统中的用户传送到前端
         *
         * @param obj
         */
        private void users(JSONObject obj) {
            List<String> userNames = new ArrayList<>();
            for (WebSocketSession webSocketSession : SESSIONS) {
                userNames.add((String) webSocketSession.getAttributes().get("WEBSOCKET_USERNAME"));
            }
            obj.put("users", userNames);
        }
    
    
    }
    View Code

    4.新建webSocket的config配置

    package com.sxd.config;
    
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.config.annotation.EnableWebSocket;
    import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
    import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    
    /**
     * @author sxd
     * @date 2019/8/2 14:43
     */
    @Slf4j
    @Configuration
    @EnableWebSocket
    public class MyWebSocketConfig  implements WebSocketConfigurer {
    
        @Autowired
        MyHandshakeInterceptor handshakeInterceptor;
    
        @Autowired
        MySocketHander socketHander;
    
    
    
    
        /**
         * 实现 WebSocketConfigurer 接口
         * 重写 registerWebSocketHandlers 方法,这是一个核心实现方法,配置 websocket 入口,允许访问的域、注册 Handler、SockJs 支持和拦截器。
         *
         * registry.addHandler()注册和路由的功能
         *      当客户端发起 websocket 连接,把 /path 交给对应的 handler 处理,而不实现具体的业务逻辑,可以理解为收集和任务分发中心。
         *
         *
         * addInterceptors() 顾名思义就是为 handler 添加拦截器
         *      可以在调用 handler 前后加入我们自己的逻辑代码。
         *
         *
         * setAllowedOrigins(String[] domains),允许指定的域名或 IP (含端口号)建立长连接
         *      如果只允许自家域名访问,这里轻松设置。如果不限时使用 * 号,如果指定了域名,则必须要以 http 或 https 开头。
         *
         * @param registry
         */
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            //部分 支持websocket 的访问链接,允许跨域
            registry.addHandler(socketHander, "/my_SXD_Socket").addInterceptors(handshakeInterceptor).setAllowedOrigins("*");
            //部分 不支持websocket的访问链接,允许跨域
            registry.addHandler(socketHander, "/sockjs/my_SXD_Socket").addInterceptors(handshakeInterceptor).setAllowedOrigins("*").withSockJS();
        }
    }
    View Code

    5.新建controller用于模拟用户登录,给Session填充值

    package com.sxd.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * 本controller中的三个方法,即相当于一个login接口的 三次请求,只不过值写死在方法中。
     *
     * 只需要分别请求三个接口,即相当于三个用户分别作了登录操作,并存储到了session中。
     *
     * @author sxd
     * @date 2019/8/2 14:48
     */
    @Controller
    @RequestMapping("/socket")
    public class LoginController {
    
    
    
    
        /**
         * 第一个用户
         *
         * @param request
         * @return
         */
        @RequestMapping("/chat1")
        public String chat1(HttpServletRequest request) {
            // 假设用户tom登录,存储到session中
            request.getSession().setAttribute("WEBSOCKET_USERNAME", "tom");
            return "chat1";
        }
    
        /**
         * 第二个用户登录
         *
         * @param request
         * @return
         */
        @RequestMapping("/chat2")
        public String chat2(HttpServletRequest request) {
            // 假设用户jerry登录,存储到session中
            request.getSession().setAttribute("WEBSOCKET_USERNAME", "jerry");
            return "chat2";
        }
    
        /**
         * 第三个用户登录
         *
         * @param request
         * @return
         */
        @RequestMapping("/chat3")
        public String chat3(HttpServletRequest request) {
            // 假设用户jack登录,存储到session中
            request.getSession().setAttribute("WEBSOCKET_USERNAME", "jack");
            return "chat3";
        }
    
    }
    View Code

    6.完善application.properties项目配置,全是freemarker的配置

    spring.freemarker.template-loader-path=classpath:/templates
    spring.freemarker.cache=false
    spring.freemarker.charset=UTF-8
    spring.freemarker.check-template-location=true
    spring.freemarker.content-type=text/html
    spring.freemarker.expose-request-attributes=false
    spring.freemarker.expose-session-attributes=false
    spring.freemarker.request-context-attribute=request
    spring.freemarker.suffix=.html
    View Code

    7.新建一个templates目录在resource下,并复制下面三份,分别在templates下创建chat1.html、chat2.html、chat3.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <title>测试websocket</title>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/css/bootstrap.min.css">
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.css">
    </head>
    <body>
    <div class="container">
        <div class="input-group mb-3">
            <div class="input-group-prepend">
                <label class="input-group-text" for="inputGroupSelect01">用户</label>
            </div>
            <select class="custom-select" id="inputGroupSelect01">
                <option selected>选择一个...</option>
            </select>
        </div>
        <div class="input-group mb-3">
            <input type="text" class="form-control">
            <div class="input-group-append">
                <span class="input-group-text" id="btn1">发送给所有人</span>
            </div>
        </div>
        <div class="input-group mb-3">
            <input type="text" class="form-control">
            <div class="input-group-append">
                <span class="input-group-text" id="btn2">发送给单人</span>
            </div>
        </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.2/js/bootstrap.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.js"></script>
    <script language=javascript>
        $(function () {
            var websocket;
            if ('WebSocket' in window) {
                console.log("WebSocket-->");
                //new WebSocket()在IDEA打开的任何项目中都可以 直接调用
                websocket = new WebSocket("ws://localhost:8080/my_SXD_Socket");
            } else if ('MozWebSocket' in window) {
                console.log("MozWebSocket-->");
                websocket = new MozWebSocket("ws://my_SXD_Socket");
            } else {
                console.log("SockJS-->");
                websocket = new SockJS("http://127.0.0.1:8080/sockjs/my_SXD_Socket");
            }
    
    
            websocket.onopen = function (evnt) {
                console.log("链接服务器成功!", evnt.data);
            };
            websocket.onmessage = function (evnt) {
                console.log('收到消息:', evnt.data);
                var json = JSON.parse(evnt.data);
                if (json.hasOwnProperty('users')) {
                    var users = json.users;
                    for (var i = 0; i < users.length; i++) {
                        $("#inputGroupSelect01").append('<option value="' + users[i] + '">' + users[i] + '</option>');
                    }
                } else {
                    //打印消息
                    toast(json.msg, 'info')
                }
            };
            websocket.onerror = function (evnt) {
            };
            websocket.onclose = function (evnt) {
                console.log("与服务器断开了链接!")
            }
            $('#btn2').bind('click', function () {
                if (websocket != null) {
                    //根据勾选的人数确定是群聊还是单聊
                    var value = $(this).parent().parent().find('input').val();
                    //得到选择的用户
                    var name = $("#inputGroupSelect01").find("option:selected").val();
                    console.log('选中的用户', name);
                    if (name === '选择一个...') {
                        toast('请选择一个用户', 'warning')
                    } else {
                        var object = {
                            to: name,
                            msg: value,
                            type: 2
                        };
                        //将object转成json字符串发送给服务端
                        var json = JSON.stringify(object);
                        websocket.send(json);
                    }
                } else {
                    console.log('未与服务器链接.');
                }
            });
            $('#btn1').bind('click', function () {
                if (websocket != null) {
                    //根据勾选的人数确定是群聊还是单聊
                    var value = $(this).parent().parent().find('input').val();
                    var object = {
                        msg: value,
                        type: 1
                    };
                    //将object转成json字符串发送给服务端
                    var json = JSON.stringify(object);
                    websocket.send(json);
                } else {
                    console.log('未与服务器链接.');
                }
            });
        })
    
        function toast(text, icon) {
            $.toast({
                text: text,
                heading: '新消息',
                icon: icon,
                showHideTransition: 'slide',
                allowToastClose: true,
                hideAfter: 3000,
                stack: 5,
                position: 'top-right',
    
                bgColor: '#444444',
                textColor: '#eeeeee',
                textAlign: 'left',
                loader: true,
                loaderBg: '#006eff'
            });
        }
    </script>
    </body>
    </html>
    View Code

    8.最终项目结构如下:

    9.启动项目,并分别访问下面三个URL,模拟用户登录了

    http://localhost:8080/socket/chat1
    
    http://localhost:8080/socket/chat2
    
    http://localhost:8080/socket/chat3

     10.发送消息给单人或多人

    发送给指定的人:

    发送给所有人:

    参考地址:https://www.cnblogs.com/hhhshct/p/8849449.html

  • 相关阅读:
    Mysql:Error Code 1235,This version of MySQL doesn’t yet support ‘LIMIT & IN/ALL/ANY/SOME 错误解决
    java 发送带Basic Auth认证的http post请求
    spring-boot 加载本地静态资源文件路径配置
    mac上使用终端生成RSA公钥和密钥
    mysql 查看正在运行的进程
    mysql中You can’t specify target table for update in FROM clause错误解决方法
    Redis的三种启动方式
    Redis在Mac下的安装与使用方法
    Linux vi中查找字符内容的方法
    spring boot 之@JsonView 简单介绍
  • 原文地址:https://www.cnblogs.com/sxdcgaq8080/p/10690644.html
Copyright © 2011-2022 走看看