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

文章目录

    • 原理
    • 作用
    • 客户端 API
    • 服务端 API
    • 生命周期
    • 常见注解
    • SpringBoot示例

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

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

原理

下图是使用轮询或WebSocket两种方式向服务器发起请求的简易流程。
轮询和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 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

服务端 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() 方法发送消息。

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

生命周期

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

常见注解

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 {@Beanpublic 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连接后,被调用@OnOpenpublic 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) {//记录日志}}// 浏览器发送消息到服务端,该方法被调用@OnMessagepublic 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 连接时被调用@OnClosepublic 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 {@Overridepublic 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;}
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开放式耳机哪个牌子好?五大爆款机型大盘点

开放式耳机采用挂耳设计&#xff0c;体积小巧&#xff0c;携带方便&#xff0c;并且更加通风透气&#xff0c;避免了耳朵过热和出汗导致的问题&#xff1b;更轻的重量能有效减少长期佩戴对耳朵带来的压力&#xff0c;佩戴时舒适度直接爆表&#xff0c;在跑步、爬山、打球等户外…

奇安信天擎 卸载

说明 之前按单位要求安装了奇安信软件&#xff0c;感觉和360一样流氓&#xff0c;最近由于一些原因&#xff0c;可以将之安装的安全软件卸载&#xff0c;今天抽时间研究了一下卸载奇安信的方法。 卸载步骤 1、使用everything搜索EntBase.dat配置文件&#xff0c;这个配置文件…

智慧能源数据监控平台

随着科技的飞速发展&#xff0c;能源管理已逐渐从传统的粗放型向精细化、智能化转变。在这个转型过程中&#xff0c;HiWoo Cloud平台的智慧能源数据监控平台以其独特的技术优势和创新理念&#xff0c;正引领着能源管理的新潮流。 一、智慧能源数据监控平台的概念 智慧能源数据…

【酱浦菌-模拟仿真】python模拟仿真PN结伏安特性

PN结的伏安特性 PN结的伏安特性描述了PN结在外部电压作用下的电流-电压行为。这种特性通常包括正向偏置和反向偏置两种情况。 正向偏置 当外部电压的正极接到PN结的P型材料&#xff0c;负极接到N型材料时&#xff0c;称为正向偏置。在这种情况下&#xff0c;外加的正向电压会…

在离线环境中将 CentOS 7.5 原地升级并迁移至 RHEL 7.9

《OpenShift / RHEL / DevSecOps 汇总目录》 说明 本文将说明如何在离线环境中将 CentOS 7.5 升级并迁移至 RHEL 7.9。为了简化准备过程&#xff0c;本文前面将在在线环境中安装用到的各种所需验证软件&#xff0c;而在后面升级迁移的时候再切换到由 ISO 构成的离线 Yum Repo…

Nacos 安全零信任实践

作者&#xff1a;柳遵飞 Nacos 作为配置中心经常存储一些敏感信息&#xff0c;但是由于误用导致安全风险&#xff0c;最常见的主要是以下两个问题&#xff1a; 1&#xff09;Nacos 暴露公网可以吗&#xff1f;不可以&#xff0c;因为 Nacos 定位是注册配置中心&#xff0c;是…

JVM垃圾收集器--分区收集器

G1收集器 属性 G1&#xff08;Garbage-First Garbage Collector&#xff09;在 JDK 1.7 时引入&#xff0c;在 JDK 9 时取代 CMS 成为了默认的垃圾收集器。G1 有五个属性&#xff1a;分代、增量、并行、标记整理、STW。 分代 G1收集器 将内部分为多个大小相等的区域&#x…

【Redis 开发】Redis分片集群

分片集群 分片集群搭建分片集群 散列插槽集群伸缩故障转移RedisTemplate访问分片集群 分片集群 在我们使用哨兵进行高并发读的问题&#xff0c;但是还有海量数据存储,高并发写的问题,使用分片集群可以解决&#xff1a; 特征&#xff1a; 集群中有多个master&#xff0c;每个m…

国内各种免费AI聊天机器人(ChatGPT)推荐(上)

作者主页&#xff1a;点击&#xff01; 国内免费AI推荐专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月27日11点25分 欢迎来到AI聊天机器人推荐系列的第一篇文章&#xff01; 在这个系列中&#xff0c;我将引领您探索国内各种AI聊天机器人的精彩世界。 从…

更易使用,OceanBase开发者工具 ODC 4.2.4 版本升级

亲爱的朋友们&#xff0c;大家好&#xff01;我们的ODC&#xff08;OceanBase Developer Center &#xff09;再次迎来了重要的升级V 4.2.4&#xff0c;这次我们诚意满满&#xff0c;从五个方面为大家精心打造了一个更加易用、贴心&#xff0c;且功能更强的新版本&#xff0c;相…

mySQL商城项目实战 (终)(全部表)(1-88张)

本章无sql语句&#xff0c;直接放转出的sql文件。 88张表结果如图! 资源在已经与文章绑定&#xff0c; 在navicat工具中&#xff0c;执行以下步骤 在新建的数据库中右键,点击【运行sql文件】&#xff0c;运行绑定的资源&#xff0c;之后您就可以在您的navicat中看到我建好的8…

2024 java easyexcel poi word模板填充数据,多个word合成一个word

先看效果 一、准备工作 1.word模版 2.文件路径 二、pom依赖 <!-- easyexcel --><dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>2.1.7</version></dependency><depe…

mysql面试题九(SQL优化)

目录 1.一条 SQL 是如何执行的 2.索引失效的几种情况 3.EXPLAIN 4.Where 子句如何优化 5.超大分页或深度分页如何处理 6.大表查询如何优化 7.分库分表 基本概念 分库分表方法 水平拆分 垂直拆分 分库分表后的注意事项 1.一条 SQL 是如何执行的 在MySQL中&#xff0…

Flink checkpoint 源码分析- Flink Checkpoint 触发流程分析

序言 最近因为工作需要在阅读flink checkpoint处理机制&#xff0c;学习的过程中记录下来&#xff0c;并分享给大家。也算是学习并记录。 目前公司使用的flink版本为1.11。因此以下的分析都是基于1.11版本来的。 在分享前可以简单对flink checkpoint机制做一个大致的了解。 …

VSCode SSH连接远程主机失败,显示Server status check failed - waiting and retrying

vscode ssh连接远程主机突然连接不上了&#xff0c;终端中显示&#xff1a;Server status check failed - waiting and retrying 但是我用Xshell都可以连接成功&#xff0c;所以不是远程主机的问题&#xff0c;问题出在本地vscode&#xff1b; 现象一&#xff1a; 不停地输入…

软考-信息系统项目管理师-论文技术架构模板(60天备考第26天)

分享一段信息系统项目管理师论文项目技术架构描述的万能模板&#xff0c;供大家参考。距离考试还有二十八天&#xff0c;如果论文写不好的可以加微进论文指导群学习论文写作。 该系统前端基于Vue开发&#xff0c;后端基于java开发&#xff0c;前后端分离部署。整体采用B/S架构&…

STM32 学习13 低功耗模式与唤醒

STM32 学习13 低功耗模式与唤醒 一、介绍1. STM32低功耗模式功能介绍2. 常见的低功耗模式&#xff08;1&#xff09;**睡眠模式 (Sleep Mode)**:&#xff08;2&#xff09;**停止模式 (Stop Mode)**:&#xff08;3&#xff09;**待机模式 (Standby Mode)**: 二、睡眠模式1. 进入…

Golang基础1-基本类型、if、switch、string

基本类型 bool 整数&#xff1a;byte(相当于uint8), rune(相当于int32), int/uint ,int8/uint8 ,int16/uint16 ,int32/uint32 ,int64/uint64 浮点数: float32 ,float64, complex64 ,complex128 array&#xff08;值类型&#xff09;、slice、map、chan&#xff08;引用类型…

西瓜书学习——决策树形状、熵和决策树的本质

文章目录 决策树形状监督学习算法分类与回归 熵信息熵香农熵 (Shannon Entropy) - H(X)联合熵 (Joint Entropy) - H(X, Y)条件熵 (Conditional Entropy) - H(Y|X)互信息 (Mutual Information) - I(X; Y)相对熵 (Relative Entropy) / KL散度 (Kullback-Leibler Divergence) - DK…

小程序使用阿里巴巴矢量图标库

一、登录官网 www.iconfont.cn 二、在搜索框中搜索想要的图标&#xff0c;将鼠标移动到图标上会看到三个标记 可以使用下载&#xff0c;直接使用&#xff1a; 可以使用css文件使用&#xff1a; 首先点击购物车样式的选项&#xff0c;而后点击下图位置&#xff1a; 点击自己创…