SpringBoot +WebSocket应用

news2024/12/22 18:24:48

我们今天不研究原理,只看应用。

什么是WebSocket

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

以上是百度百科的WebSocket定义。其实我们就可以理解WebSocket是一种全双工通讯协议,也就是服务端和客户端都可以发送、接收信息,对比Http协议想一下就很容易理解:Http协议是无状态的单向通讯协议,只能是客户端发送请求给服务端、服务端返回响应给客户端,之后连接就关闭了,服务端是不可以主动发送消息给客户端的。

应用场景

为什么需要WebSocket?

WebSocket是为了解决Web应用场景下特定需求的,Web应用使用的是http协议,服务端是没有办法主动发送消息给客户端的,但是某些场景下恰恰需要服务端主动发送消息给客户端,比如实时数据监控的场景:服务端定时(比如每分钟)获取到被监控设备的实时数据,前端页面实时展示监控值,当服务器端获取到新的数据的时候,要求前端页面相应的进行实时展示。

如果不采用WebSocket,一般会采用如下两种方式实现以上需求:

  1. 轮询:前端js定时轮询,发起查询请求,如果有新数据的话当然就可以获取到,但是即使没有新数据,前端也不会知道,只能机械式的不断轮询。
  2. 长轮询(long Polling):阻塞式轮询,拿不到数据就不返回一直等待,其实是一种更傻的轮询方式。

轮询方式是基于Http协议实现的,主要缺点是开销大,因为每次轮询都需要发起http请求。

而WebSocket是该应用场景下以上两种轮询方式的理想替代方案,WebSocket仅在首次与服务器通讯的时候采用http协议,建立链接之后立即升级协议为WebSocket协议,建立长连接,之后的通讯都会工作在这个长连接下,所以,可以极大减少及时通讯业务场景下的连接开销。

SpringBoot集成WebSocket

SpringBoot对WebSocket做了无缝支持,使用起来也非常方便。

第一步:引入依赖。

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

第二步:使用ServerEndpoint注解创建WebSocket服务端:

package com.example.service;

import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint(value = "/websocket")
@Component
public class MyWebSocket {

    //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;

    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<MyWebSocket>();

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

    //记录客户端特性:比如页面查询参数等等
    private String params;
    private HashMap<String,String> frontEndData = new HashMap<>();

    /**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在线数加1
        System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
        try {
            sendMessage(session.getId()+ ":connected...");
        } catch (IOException e) {
            System.out.println("IO异常");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);  //从set中删除
        subOnlineCount();           //在线数减1
        System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息*/
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("来自客户端的消息:" + message);
        this.params = message;
        try{
            sendMessage(message);
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 发生错误时调用
     **/
     @OnError
     public void onError(Session session, Throwable error) {
         System.out.println("发生错误");
         error.printStackTrace();
     }


     public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
         //this.session.getAsyncRemote().sendText(message);
         //群发消息
     }

    //给所有客户端发送消息
     public void sendMessageToAll(String message,MonitorService monitorService){
         for (MyWebSocket item : webSocketSet) {
             try {
                 if(monitorService.matchMessage(item.params,message))
                    item.sendMessage(message);
             } catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }

     /**
      * 群发自定义消息
      * */
    public static void sendInfo(String message) throws IOException {
        for (MyWebSocket item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                continue;
            }
        }
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        MyWebSocket.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        MyWebSocket.onlineCount--;
    }
}

第三部:创建一个MonitorService类,通过前台页面模拟设备数据提交给MonitorService,MonitorService接收到数据之后调用MyWebSocket 的sendMessageToAll方法,将设备监控数据发送给前端进行展示:

package com.example.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MonitorService {
    @Autowired
    private MyWebSocket myWebSocket;
    public void getMonitorValue(String value){
        sendValue(value);
    }

    private void sendValue(String value) {
        try{
            myWebSocket.sendMessageToAll(value,this);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public boolean matchMessage(String params,String message){
        //信息匹配
        if(message.startsWith(params)){
            return true;
        }
        return false;
    }
}

matchMessage方法做一个简单的业务场景的模拟:前端页面通过WebSocket发送一个订阅信息上来,表示当前页面订阅某一种类型的监测数据,matchMessage方法暂定一个特别简单的逻辑:以订阅信息打头的监测数据仅发送给该订阅客户端。比如客户端订阅“TTT”信息,则监测数据为TTTabce123将发送给该客户端,其他非TTT打头的监测数据将不会发送给该客户端。

第四步:创建Controller,调用MonitorService的getMonitorValue方法,模拟设备监控数据的获取、并发送给订阅该数据的客户端。

package com.example.controller;

import com.example.service.MonitorService;
import com.example.service.TestProperties;
import com.example.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/hello")
@Slf4j
public class HelloWorldController {

    @Autowired
    private MonitorService monitorService;
 
    @GetMapping ("/sendmessage/{msg}")
    public String sendMessage(@PathVariable String msg){
        monitorService.getMonitorValue(msg);
        return "hello";
    }


第五步:客户端代码,写入websocket.htm文件中:

<!DOCTYPE HTML>
<html>
<head>
    <title>My WebSocket</title>
</head>

<body>
Welcome<br/>
<input id="text" type="text" /><button onclick="send()">Send</button>    <button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>

<script type="text/javascript">
    var websocket = null;

    //判断当前浏览器是否支持WebSocket
    if('WebSocket' in window){
        websocket = new WebSocket("ws://localhost:8003/websocket");
    }
    else{
        alert('Not support websocket')
    }

    //连接发生错误的回调方法
    websocket.onerror = function(){
        setMessageInnerHTML("error");
    };

    //连接成功建立的回调方法
    websocket.onopen = function(event){
        setMessageInnerHTML("open");
    }

    //接收到消息的回调方法
    websocket.onmessage = function(event){
        setMessageInnerHTML(event.data);
    }

    //连接关闭的回调方法
    websocket.onclose = function(){
        setMessageInnerHTML("close");
    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function(){
        websocket.close();
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML){
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    //关闭连接
    function closeWebSocket(){
        websocket.close();
    }

    //发送消息
    function send(){
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>

运行测试

启动SpringBoot项目,使用浏览器打开websocket.htm,文本框中输入ttt,表示订阅以ttt开头的实时监测数据,点击send按钮,页面回显ttt,表示客户端与服务端通过WebSocket成功发送、接收了信息:
在这里插入图片描述
浏览器再打开两个websocket.htm页面,分别发送ccc,AAA:
在这里插入图片描述
在这里插入图片描述
浏览器访问我们编写的模拟发送监测数据的服务,发送ttt12323,页面收到返回信息hello,表示发送成功:
在这里插入图片描述
查看已经打开的websocket.htm页面,发现订阅ttt服务的页面收到了服务器端通过websocket协议推送的信息:
在这里插入图片描述
另外两个页面没有收到信息,按照我们的设计,另外两个页面分别定制的是ccc以及AAA信息所以不会收到ttt信息。因此我们分别再次发送ccc及AAA信息:
在这里插入图片描述
在这里插入图片描述
检查websocket.htm页面:
在这里插入图片描述
在这里插入图片描述
ccc以及AAA客户端分别接收到了对应的信息,测试成功!

总结

在SpringBoot项目中使用WebSocket其实非常简单:

  1. 引入依赖。
  2. 通过@ServerEndpoint注解编写WebScoket服务端。
  3. 前端页面通过js代码编写WebSocket客户端。

以上三步,一个简单的WebSocket应用就可以正常工作了。

OK,Many thanks!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1508512.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序开发系列(二十)·wxml语法·setData()修改对象类型数据、ES6 提供的展开运算符、delete和rest的用法

目录 1. 新增单个、多个属性 1.1 新增单个属性 1.2 新增多个属性 2. 修改单个、多个属性 2.1 修改单个属性 2.2 修改多个属性 3. 优化 3.1 ES6 提供的展开运算符 3.2 Object.assign()将多个对象合并为一个对象 4. 删除单个、多个属性 4.1 删除单个属性 …

Spring揭秘:Environment接口应用场景及实现原理!

内容概要 Environment接口提供了强大且灵活的环境属性管理能力&#xff0c;通过它&#xff0c;开发者能轻松地访问和配置应用程序运行时的各种属性&#xff0c;如系统属性、环境变量等。 同时&#xff0c;Environment接口还支持属性源的定制和扩展&#xff0c;使得开发者能根…

20240309web前端_第一周作业_完成电子汇款单

作业二&#xff1a;完成电子汇款单 成果展示: 完整代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"…

算法-状数组与线段树-1264. 动态求连续区间和

题目 思路 线段树&树状数组 - AcWing算法学习笔记(14): 线段树 - 知乎 (zhihu.com) 代码 Python超时版 def calculate_subarray_sum(nums, a, b):return sum(nums[a-1:b])n, m map(int, input().split()) nums list(map(int, input().split()))for _ in range(m):op,…

社交媒体革新者:揭秘Facebook对在线互动的影响

1. Facebook的兴起与发展 Facebook由马克扎克伯格在哈佛大学宿舍创建&#xff0c;最初只是服务于哈佛大学学生的社交网络。然而&#xff0c;其后快速扩张到其他大学和全球&#xff0c;成为了全球最大的社交媒体平台之一。其发展历程不仅是数字时代的典范&#xff0c;也是创业成…

地球的纬度和中国在地球上大概位置

每次都忘记,做一个比较画一张图下次看见一下就能想起 (中国在北纬和南纬) N 表示北纬&#xff0c;范围是 0 到 90。北纬是正数 S 表示南纬&#xff0c;范围也是 0 到 -90&#xff0c;但南纬是负数 (中国在东经) E 表示东经&#xff0c;范围是 0 到 180 东经是正数 W 表示西经&a…

Nginx七层的负载均衡使用keepalived实现高可用

目录 一、环境准备 二、两台nginx服务器作为代理服务器,配置nginx的负载均衡 三、Keepalived实现调度器 Proxy-master 与 Proxy-slave机器同时操作安装 备份配置文件 编辑主Proxy-master的配置文件 编辑从 Proxy-slaver的配置文件 四、 启动KeepAlived&#xff08;主备…

如何在Linux使用Docker部署Firefox并实现无公网IP访问本地浏览器

文章目录 1. 部署Firefox2. 本地访问Firefox3. Linux安装Cpolar4. 配置Firefox公网地址5. 远程访问Firefox6. 固定Firefox公网地址7. 固定地址访问Firefox Firefox是一款免费开源的网页浏览器&#xff0c;由Mozilla基金会开发和维护。它是第一个成功挑战微软Internet Explorer浏…

Unity笔记:C#基础(1)

杂项 虚函数 CSDN - C虚函数详解 cnblog - C#中的虚函数virtual 常量池与new 在C#中&#xff0c;string是不可变的&#xff0c;这意味着对string对象的操作通常会返回一个新的string对象&#xff0c;而不会修改原始的string对象。因此&#xff0c;几乎所有涉及更改string内…

安装zabbix

部署Zabbix监控平台 部署一台Zabbix监控服务器&#xff0c;一台被监控主机&#xff0c;为进一步执行具体的监控任务做准备&#xff1a; 安装LNMP环境源码安装Zabbix安装监控端主机&#xff0c;修改基本配置初始化Zabbix监控Web页面修改PHP配置文件&#xff0c;满足Zabbix需求…

IP形象设计是什么设计?如何做?

随着市场竞争的激烈&#xff0c;越来越多的企业开始关注品牌形象的塑造和推广。在品牌形象中&#xff0c;知识产权形象设计是一个非常重要的方面。在智能和互联网的趋势下&#xff0c;未来的知识产权形象设计可能更加关注数字和社交网络。通过数字技术和社交媒体平台&#xff0…

vue本地实现生成二维码功能

首先导入依赖 npm install qrcode --save-dev然后是页面代码 <template><div><canvas ref"qrcodeCanvas"></canvas></div> </template><script> import QRCode from qrcode;export default {mounted() {this.generateQR…

(2023)从零开始用qemu搭建虚拟arm环境

用qemu搭建虚拟arm环境 引言安装版本 1. VMware ubuntu20.04 qemu安装2.安装交叉编译工具3.编译内核kernel4.u-boot编译5.制作根文件系统第一步&#xff1a;下载、编译和安装busybox第二步&#xff1a;形成根目录结构第三步&#xff1a;制作根文件系统镜像 测试HelloWorld应用…

Servlet API 详细讲解

Servlet API 详细讲解 文章目录 Servlet API 详细讲解1. HttpServlet2.HttpServletRequest服务器如何获取到 query string 和 body 的数据 &#xff1f;&#xff1f; 3.HttpServletResponse API就是一组类和方法的集合&#xff0c;servlet 中的 类是非常多的&#xff0c;咱们只…

Python实现图片(合并)转PDF

在日常的工作和学习过程当中,我相信很多人遇到过这样一个很普通的需求,就是将某一个图片转为PDF或者是将多个图片合并到一个PDF文件。但是,在苦苦搜寻一圈之后发现要么要下载软件,下载了还要注册,注册了还要VIP,甚至SVIP才能实现这样的需求! 今天,我带大家把这个功能打…

【学习笔记】VMware vSphere 6.7虚拟化入门

VMware vSphere 6.7虚拟化入门课程介绍 课程内容 1、VMware vSphere 6.7虚拟化入门课程介绍 2、ESXi6.7控制台设置 3、使用vSpkere Host client管理虚拟机 4、VMware EsXi基础操作 5、VMware Esxi存储管理 6、管理ESXi主机网络与虚拟机网络 7、安装配置vCenter Server Applia…

neo4j网页无法打开,启动一会儿后自动关闭,查看neo4j status显示Neo4j is not running.

目录 前情提要User limit of inotify watches reached无法访问此网站 前情提要 公司停电&#xff0c;服务器未能幸免&#xff0c;发现无法访问此网站&#xff0c;http://0.0.0.0:7474 在此之前都还好着 User limit of inotify watches reached (base) [rootlocalhost ~]# n…

利用MyRandom函数求一组指定个数的随机返回数

《VBA信息获取与处理》教程(版权10178984)是我推出第六套教程&#xff0c;目前已经是第一版修订了。这套教程定位于最高级&#xff0c;是学完初级&#xff0c;中级后的教程。这部教程给大家讲解的内容有&#xff1a;跨应用程序信息获得、随机信息的利用、电子邮件的发送、VBA互…

搭建prometheus、grafana监控平台

程序员的公众号&#xff1a;源1024&#xff0c;获取更多资料&#xff0c;无加密无套路&#xff01; 最近整理了一波电子书籍资料&#xff0c;包含《Effective Java中文版 第2版》《深入JAVA虚拟机》&#xff0c;《重构改善既有代码设计》&#xff0c;《MySQL高性能-第3版》&…

【Qt】—— 信号与槽

目录 &#xff08;一&#xff09;信号和槽概述 1.1 信号的本质 1.2 槽的本质 &#xff08;二&#xff09;信号和槽的使用 2.1 信号和槽的连接 2.2 查看内置信号和槽 2.3 通过Qt Creator⽣成信号槽代码 &#xff08;三&#xff09;自定义信号和槽 3.1 基本语法 3.2 带参…