zoukankan      html  css  js  c++  java
  • Spring Boot 集成 WebSocket,轻松实现信息推送!

    在一次项目开发中,使用到了Netty网络应用框架,以及MQTT进行消息数据的收发,这其中需要后台来将获取到的消息主动推送给前端,于是就使用到了MQTT,特此记录一下。

    一、什么是websocket?

    WebSocket协议是基于TCP的一种新的网络协议。它实现了客户端与服务器全双工通信,学过计算机网络都知道,既然是全双工,就说明了服务器可以主动发送信息给客户端

    这与我们的推送技术或者是多人在线聊天的功能不谋而合。

    为什么不使用HTTP 协议呢?

    这是因为HTTP是单工通信,通信只能由客户端发起,客户端请求一下,服务器处理一下,这就太麻烦了。于是websocket应运而生。

    下面我们就直接开始使用Springboot开始整合。以下案例都在我自己的电脑上测试成功,你可以根据自己的功能进行修改即可。

    我的项目结构如下:

    二、使用步骤

    1.添加依赖

    Maven依赖:

    <dependency>  
        <groupId>org.springframework.boot</groupId>  
        <artifactId>spring-boot-starter-websocket</artifactId>  
    </dependency> 
    

    2.启用Springboot对WebSocket的支持

    启用WebSocket的支持也是很简单,几句代码搞定:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    /**
     * @ Auther: 马超伟
     * @ Date: 2020/06/16/14:35
     * @ Description: 开启WebSocket支持
     */
    @Configuration
    public class WebSocketConfig {
        @Bean
        public ServerEndpointExporter serverEndpointExporter() {
            return new ServerEndpointExporter();
        }
    }
    

    Spring Boot 最新教程推荐看这个:https://github.com/javastacks/spring-boot-best-practice

    3.核心配置:WebSocketServer

    因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller

    • @ ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端, 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
    • 新建一个ConcurrentHashMap webSocketMap 用于接收当前userId的WebSocket,方便传递之间对userId进行推送消息。

    下面是具体业务代码:

    package cc.mrbird.febs.external.webScoket;
    
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    import org.springframework.stereotype.Service;
    
    import javax.websocket.*;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import java.io.IOException;
    import java.time.LocalDateTime;
    import java.util.List;
    import java.util.concurrent.CopyOnWriteArraySet;
    
    /**
     * Created with IntelliJ IDEA.
     * @ Auther: 马超伟
     * @ Date: 2020/06/16/14:35
     * @ Description:
     * @ ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
     * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
     */
    @Component
    @Slf4j
    @Service
    @ServerEndpoint("/api/websocket/{sid}")
    public class WebSocketServer {
        //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
        private static int onlineCount = 0;
        //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
        private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();
    
        //与某个客户端的连接会话,需要通过它来给客户端发送数据
        private Session session;
    
        //接收sid
        private String sid = "";
    
        /**
         * 连接建立成功调用的方法
         */
        @OnOpen
        public void onOpen(Session session, @PathParam("sid") String sid) {
            this.session = session;
            webSocketSet.add(this);     //加入set中
            this.sid = sid;
            addOnlineCount();           //在线数加1
            try {
                sendMessage("conn_success");
                log.info("有新窗口开始监听:" + sid + ",当前在线人数为:" + getOnlineCount());
            } catch (IOException e) {
                log.error("websocket IO Exception");
            }
        }
    
        /**
         * 连接关闭调用的方法
         */
        @OnClose
        public void onClose() {
            webSocketSet.remove(this);  //从set中删除
            subOnlineCount();           //在线数减1
            //断开连接情况下,更新主板占用情况为释放
            log.info("释放的sid为:"+sid);
            //这里写你 释放的时候,要处理的业务
            log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
    
        }
    
        /**
         * 收到客户端消息后调用的方法
         * @ Param message 客户端发送过来的消息
         */
        @OnMessage
        public void onMessage(String message, Session session) {
            log.info("收到来自窗口" + sid + "的信息:" + message);
            //群发消息
            for (WebSocketServer item : webSocketSet) {
                try {
                    item.sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * @ Param session
         * @ Param error
         */
        @OnError
        public void onError(Session session, Throwable error) {
            log.error("发生错误");
            error.printStackTrace();
        }
    
        /**
         * 实现服务器主动推送
         */
        public void sendMessage(String message) throws IOException {
            this.session.getBasicRemote().sendText(message);
        }
    
        /**
         * 群发自定义消息
         */
        public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {
            log.info("推送消息到窗口" + sid + ",推送内容:" + message);
    
            for (WebSocketServer item : webSocketSet) {
                try {
                    //这里可以设定只推送给这个sid的,为null则全部推送
                    if (sid == null) {
    //                    item.sendMessage(message);
                    } else if (item.sid.equals(sid)) {
                        item.sendMessage(message);
                    }
                } catch (IOException e) {
                    continue;
                }
            }
        }
    
        public static synchronized int getOnlineCount() {
            return onlineCount;
        }
    
        public static synchronized void addOnlineCount() {
            WebSocketServer.onlineCount++;
        }
    
        public static synchronized void subOnlineCount() {
            WebSocketServer.onlineCount--;
        }
    
        public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
            return webSocketSet;
        }
    }
    

    4.测试Controller

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.servlet.ModelAndView;
    
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Created with IntelliJ IDEA.
     *
     * @ Auther: 马超伟
     * @ Date: 2020/06/16/14:38
     * @ Description:
     */
    @Controller("web_Scoket_system")
    @RequestMapping("/api/socket")
    public class SystemController {
        //页面请求
        @GetMapping("/index/{userId}")
        public ModelAndView socket(@PathVariable String userId) {
            ModelAndView mav = new ModelAndView("/socket1");
            mav.addObject("userId", userId);
            return mav;
        }
    
        //推送数据接口
        @ResponseBody
        @RequestMapping("/socket/push/{cid}")
        public Map pushToWeb(@PathVariable String cid, String message) {
            Map<String,Object> result = new HashMap<>();
            try {
                WebSocketServer.sendInfo(message, cid);
                result.put("code", cid);
                result.put("msg", message);
            } catch (IOException e) {
                e.printStackTrace();
            }
            return result;
        }
    }
    

    5.测试页面index.html

    <!DOCTYPE html>
    <html>
    
    	<head>
    		<meta charset="utf-8">
    		<title>Java后端WebSocket的Tomcat实现</title>
    		<script type="text/javascript" src="js/jquery.min.js"></script>
    	</head>
    
    	<body>
    		<div id="main" style=" 1200px;height:800px;"></div>
    		Welcome<br/><input id="text" type="text" />
    		<button onclick="send()">发送消息</button>
    		<hr/>
    		<button onclick="closeWebSocket()">关闭WebSocket连接</button>
    		<hr/>
    		<div id="message"></div>
    	</body>
    	<script type="text/javascript">
    		var websocket = null;
    		//判断当前浏览器是否支持WebSocket
    		if('WebSocket' in window) {
    			//改成你的地址
    			websocket = new WebSocket("ws://192.168.100.196:8082/api/websocket/100");
    		} else {
    			alert('当前浏览器 Not support websocket')
    		}
    
    		//连接发生错误的回调方法
    		websocket.onerror = function() {
    			setMessageInnerHTML("WebSocket连接发生错误");
    		};
    
    		//连接成功建立的回调方法
    		websocket.onopen = function() {
    			setMessageInnerHTML("WebSocket连接成功");
    		}
    		var U01data, Uidata, Usdata
    		//接收到消息的回调方法
    		websocket.onmessage = function(event) {
    			console.log(event);
    			setMessageInnerHTML(event);
    			setechart()
    		}
    
    		//连接关闭的回调方法
    		websocket.onclose = function() {
    			setMessageInnerHTML("WebSocket连接关闭");
    		}
    
    		//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    		window.onbeforeunload = function() {
    			closeWebSocket();
    		}
    
    		//将消息显示在网页上
    		function setMessageInnerHTML(innerHTML) {
    			document.getElementById('message').innerHTML += innerHTML + '<br/>';
    		}
    
    		//关闭WebSocket连接
    		function closeWebSocket() {
    			websocket.close();
    		}
    
    		//发送消息
    		function send() {
    			var message = document.getElementById('text').value;
    			websocket.send('{"msg":"' + message + '"}');
    			setMessageInnerHTML(message + "&#13;");
    		}
    	</script>
    
    </html>
    

    6.结果展示

    后台:

    如果有连接请求

    前台显示:

    总结

    这中间我遇到一个问题,就是说WebSocket启动的时候优先于spring容器,从而导致在WebSocketServer中调用业务Service会报空指针异常

    所以需要在WebSocketServer中将所需要用到的service给静态初始化一下:

    如图所示:

    还需要做如下配置:

    原文链接:https://blog.csdn.net/MacWx/article/details/111319558

    版权声明:本文为CSDN博主「大树先生.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

    近期热文推荐:

    1.600+ 道 Java面试题及答案整理(2021最新版)

    2.终于靠开源项目弄到 IntelliJ IDEA 激活码了,真香!

    3.阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!

    4.Spring Cloud 2020.0.0 正式发布,全新颠覆性版本!

    5.《Java开发手册(嵩山版)》最新发布,速速下载!

    觉得不错,别忘了随手点赞+转发哦!

  • 相关阅读:
    yzoj P2344 斯卡布罗集市 题解
    yzoj P2350 逃离洞穴 题解
    yzoj P2349 取数 题解
    JXOI 2017 颜色 题解
    NOIP 2009 最优贸易 题解
    CH 4302 Interval GCD 题解
    CH4301 Can you answer on these queries III 题解
    Luogu2533[AHOI2012]信号塔
    Luogu3320[SDOI2015]寻宝游戏
    Luogu3187[HNOI2007]最小矩形覆盖
  • 原文地址:https://www.cnblogs.com/javastack/p/14817128.html
Copyright © 2011-2022 走看看