前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测)

这篇具有很好参考价值的文章主要介绍了前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

写这个项目主要是有有个项目需要后端有数据实话返回前端,一开始采用前端轮询的方式,后面觉得及时性上有些不行,然后改为使用websocket ,具体实现demo以及测试流程发出来提供交流学习,

一、Web Socket 简绍

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

1 为什么用 websocket?

换句话说,websocket 解决了什么问题?答案是,解决了两个主要问题:

  • 只能客户端发送请求
  • 一段时间内的频繁信息发送

假设现在需要设计一个实时预警系统的通知模块,那么作为工程师我们应该怎么设计通知的这个功能呢?因为这些系统的数据来源,一般他通过硬件设备采集到后台的,如果我们现在只有 http 协议,那么我们只能让客户端不断地轮询服务器,轮询的时间间隔越小越能接近实时的效果。可是,轮询的效率低,又浪费资源。针对这样的场景,websocket 应运而生。

前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议
特点:
(1)建立在 TCP 协议之上,服务器端的实现比较容易,是一个可靠的传输协议。
(2)与 HTTP协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

二、代码实现

1、前端(html)

1.1、无前端向后端发送消息

前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议

uid实际开发中应该使用唯一值作为当前对话的key

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
消息展示区:<br/>
<div id="textArea"></div>
 
</body>
<script>
    var textArea = document.getElementById('textArea');
 
 
    var websocket = null;
    //如果浏览器支持websocket就建立一个websocket,否则提示浏览器不支持websocket 
                //uid应该要用唯一标识,为了测试方便看
    if('websocket' in window){
        websocketPage = new WebSocket('ws://localhost:8080/websocket/' + 99);
    }else{
        alert('浏览器不支持websocket!');
    }
    //建立websocket时自动调用
    websocketPage.onopen = function (event) {
        console.log('建立连接');
    }
    //关闭webscoket时自动调用
    websocketPage.oncolse = function (event){
        console.log('关闭连接');
    }
    //websocket接收到消息时调用
    websocketPage.onmessage = function (event){
        //将接收到的消息展示在消息展示区  (心跳响应回来的消息不显示)
        if (event.data !== "conn_success"){
            textArea.innerText += event.data;
            textArea.innerHTML += "<br/>";
        }
    }
    //websocket出错自动调用
    websocketPage.onerror = function () {
        alert('websocket出错');
    }
    //关闭窗口前关闭websocket连接
    window.onbeforeunload = function (){
        websocketPage.close();
    }
 
</script>
</html>

1.2、有前端向后端发送消息

前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议

<!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>
		
		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://localhost:8080/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);
			if (event.data !== "conn_success"){
				setMessageInnerHTML("接收消息:"+event.data);
				// 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 + "");
		}
	</script>
</html>

2、后端具体代码(spring boot)

2.1、maven依赖

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

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

– yml没有东西,只有一个默认端口

2.2、配置类

需要加一个 WebSocket 端点暴露 的bean 和定时器注解

@EnableScheduling  //定时器
@SpringBootApplication
public class WebSocketApplication {

    public static void main(String[] args) {
        SpringApplication.run(WebSocketApplication.class, args);
    }



    /** 
     * 服务器端点导出 
     * @author zhengfuping
     * @date 2023/8/22 
     * @return ServerEndpointExporter 
     */
    @Bean
    public ServerEndpointExporter getServerEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

2.3、Web Socket连接工具类

@Slf4j
@Service
@ServerEndpoint("/websocket/{uid}")
public class WebSocketServer2 {

    //连接建立时长
    private static final long sessionTimeout = 60000;

    // 用来存放每个客户端对应的WebSocketServer对象
    private static Map<String, WebSocketServer2> webSocketMap = new ConcurrentHashMap<>();

    // 与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    // 接收id
    private String uid;

    /**
     * 连接建立成功调用的方法
     * @author zhengfuping
     * @date 2023/8/22
     * @param session
     * @param uid
     */
    @OnOpen
    public void onOpen(Session session , @PathParam("uid") String uid){
        session.setMaxIdleTimeout(sessionTimeout);
        this.session = session;
        this.uid = uid;
        if (webSocketMap.containsKey(uid)){
            webSocketMap.remove(uid);
        }
        webSocketMap.put(uid,this);
        log.info("websocket连接成功编号uid: " + uid + ",当前在线数: " + getOnlineClients());

        try{
        // 响应客户端实际业务数据!
            sendMessage("conn_success");
        }catch (Exception e){
            log.error("websocket发送连接成功错误编号uid: " + uid + ",网络异常!!!");
        }
    }

    /**
     * 连接关闭调用的方法
     * @author zhengfuping
     * @date 2023/8/22
     */
    @OnClose
    public void onClose(){
        try {
            if (webSocketMap.containsKey(uid)){
                webSocketMap.remove(uid);
            }
            log.info("websocket退出编号uid: " + uid + ",当前在线数为: " + getOnlineClients());
        } catch (Exception e) {
            log.error("websocket编号uid连接关闭错误: " + uid + ",原因: " + e.getMessage());
        }
    }


    /**
     * 收到客户端消息后调用的方法
     * @param message 客户端发送过来的消息
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        try {
            WebSocketServer2.sendInfo(message);
            log.info("websocket收到客户端编号uid消息: " + uid + ", 报文: " + message);
        } catch (Exception e) {
            log.error("websocket发送消息失败编号uid为: " + uid + ",报文: " + message);
        }

    }

    /**
     * 发生错误时调用
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("websocket编号uid错误: " + this.uid + "原因: " + error.getMessage());
        error.printStackTrace();
    }

    /**
     * 实现服务器主动推送
     * @author yingfeng
     * @date 2023/8/22 10:11
     * @Param * @param null
     * @return
     */

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }

    /**
     * 获取客户端在线数
     * @author zhengfuping
     * @date 2023/8/22 10:11
     * @param
     */
    public static synchronized int getOnlineClients() {
        if (Objects.isNull(webSocketMap)) {
            return 0;
        } else {
            return webSocketMap.size();
        }
    }




    /**
     * 单机使用,外部接口通过指定的客户id向该客户推送消息
     * @param key
     * @param message
     * @return boolean
     */
    public static boolean sendMessageByWayBillId(@NotNull String key, String message) {
        WebSocketServer2 webSocketServer = webSocketMap.get(key);
        if (Objects.nonNull(webSocketServer)) {
            try {
                webSocketServer.sendMessage(message);
                log.info("websocket发送消息编号uid为: " + key + "发送消息: " + message);
                return true;
            } catch (Exception e) {
                log.error("websocket发送消息失败编号uid为: " + key + "消息: " + message);
                return false;
            }
        } else {
            log.error("websocket未连接编号uid号为: " + key + "消息: " + message);
            return false;
        }
    }

    /**
     * 群发自定义消息
     * @author zhengfuping
     * @date 2023/8/22 9:52
     * @param message
     */
    public static void sendInfo(String message) {
        webSocketMap.forEach((k, v) -> {
            WebSocketServer2 webSocketServer = webSocketMap.get(k);
            try {
                webSocketServer.sendMessage(message);
                log.info("websocket群发消息编号uid为: " + k + ",消息: " + message);
            } catch (IOException e) {
                log.error("群发自定义消息失败: " + k + ",message: " + message);
            }
        });
    }
    /**
     * 服务端群发消息-心跳包
     * @author zhengfuping
     * @date 2023/8/22 10:09
     * @param message 推送数据
     * @return int 连接数
     */
    public static synchronized int sendPing(String message){
        if (webSocketMap.size() == 0)
            return 0;
        StringBuffer uids = new StringBuffer();
        AtomicInteger count = new AtomicInteger();
        webSocketMap.forEach((uid,server)->{
            count.getAndIncrement();

            if (webSocketMap.containsKey(uid)){
                WebSocketServer2 webSocketServer = webSocketMap.get(uid);
                try {
                    if (Integer.valueOf(uid) ==101){
                        Integer i=1/0;
                    }

                    webSocketServer.sendMessage(message);
                    if (count.equals(webSocketMap.size() - 1)){
                        uids.append("uid");
                        return;

                    }
                    uids.append(uid).append(",");
                } catch (Exception e) {
                    webSocketMap.remove(uid);
                    log.info("客户端心跳检测异常移除: " + uid + ",心跳发送失败,已移除!");

                }
            }else {
                log.info("客户端心跳检测异常不存在: " + uid + ",不存在!");

            }
        });
        log.info("客户端心跳检测结果: " + uids + "连接正在运行");
        return webSocketMap.size();
    }
    /**
     * 连接是否存在
     * @param uid
     * @return boolean
     */
    public static boolean isConnected(String uid) {
        if (Objects.nonNull(webSocketMap) && webSocketMap.containsKey(uid)) {
            return true;
        } else {
            return false;
        }
    }
}

2.4、Controller用于测试主动发送消息

@RestController
@RequestMapping("/test")
public class WebSocketController{
    /**
     * 检验连接
     * @date 2023/8/22
     * @Param * @param webSocketId
     * @return * @return String
     */
    @GetMapping("/webSocketIsConnect/{webSocketId}")
    public String webSocketIsConnect(@PathVariable("webSocketId") String webSocketId){
        if (WebSocketServer2.isConnected(webSocketId)) {
            return webSocketId+"正在连接";
        }
        return webSocketId+"连接断开!";
    }

    /**
     * 单发 消息
     * @author zhengfuping
     * @date 2023/8/22 10:25
     * @param webSocketId  指定 连接
     * @param message  数据
     * @param pwd 验证密码
     * @return String
     */
    @GetMapping("/sendMessageByWayBillId")
    public String sendMessageByWayBillId(String webSocketId, String message, String pwd) {
        boolean flag = false;

            flag = WebSocketServer2.sendMessageByWayBillId(webSocketId, message);

        if (flag) {
            return "发送成功!";
        }
        return "发送失败!";
    }

    /**
     * 群发
     * @author zhengfuping
     * @date 2023/8/22 10:26
     * @param message
     * @param pwd
     */
    @GetMapping("/broadSendInfo")
    public void sendInfo(String message, String pwd) {
            WebSocketServer2.sendInfo(message);
    }
}

2.5、配置定时任务,用于调用主动向客户端发送心跳

每10秒调用一次,主动检测,查看客户端连接是否异常断开,如果异常断开,则把该会话从集合中剔除掉,避免无限积压。

@Component
@Slf4j
public class WebSocketTask {
    
    @Scheduled(cron = "0/10 * * * * ?")
    public void clearOrders(){
        int num = 0;
        try {
            num = WebSocketServer2.sendPing("conn_success");
        } finally {
            log.info("websocket心跳检测结果,共【" + num + "】个连接");
        }
    }
}

三、测试

1、 测试消息发送

1.1、前端日志

前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议

1.2、后端日志

前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议

2、测试客户端异常断开,服务器通过心跳检测自动剔除掉异常对话。

因为测试不方便,只能通过断点实现效果

  1. 前端需要把主动关闭会话的注释掉,不让主动关闭
    前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议

  2. 先在连接关闭的地方和心跳检测地方打上断点,断点需要设置成Thread,要不然没法异步
    前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议

  3. 然后关闭掉一个前端页面让他把会话关闭,就会进入该断点位置,通过断点让它停住不让他去正常关闭

前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议4. 然后选择执行该心跳检测的断点代码
前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议
5. 进入心跳的循环给每个会话发送心跳检测,此时前端已经异常断开了
前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议
6. 因为前端已经关闭会话了,则发送心跳会失败,会直接进入catch块,然后把该会话从集合中剔除掉
前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议
最终日志
前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测),IO通讯,前端,spring boot,后端,websocket,通讯协议文章来源地址https://www.toymoban.com/news/detail-666179.html

到了这里,关于前端加springboot实现Web Socket连接通讯以及测试流程(包括后端实现心跳检测)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包