WebSocket理论和实战

一 WebSocket理论

1.1 什么是http请求

http链接分为短链接、长链接,短链接是每次请求都要三次握手才能发送自己的信息。即每一个request对应一个response。长链接是在一定的期限内保持链接(但是是单向的,只能从客户端向服务端发消息,然后服务端才能响应数据给客户端,服务端不可以主动给客户端发消息)。保持TCP连接不断开。客户端与服务器通信,必须要有客户端发起然后服务器返回结果。客户端是主动的,服务器是被动的。

1.2 WebSocket

WebSocket他是为了解决客户端发起多个http请求到服务器资源浏览器必须要经过长时间的轮训问题而生的,他实现了多路复用,他是全双工通信。在webSocket协议下客服端和浏览器可以同时发送信息。

建立了WebSocket之后服务器不必在浏览器发送request请求之后才能发送信息到浏览器。这时的服务器已有主动权想什么时候发就可以随时发送信息到浏览器。而且信息当中不必在带有head的部分信息了,与http的长链接通信来对比,这种方式,不仅能降低服务器的压力。而且信息当中也减少了部分多余的信息。

1.3 WebSocket与http关系

在这里插入图片描述
相同点:

  1. 都是基于tcp的,都是可靠性传输协议
  2. 都是应用层协议

不同点:

  1. WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息
  2. HTTP是单向的
  3. WebSocket是需要浏览器和服务器握手进行建立连接的
  4. 而http是浏览器发起向服务器的连接,服务器预先并不知道这个连接

联系
WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的

http存在的问题

  • http是一种无状态协议,每当一次会话完成后,服务端都不知道下一次的客户端是谁,需要每次知道对方是谁,才进行相应的响应,因此本身对于实时通讯就是一种极大的障碍
  • http协议采用一次请求,一次响应,每次请求和响应就携带有大量的header头,对于实时通讯来说,解析请求头也是需要一定的时间,因此,效率也更低下
  • 最重要的是,需要客户端主动发,服务端被动发,也就是一次请求,一次响应,不能实现主动发送

在这里插入图片描述

二 代码实战

SpringBoot集成WebSocket实战

引入相关依赖

<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>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.71</version>
</dependency>

WebSocket配置类

/**
 * @Author:sgw
 * @Date:2023/7/13
 * @Description:WebSocket配置类
 */
@Configuration
public class WebSocketConfig {
    /**
     * 	注入ServerEndpointExporter,
     * 	这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

websocket处理消息核心代码

package com.ws.websocket.demos.service;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

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

/**
 * @Author:sgw
 * @Date:2023/7/13
 * @Description: 实现websocket处理消息
 */
@Component
//userId是项目每次启动时,都要与服务端的websocket建立长连接的userid值,建立长连接后,服务端就可以随时给指定的用户推送消息了
@ServerEndpoint("/ws/asset/{userId}")
public class MyWebSocket {
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    //虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
    //  注:底下WebSocket是当前类名
    private static CopyOnWriteArraySet<MyWebSocket> webSockets = new CopyOnWriteArraySet<>();
    // 用来存:在线连接数
    private static Map<String, Session> sessionPool = new HashMap<String, Session>();
    private static Logger log = LoggerFactory.getLogger(MyWebSocket.class);

    /**
     * 链接成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId") String userId) {
        try {
            this.session = session;
            webSockets.add(this);
            sessionPool.put(userId, session);

            log.info("sessionId值:" + session.getId());
            log.info("【websocket消息】有新的连接,总数为:" + webSockets.size());
            sendMessage(session, "连接成功");
        } catch (Exception e) {
        }
    }

    /**
     * 发送消息,实践表明,每次浏览器刷新,session会发生变化。
     *
     * @param session
     * @param message
     */
    public static void sendMessage(Session session, String message) {
        try {
            session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)", message, session.getId()));
        } catch (IOException e) {
            log.error("发送消息出错:{}", e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 链接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        try {
            webSockets.remove(this);

            log.info("【websocket消息】连接断开,总数为:" + webSockets.size());
        } catch (Exception e) {
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("【websocket消息】收到客户端消息:" + message);
    }

    /**
     * 发送错误时的处理
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.info("用户错误,原因:" + error.getMessage());
        error.printStackTrace();
    }


    // 此为广播消息
    public void sendAllMessage(String message) {
        log.info("【websocket消息】广播消息:" + message);
        for (MyWebSocket webSocket : webSockets) {
            try {
                if (webSocket.session.isOpen()) {
                    webSocket.session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    // 此为单点消息
    public void sendOneMessage(String userId, String message) {
        Session session = sessionPool.get(userId);
        if (session != null && session.isOpen()) {
            try {
                log.info("【websocket消息】 单点消息:" + message);
                //session.getAsyncRemote().sendText(message);
                session.getAsyncRemote().sendText(String.format("%s (From Server,Session ID=%s)", message, session.getId()));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else {
            log.info("没找到目前已经建立连接的用户,即要推送给的用户目前没登录");
        }
    }

    // 此为单点消息(多人)
    public void sendMoreMessage(String[] userIds, String message) {
        for (String userId : userIds) {
            Session session = sessionPool.get(userId);
            if (session != null && session.isOpen()) {
                try {
                    log.info("【websocket消息】 单点消息:" + message);
                    session.getAsyncRemote().sendText(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

Controller入口类

package com.ws.websocket.demos.web;

import com.alibaba.fastjson.JSONObject;
import com.ws.websocket.demos.service.MyWebSocket;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;


/**
 * @Author:sgw
 * @Date:2023/7/13
 * @Description: websocket入口类
 */

@RequestMapping("/api/ws")
@RestController
public class BasicController {

    @Resource
    private MyWebSocket myWebSocket;

    /**
     * 发送给单个用户websocket消息
     *
     * @param userId  用户的id
     * @param message 消息体
     * @return
     */
    @RequestMapping("/wsTest")
    public String websocket(String userId, String message) {
        JSONObject obj = new JSONObject();
        obj.put("cmd", "topic");//业务类型
        obj.put("msgId", "987654321");//消息id
        obj.put("msgTxt", message);//消息内容

        //单个用户发送 (userId为用户id)
        myWebSocket.sendOneMessage(userId, obj.toJSONString());
        
        return "index.html";
    }

    /**
     * 发送给所有用户websocket消息
     *
     * @param message 消息体
     * @return
     */
    @RequestMapping("/wsTest2")
    public String websocket2(String message) {
        JSONObject obj = new JSONObject();
        obj.put("cmd", "topic");//业务类型
        obj.put("msgId", "987654321");//消息id
        obj.put("msgTxt", message);//消息内容

        //全体发送
        myWebSocket.sendAllMessage(obj.toJSONString());

        return "index.html";
    }

    /**
     * 发送给多个指定用户websocket消息
     *
     * @param message 消息体
     * @return
     */
    @RequestMapping("/wsTest3")
    public String websocket3(String message) {
        JSONObject obj = new JSONObject();
        obj.put("cmd", "topic");//业务类型
        obj.put("msgId", "987654321");//消息id
        obj.put("msgTxt", message);//消息内容
        
        //给多个用户发送 (userIds为多个用户id,逗号‘,’分隔)
        String[] userIds = {"euoiqhfljsak", "jowiqnfdnas"};
        myWebSocket.sendMoreMessage(userIds, obj.toJSONString());

        return "index.html";
    }
}

前端代码

这里的前端代码以最简单的html来进行编写

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>websocket测试</title>
    <style type="text/css">
        h3, h4 {
            text-align: center;
        }
    </style>
</head>
<body>

<h3>WebSocket测试,在<span style="color:red">控制台</span>查看测试信息输出!</h3>
<h4>
    [url=/api/ws/wsTest?message=单发消息内容&userId=none]单发消息链接[/url]
    <br>
    [url=/api/ws/wsTest1?message=群发给所有用户消息内容]群发消息链接[/url]
    <br>
    [url=/api/ws/wsTest2?message=指定多个用户发消息内容]群发消息链接[/url]
</h4>


<script type="text/javascript">
    var socket;
    if (typeof (WebSocket) == "undefined") {
        console.log("遗憾:您的浏览器不支持WebSocket");
    } else {
        console.log("恭喜:您的浏览器支持WebSocket");

        //实际业务中,这里需要从系统里获取当前当前登录的用户id
        var userId = "54321";

        /*实现化WebSocket对象
        指定要连接的服务器地址与端口建立连接
        注意ws、wss使用不同的端口。我使用自签名的证书测试,
        无法使用wss,浏览器打开WebSocket时报错
        ws对应http、wss对应https。*/

        socket = new WebSocket("ws://localhost:8080/ws/asset/" + userId);
        //连接打开事件
        socket.onopen = function () {
            console.log("Socket 已打开");
            socket.send("消息发送测试");
        };
        //收到消息事件
        socket.onmessage = function (msg) {
            console.log(msg.data);
        };
        //连接关闭事件
        socket.onclose = function () {
            console.log("Socket已关闭");
        };
        //发生了错误事件
        socket.onerror = function () {
            alert("Socket发生了错误");
        }

        //窗口关闭时,关闭连接
        window.unload = function () {
            socket.close();
        };
    }
</script>

</body>
</html>

这里模拟登录用户的id是54321(实际业务中,这个id是要从系统里进行获取的),项目启动时,就会加载这段代码,把54321这个用户的前端与后端的websocket服务端进行长连接
在这里插入图片描述

三 测试

访问前端首页:http://localhost:8080/,打开f12控制台,可以看到,连接成功
在这里插入图片描述
使用postman调接口,给用户id是54321的用户发送消息,看看浏览器这里能不能接收到websocket消息
在这里插入图片描述
发现浏览器接收到消息了,如下
在这里插入图片描述
如果使用postman,发给其他用户(还没登录的用户),浏览器接收不到消息
在这里插入图片描述

浏览器没有新消息:
在这里插入图片描述
后端日志:
在这里插入图片描述
查看websocket发送与接收的消息
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux: cannot read file data

报错&#xff1a; Could not load library libcudnn_cnn_infer.so.8. Error: /home/qc/miniconda3/envs/DNAqc/lib/python3.10/site-packages/torch/lib/libcudnn_cnn_infer.so.8: cannot read file data Please make sure libcudnn_cnn_infer.so.8 is in your library path! A…

IDEA启动tomcat控制台中文乱码问题

IntelliJ IDEA是很多程序员必备且在业界被公认为最好的Java开发工具&#xff0c;有很多小伙伴在安装完IDEA并且tomcat之后&#xff0c;启动tomcat会出现控制台中文乱码问题&#xff0c;如下图所示&#xff1a; 具体解决步骤&#xff1a; 一、修改当前 Web 项目 Tomcat Server…

基于XPopup实现的弹窗效果

基于XPopup实现的弹窗效果&#xff1a; implementation com.github.li-xiaojun:XPopup:2.9.191、底部弹窗(BottomPopupView) class OutlinkDealDialog(context: Context,private val selectClickCallback: ((index: Int) -> Unit)? null, ) : BottomPopupView(context) {…

【岛屿最大面积】BJ某IT厂笔试题

该题在LeetCode上能找到原题&#xff0c;大致意思是&#xff0c;给你一个大小为 m x n 的二进制矩阵 grid 。岛屿 是由一些相邻的 1 (代表土地) 构成的组合&#xff0c;这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0&…

浅谈一下企业IT运维痛点以及好用的运维软件推荐

随着IT建设的不断深入和完善&#xff0c;IT资产越来越多&#xff0c;IT运维管理越发显得重要。但不少企业不知道如何有效进行IT运维&#xff0c;不知道如何更好进行IT运维&#xff0c;今天我们就来一起浅谈一下企业IT运维痛点&#xff0c;以及给大家推荐一款好用的运维管理软件…

visual studio配置调用c++ dll opencv为例

1&#xff0c;配置VC目录&#xff0c;包含目录和库目录。 2&#xff0c;链接器->输入->包含目录 3&#xff0c;生成目录下包含对应的dll文件 4&#xff0c;需注意对应的Debug&#xff0c;Release及X86&#xff0c;X64选项

IP库新增多种颜色转换空间IP

颜色空间转换是图像及视频中常用的解决方案&#xff0c;涉及hsv-rgb、rgb-ycrcb等一些常见的颜色空间互相转换&#xff0c;今天带来几种常见的颜色空间转换IP&#xff0c;主要如下&#xff1a; IP库简介 一直想做一个可以供大家学习、使用的开源IP库&#xff0c;类似OpenCores&…

利用鸿鹄优化共享储能的SCADA 系统功能,赋能用户数据自助分析

摘要 本文主要介绍了共享储能的 SCADA 系统大数据架构&#xff0c;以及如何利用鸿鹄来更好的优化 SCADA 系统功能&#xff0c;如何为用户进行数据自助分析赋能。 1、共享储能介绍 说到共享储能&#xff0c;可能不少朋友比较陌生&#xff0c;下面我们简单介绍一下共享储能的价值…

行为型模式 - 模板方法模式

概述 在面向对象程序设计过程中&#xff0c;程序员常常会遇到这种情况&#xff1a;设计一个系统时知道了算法所需的关键步骤&#xff0c;而且确定了这些步骤的执行顺序&#xff0c;但某些步骤的具体实现还未知&#xff0c;或者说某些步骤的实现与具体的环境相关。 例如&#…

Docker 基本管理与应用

目录 一、Docker介绍 1、docker概述 2、容器优势 3、Docker与虚拟机的区别 4、Docker核心概念 二&#xff1a; 安装 Docker 1、环境准备、安装依赖包 2、 设置阿里云镜像源&#xff0c;安装 Docker-CE并设置为开机自动启动 3、信息查看 ​三&#xff1a;Docker 镜像操作…

XSS简介

OWASP TOP 10 OWASP(开放式Web应用程序安全项目)的工具&#xff0c;文档&#xff0c;论坛额全球各地分会都是开放的&#xff0c;对所有致力于改进应用程序安全的人士开放&#xff0c;最具权威的就是10项目最严重的Web应用程序安全风险列表 Cross Site Scripting&#xff08;X…

使用Vue的插件clipboard使用复制功能

1.安装clipboard插件 npm install clipboard 2.使用 clipboard <template><div style"margin-right: auto;margin-left: auto; 800px"><el-table :data"list"><el-table-column label"搜索引擎" prop"name"&g…

cesium实战(1)、cesium 加载本地json、GeoJson数据

1、cesium加载本地图层json图层数据 并设置样式 添加图层 // 加载路网数据 wms数据服务let addRoadLayer () > {Cesium.GeoJsonDataSource.load(/cesium/layers/road_84.json, {stroke: Cesium.Color.YELLOW,//多边形或线的颜色 strokeWidth: 3,//多边形或线 宽度clampToG…

【优选算法题练习】day6

文章目录 一、76. 最小覆盖子串1.题目简介2.解题思路3.代码4.运行结果 二、704. 二分查找1.题目简介2.解题思路3.代码4.运行结果 三、34. 在排序数组中查找元素的第一个和最后一个位置1.题目简介2.解题思路3.代码4.运行结果 总结 一、76. 最小覆盖子串 1.题目简介 76. 最小覆…

Selenium(3 + 4 超级详细笔记)

文章目录 selenium&#xff08;web自动化测试&#xff09;1. selenium初始化&#xff08;2种&#xff09;2. chrome 启动参数&#xff08;3种&#xff09;3. 八大定位方式3.1 css 定位3.2 xpath 定位3.3 link_text 定位3.4 partial_link_text 定位3.5 relative 相对定位 4. 添加…

OpenCv之图像轮廓

目录 一、图像轮廓定义 二、绘制轮廓 三、计算轮廓面积与周长 一、图像轮廓定义 图像轮廓是具有相同颜色或灰度的连续带你的曲线.轮廓在形状分析和物体的检测和识别中很有用 轮廓的作用: 用于图形分析物体的识别与检测 注意点: 为了检测的准确性&#xff0c;需要先对图像…

采集传感器的物联网网关怎么采集数据?

随着工业4.0和智能制造的快速发展&#xff0c;物联网&#xff08;IoT&#xff09;技术的应用越来越广泛&#xff0c;传感器在整个物联网系统中使用非常普遍&#xff0c;如温度传感器、湿度传感器、光照传感器等&#xff0c;对于大部分物联网应用来说&#xff0c;采集传感器都非…

JVM系统优化实践(20):GC生产环境案例(三)

您好&#xff0c;这里是「码农镖局」CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e; 某新手开发工程师接到了一个保存Elasticsearch日志的任务&#xff0c;以供后续分析之用。但写代码的时候&#xff0c;误将保存日志的代码段弄成了无限循环&#xff0c;程序…

C#盯盘小工具,“监”

也是一个小工具&#xff0c;用来看大A股票和主要指数行情的。 如果你是一个上班族&#xff0c;同时你也是一颗小韭菜&#xff0c;a股在开市交易盘中时刻惦记着股票是涨了还是跌了&#xff0c;却不能时刻盯着手机看行情&#xff0c;也不能在电脑上开着同花顺来回切窗口&#xff…

数据可视化自助式分析工具:jvs-bi数据扩展及函数配置说明

jvs-bi数据拓展节点 数据拓展是数据可视化加工过程中的重要工具&#xff0c;它核心的作用是对原有数据表进行加工扩展&#xff0c;实现功能如下图所示 函数配置操作过程 操作说明 1、拖动数据拓展字段&#xff0c;并将字段拓展与之前的历史节点连接起来&#xff0c;点击数据拓…