WebSocket的原理、作用、常见注解和生命周期的简单介绍,附带SpringBoot示例

文章目录

    • WebSocket是什么
    • WebSocket的原理
    • WebSocket的作用
    • 全双工和半双工
    • 客户端【浏览器】API
    • 服务端 【Java】API
    • WebSocket的生命周期
    • WebSocket的常见注解
    • SpringBoot简单代码示例

WebSocket是什么

WebSocket是一种 通信协议 ,它在 客户端和服务器之间建立了一个双向通信的网络连接 。WebSocket是一种基于TCP连接上进行 全双工通信协议

轮询和WebSocket

上图的左边图是 轮询 的方式进行获取信息,可以看出在 一定时间间隔 的情况下客户端向服务端发起请求,如果有数据则返回数据,没有数据则返回空数据。

右边图则是使用WebSocket方式,在 一次握手 之后,两者就可以 创建持久性的连接 ,如果服务端有数据的话,就可以实时返回数据。

WebSocket的原理

WebSocket的原理图
该图解释了WebSocket连接原理。下面是上图的HTTP协议中的请求和响应数据。

请求数据:

  • GET ws://localhost/chat HTTP/1.1
  • Host: localhost
  • Connection: Upgrade
  • Upgrade: websocket
  • Sec-WebSocket-Version: 13
  • Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==Sec-WebSocket-Extensions: permessage-deflate

响应数据:

  • HTTP/1.1 101 Switching Protocols
  • Upgrade: websocket
  • Connection: Upgrade
  • Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=Sec-WebSocket-Extensions: permessage-deflate

WebSocket的作用

WebSocket允许客户端和服务器在 单个TCP连接上 进行 实时 双向通信,而不是传统的 请求-响应模型 。WebSocket协议允许 任意多的数据包任意方向上 发送,因此可以实现 实时的双向的交互式的 数据传输。

  • 实时数据传输:WebSocket允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要 完成一次握手 ,两者之间就可以直接创建 持久性的连接 ,并进行 双向数据传输
  • 实时通知:WebSocket可以保持一个 长连接,当服务器端有新的消息时,能够实时地推送到客户端。这使得诸如知乎的点赞通知、评论等实时交互功能得以实现,从而提供更好的用户体验。
  • 数据收集:一些 次优级别的数据 ,如行为日志、trace、异常执行栈收集等,可以通过WebSocket通道进行传输。这增加了信息的集中度,并能及时针对用户的行为进行合适的配置推送。

WebSocket通常用于 实时Web应用程序 ,如聊天室、游戏、股票价格跟踪等,这些应用需要 实时交互数据更新

但是在实际使用中仍需注意其安全性。例如,某些工具(如Fiddler、Charles等)能够 捕获WebSocket包 ,但这并不意味着WebSocket本身不安全。在使用WebSocket时,应确保采取适当的安全措施,如加密和认证,以保护数据的传输和存储。

全双工和半双工

全双工(Full Duplex)和半双工(Half Duplex)是 通讯传输 中的两种不同模式,主要区别在于 数据在通信线路上的传输方向和方式

  • 全双工允许数据在 两个方向上同时传输 ,即通信线路可以同时存在 双向信号传输 。这意味着在发送数据的同时,也能接收数据,两者是同步进行的。这种模式类似于我们平时打电话,说话的同时也能听到对方的声音。全双工方式下,通信系统的每一端都设置了 发送器和接收器 ,因此能控制数据同时在两个方向上传送。
  • 半双工模式允许 数据在一个信号载体的两个方向上传输,但不能同时传输 。虽然数据可以沿两个方向传送,但在同一时刻,一个信道只允许单方向传送。若要改变传输方向,需由开关进行切换。半双工通信也被称为 双向交替通信 ,即发送和接收动作是交替进行的,不能同时进行。例如,一条窄窄的马路,同时只能有一辆车通过,当有两辆车对开时,只能一辆先过,另一辆再开。

全双工和半双工的主要区别在于数据传输的同步性和方向性。全双工能实现数据的瞬时同步双向传输,而半双工则只能实现双向交替传输。

客户端【浏览器】API

let  ws  =  new WebSocket(URL);  // WebSocket对象创建
  • 格式:协议://ip地址/访问路径
  • 协议:协议名称为 ws

WebSocket对象相关事件:

事件事件处理程序描述
openws.onopen连接建立时触发
messagews.onmessage客户端接收到服务器发送的数据时触发
closews.onclose连接关闭时触发

WebSocket对象提供的方法:

方法名称描述
send()通过websocket对象调用该方法发送数据给服务端

前端客户端使用方式:
websocket

服务端 【Java】API

Tomcat的7.0.5 版本开始支持WebSocket,并且实现了Java WebSocket规范。Java WebSocket 应用由一系列的Endpoint 组成。Endpoint 是一个java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体WebSocket 消息的接口。

我们可以通过两种方式定义 Endpoint

  • 第一种是编程式,即继承类 javax.websocket.Endpoint 并实现其方法。
  • 第二种是注解式,即定义一个POJO,并添加 @ServerEndpoint 相关注解。

Endpoint实例 在 WebSocket 握手 时创建,并在客户端与服务端链接过程中有效,最后在链接关闭时结束。在 Endpoint接口 中明确定义了与其生命周期相关的方法, 规范实现者确保生命周期的各个阶段调用实例的相关方法。

生命周期方法如下:

方法描述注解
onOpen()当开启一个新的会话时调用,该方法是客户端与服务端握手成功后调用的方法@OnOpen
onClose()当会话关闭时调用@OnClose
onError()当连接过程异常时调用@OnError

服务端如何接收客户端发送的数据呢?

  • 编程式:通过添加 MessageHandler 消息处理器来接收消息
  • 注解式:在定义 Endpoint 时,通过 @OnMessage 注解指定接收消息的方法

服务端如何推送数据给客户端呢?

发送消息则由 RemoteEndpoint 完成, 其实例由 Session 维护。发送消息有2种方式发送消息:

  • 通过 session.getBasicRemote 获取同步消息发送的实例 , 然后调用其 sendXxx() 方法发送消息。
  • 通过 session.getAsyncRemote 获取异步消息发送实例,然后调用其 sendXxx() 方法发送消息。

服务端使用方式:
在这里插入图片描述

WebSocket的生命周期

该图中右上角应为@OnClose。
在这里插入图片描述

WebSocket的常见注解

JavaWebSocket API 中,常见的注解包括以下几种:

  • @ServerEndpoint :该类级别的注解,用于标记一个类并将其声明为WebSocket服务端点。这意味着此类会被部署为WebSocket服务器并在WebSocket实现的URI空间中可用。
  • @OnOpen :用于标识一个方法,当WebSocket连接建立时被调用。这个方法可以包含连接建立时需要执行的逻辑。
  • @OnClose :用于标识一个方法,当WebSocket连接关闭时被调用。此方法中可以执行与关闭连接相关的操作或资源释放。
  • @OnMessage :用于标识一个方法,在接收到客户端消息时被调用。此方法可以接收不同类型的参数,如String或ByteBuffer,用于处理从客户端接收到的消息。
  • @OnError :用于标识一个方法,在WebSocket通信过程中发生错误时被调用。此方法可以用于处理错误情况,如连接失败、消息解析错误等。

此外,还有一些其他的注解可以用于指定WebSocket的配置,例如:

  • encoders :编码器属性,用于指定将消息从Java对象转换为WebSocket消息格式的编码器。
  • decoders :解码器属性,用于指定将WebSocket消息格式解码为Java对象的解码器。
  • subprotocols :子协议属性,用于指定WebSocket连接支持的子协议。
  • configurator :配置器属性,用于指定WebSocket端点的配置器,可以对端点进行更高级的配置。

以上列出的是基于 Java WebSocket API 的常见注解。如果使用其他编程语言或框架,注解可能会有所不同。

SpringBoot简单代码示例

这是一个简单示例,主要了解在SpringBoot中怎么使用WebSocket。

首先,需要在SpringBoot项目中引入依赖

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

注册WebSocket

@Configuration
public class WebsocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

用来封装http请求的响应数据

@Data
public class Result {
    private boolean flag;
    private String message;
}

用于接收登录请求的数据

@Data
public class User {

    private String userId;
    private String username;
    private String password;
}

用于封装浏览器发送给服务端的消息数据

@Data
public class Message {
    private String toName;
    private String message;
}

用来封装服务端给浏览器发送的消息数据

@Data
public class ResultMessage {
    private boolean isSystem;
    private String fromName;
    private Object message; //如果是系统消息是数组
}

定义ChatEndpoint 类用于处理WebSocket中数据传输的逻辑,生命周期等功能。

@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfig.class)
@Component
public class ChatEndpoint {
	// 线程安全
    private static final Map<String,Session> onlineUsers = new ConcurrentHashMap<>();
    private HttpSession httpSession;

    // 建立websocket连接后,被调用
    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        //1,将session进行保存
        this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        String user = (String) this.httpSession.getAttribute("user");
        onlineUsers.put(user,session);
        //2,广播消息。需要将登陆的所有的用户推送给所有的用户
        String message = MessageUtils.getMessage(true,null,getFriends());
        broadcastAllUsers(message);
    }

    public Set getFriends() {
        Set<String> set = onlineUsers.keySet();
        return set;
    }

    private void broadcastAllUsers(String message) {
        try {
            //遍历map集合
            Set<Map.Entry<String, Session>> entries = onlineUsers.entrySet();
            for (Map.Entry<String, Session> entry : entries) {
                //获取到所有用户对应的session对象
                Session session = entry.getValue();
                //发送消息
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            //记录日志
        }
    }

    // 浏览器发送消息到服务端,该方法被调用
    @OnMessage
    public void onMessage(String message) {
        try {
            //将消息推送给指定的用户
            Message msg = JSON.parseObject(message, Message.class);
            //获取 消息接收方的用户名
            String toName = msg.getToName();
            String mess = msg.getMessage();
            //获取消息接收方用户对象的session对象
            Session session = onlineUsers.get(toName);
            String user = (String) this.httpSession.getAttribute("user");
            String msg1 = MessageUtils.getMessage(false, user, mess);
            session.getBasicRemote().sendText(msg1);
        } catch (Exception e) {
            //记录日志
        }
    }

    // 断开 websocket 连接时被调用
    @OnClose
    public void onClose(Session session) {
        // 1,从onlineUsers中剔除当前用户的session对象
        String user = (String) this.httpSession.getAttribute("user");
        onlineUsers.remove(user);
        // 2,通知其他所有的用户,当前用户下线了
        String message = MessageUtils.getMessage(true,null,getFriends());
        broadcastAllUsers(message);
    }
}

由于使用user name作为Map的key,所以要实时保存,用于消息的传递标识。如果使用数据库,建议还是使用userId作为key,保证唯一性。

public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        //获取HttpSession对象
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        //将httpSession对象保存起来
        sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
    }
}

controller请求类

@RestController
@RequestMapping("user")
public class UserController {

    /**
     * 登陆
     * @param user 提交的用户数据,包含用户名和密码
     * @param session
     * @return
     */
    @PostMapping("/login")
    public Result login(@RequestBody User user, HttpSession session) {
        Result result = new Result();
        if(user != null && "123".equals(user.getPassword())) {
            result.setFlag(true);
            //将数据存储到session对象中
            session.setAttribute("user",user.getUsername());
        } else {
            result.setFlag(false);
            result.setMessage("登陆失败");
        }
        return result;
    }

    /**
     * 获取用户名
     * @param session
     * @return
     */
    @GetMapping("/getUsername")
    public String getUsername(HttpSession session) {

        String username = (String) session.getAttribute("user");
        return username;
    }
}

项目目录结构:
在这里插入图片描述

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

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

相关文章

开发环境中的调试视图(IDEA)

当程序员写完一个代码时必然要运行这个代码&#xff0c;但是一个没有异常的代码却未必满足我们的要求&#xff0c;因此就要求程序员对已经写好的代码进行调试操作。在之前&#xff0c;如果我们要看某一个程序是否满足我们的需求&#xff0c;一般情况下会对程序运行的结果进行打…

java泛型介绍

Java 泛型是 JDK 5 引入的一个特性&#xff0c;它允许我们在定义类、接口和方法时使用类型参数&#xff0c;从而使代码更加灵活和类型安全。泛型的主要目的是在编译期提供类型参数&#xff0c;让程序员能够在编译期间就捕获类型错误&#xff0c;而不是在运行时才发现。这样做提…

C语言学习/复习30--结构体的声明/初始化/typedef改名/内存对齐大小计算

一、自定义数据类型 二、结构体 1.结构体的定义&#xff08;与数组相对比&#xff09; 2.结构体全局/局部变量的定义 3.typedef对结构体改名 4.匿名结构体类型的声明 注意事项1&#xff1a; 匿名后必须立即创建结构体变量 、 5.结构体与链表节点定义 注意事项1&…

Python基础07-高级列表推导式和Lambda函数

在Python中&#xff0c;列表推导式和Lambda函数是处理数据的强大工具。本文将介绍如何使用嵌套列表推导式、带有条件的列表推导式、多可迭代对象的列表推导式、Lambda函数、在列表推导式中使用Lambda函数、用于展平嵌套列表的列表推导式、对元素应用函数、使用Lambda函数与Map和…

Arena-Hard:开源高质量大模型评估基准

开发一个安全、准确的大模型评估基准通常需要包含三个重要内容&#xff1a;1&#xff09;稳定识别模型的能力&#xff1b;2&#xff09;反映真实世界使用情况中的人类偏好&#xff1b;3&#xff09;经常更新以避免过拟合或测试集泄漏。 但传统的基准测试通常是静态的或闭源的&…

程序员缓解工作压力小技巧

文章目录 1. 规划时间和任务2. 学会放松和调节情绪3. 培养兴趣爱好4. 保持健康的生活方式总结 当面对程序员这样需要高度精神集中和持续创新的工作时&#xff0c;缓解工作压力是至关重要的。下面分享一些我个人的经验和方法&#xff0c;希望能对大家有所帮助&#xff1a; 1. 规…

如何让AI生成自己喜欢的歌曲-AI音乐创作的正确方式 - 第507篇

历史文章 AI音乐&#xff0c;8大变现方式——Suno&#xff1a;音乐版的ChatGPT - 第505篇 日赚800&#xff0c;利用淘宝/闲鱼进行AI音乐售卖实操 - 第506篇 导读 在使用AI生成音乐&#xff08;AI写歌&#xff09;的时候&#xff0c;你是不是有这样的困惑&#xff1a; &…

线性模型算法

简介 本文章介绍机器学习中的线性模型有关内容&#xff0c;我将尽可能做到详细得介绍线性模型的所有相关内容 前置 什么是回归 回归的就是整合&#xff0b;预测 回归处理的问题可以预测&#xff1a; 预测房价 销售额的预测 设定贷款额度 可以根据事物的相关特征预测出对…

模型部署的艺术:让深度学习模型跃入生产现实

模型部署的艺术&#xff1a;让深度学习模型跃入生产现实 1 引言 1.1 部署的意义&#xff1a;为何部署是项目成功的关键 在深度学习项目的生命周期中&#xff0c;模型的部署是其成败的关键之一。通常&#xff0c;一个模型从概念构思、数据收集、训练到优化&#xff0c;最终目的…

【UML建模】用例图

1 参与者 参与者的概念&#xff1a; 指系统以外的、需要使用系统或与系统交互的外部实体 可以分为&#xff1a;人、外部设备、外部系统 参与者的图形符号&#xff1a; 例 3.1 在一个银行业务系统中&#xff0c;可能会有以下参与者 客户 &#xff1a;在银行业务系统中办理…

详细分析MySQL中的distinct函数(附Demo)

目录 前言1. 基本知识2. 基础Demo3. 进阶Demo 前言 该函数主要用于去重&#xff0c;对于细节知识&#xff0c;此文详细补充说明 1. 基本知识 DISTINCT 是一种用于查询结果中去除重复行的关键字 在查询数据库时&#xff0c;可能会得到重复的结果行&#xff0c;但有时只需要这…

奇妙的探索——偶然发现的bug

今天想在腾讯招聘官网找几个前端的岗位投一下&#xff0c;最近自己也在找工作&#xff0c;结果简历还没有投出去&#xff0c;就发现了腾旭招聘官网的3个前端bug。 1.有时候鼠标hover还没有滑倒下拉选框的菜单上&#xff0c;就消失了&#xff0c;消失的太快了&#xff0c;根本点…

揭秘App广告变现,如何轻松赚取额外收入?

揭秘App广告变现&#xff0c;如何轻松赚取额外收入&#xff1f; 在移动互联网高速发展的今天&#xff0c;APP广告变现已经成为了众多开发者和公司的主要盈利方式。但是&#xff0c;如何让一个APP实现高效的广告变现呢&#xff1f;这是一门大学问&#xff0c;需要我们用心去揣摩…

聚观早报 | TCL召开电视新品发布会;OceanBase 4.3发布

聚观早报每日整理最值得关注的行业重点事件&#xff0c;帮助大家及时了解最新行业动态&#xff0c;每日读报&#xff0c;就读聚观365资讯简报。 整理丨Cutie 4月22日消息 TCL召开电视新品发布会 OceanBase 4.3发布 科大讯飞推出耳背式助听器 F1联想中国大奖赛开赛 蔚来展…

个人博客建设必备:精选域名和主机的终极攻略

本文目录 &#x1f30f;引言&#x1f30f;域名的选择&#x1f315;域名的重要性品牌识别营销和宣传可访问性和易记性信任和权威感搜索引擎优化&#xff08;SEO&#xff09;未来的灵活性和扩展性保护品牌 &#x1f315;如何选择域名&#x1f315;工具与资源分享国内的主流域名注…

C++ 性能分析的实战指南(gperftools工具)[建议收藏]

文章目录 使用gperftools进行 C 性能分析的实战指南一、编译安装 gperftools1. 下载源代码&#xff1a;2. 编译和安装&#xff1a; 二、编写测试程序三、使用 gperftools 代码示例四、查看分析结果五、一份实际代码实例及实操1.代码实例2.操作命令3.结果分析根据上述数据&#…

我有一种不完美的完美主义,必须要有缺点,没有缺点就是不完美的

《程客有话说》是我们最新推出的一个访谈栏目&#xff0c;邀请了一些国内外有趣的程序员来分享他们的经验、观点与成长故事&#xff0c;我们尝试建立一个程序员交流与学习的平台&#xff0c;也欢迎大家推荐朋友或自己来参加我们的节目&#xff0c;一起加油。 本期我们邀请的程…

App Inventor 2 如何预览PDF文档?

预览PDF文档的方式 你可以使用Activity启动器查看已存储在你的设备上的 pdf 文档&#xff0c;也可以使用Web客户端通过网址URL打开 pdf 文档。 App Inventor 2 可以使用 .pdf 扩展名从程序包资产中查看 pdf 文件&#xff0c;不再需要外部 pdf 查看器&#xff01; 代码如下&a…

二进制,八进制,十六进制 开头的标识符号

1、引言 每个进制开头的标识符号容易忘&#xff0c;写篇小小博客&#xff0c;拯救一下俺的小脑袋瓜子。 2、进制开头标识符号

Ts类型体操详讲 之 extends infer (下)

目录 1、函数 &#xff08;1&#xff09;提取参数类型 &#xff08;2&#xff09;提取返回值类型 2、构造器 &#xff08;1&#xff09;提取构造器返回值 &#xff08;2&#xff09;提取构造器参数类型 3、索引类型 本章我们继续上节的内容继续&#xff0c;展示我们对ex…