WebSocket(一)

一.什么是WebSocket

【1】WebSocket是一种协议,设计用于提供低延迟,全双工和长期运行的连接。

全双工:通信的两个参与方可以同时发送和接收数据,不需要等待对方的响应或传输完成。

【2】比较

传统通信(http协议):电子邮件,网页游览,存在延迟,需要用户主动请求来更新数据。
实时通信(websocket协议):即时消息传递,音视频通话,在线会议和实时数据传输等,可以实现即时的数据传输和交流,不需要用户主动请求或刷新来获取更新数据。

【3】WebSocket之前的世界(基于http):
(1)轮询:客户端定期向服务器发送请求
缺点--会产生大量的请求和响应,导致不必要的网络开销和延迟。
(2)长轮询:在客户端发出请求后,保持连接打开,等待新数据相应后再关闭连接。
缺点--虽然消灭了了无效轮询,但是还是需要频繁的建立和关闭连接。
(3)Comet:保持长连接,在返回请求后继续保持连接打开,并允许服务器通过流式传输,frame等推送技术来主动向客户端推送数据。
缺点--虽然模拟了事实通信,但还是基于http模型,使用推送技巧来实现的。

【4】那么怎么建立websocket连接呢?

需要通过HTTP发送一次常规的Get请求,并在请求头中带上Upgrade,告诉服务器,我想从HTTP升级成WebSocket,连接就建立成功了,之后客户端就可以像服务器发送信息。

二.入门案例

1.先搭建配置和程序的基本结构

【1】导入依赖(websocket和fastjson)

springboot集成websocket,因为springboot项目都继承父项目,所以不用写依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
在websocket中不能直接像controller那样直接将对象return,所以需要fastjson之类的工具将对象转化为json字符串再返回给前端。
<dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version>
</dependency>

【2】开启springboot对websocket的支持

@Configuration
public class WebSocketConfig {@Bean//注入ServerEndpointExporter,自动注册使用@ServerEndpoint注解的public ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
}

【3】定义EndPoint类实现(一个websocket的链接对应一个Endpoint类)

/*** @ServerEndpoint 注解的作用** @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端*/@Slf4j
@Component
@ServerEndpoint("/websocket/{name}")
public class WebSocket {/*** 与某个客户端的连接对话,需要通过它来给客户端发送消息*/private Session session;/*** 标识当前连接客户端的用户名*/private String name;/*** 用于存储每一个客户端对象对应的WebSocket对象,因为它是属于类的,所以要用static*/private static ConcurrentHashMap<String,WebSocket> webSocketSet = new ConcurrentHashMap<>();//下面有三个生命周期//生命周期一:连接建立成功调用的方法//注意:这个方法的参数列表中只能使用@PathParam结束路径参数,而且必须使用至少一个@PathParam接收路径参数@OnOpenpublic void OnOpen(Session session, @PathParam(value = "name") String name){log.info("----------------------------------");this.session = session;this.name = name;// name是用来表示唯一客户端,如果需要指定发送,需要指定发送通过name来区分webSocketSet.put(name,this);log.info("[WebSocket] 连接成功,当前连接人数为:={}",webSocketSet.size());log.info("----------------------------------");log.info("");GroupSending(name+" 来了");}//生命周期二:连接建立关闭调用的方法@OnClosepublic void OnClose(){webSocketSet.remove(this.name);log.info("[WebSocket] 退出成功,当前连接人数为:={}",webSocketSet.size());GroupSending(name+" 走了");}//生命周期三:连接建立关闭调用的方法收到客户端消息后调用的方法@OnMessagepublic void OnMessage(String message_str){//只要某个客户端给服务端发送消息,就给它发送666AppointSending(this.name,"666");}//生命周期四:发生错误后调用的方法@OnErrorpublic void onError(Session session, Throwable error){log.info("发生错误");error.printStackTrace();}/*** 群发* @param message*/public void GroupSending(String message){for (String name : webSocketSet.keySet()){try {webSocketSet.get(name).session.getBasicRemote().sendText(message);}catch (Exception e){e.printStackTrace();}}}/*** 指定发送* @param name* @param message*/public void AppointSending(String name,String message){try {webSocketSet.get(name).session.getBasicRemote().sendText(message);}catch (Exception e){e.printStackTrace();}}
}

简单说就是每有一个客户端连接这个websocket,就会给生成一个websocket对象,并调用OnOpen方法打印日志和存储用户信息。然后连接保持,中间如果这个连接出错,调用OnError方法,如果客户端给服务端发送信息,就调用OnMessage,直到连接关闭,才调用OnClose方法。

然后服务端给客户端发送消息是通过你定义的EndPoint类的session.getBasicRemote().sendText(message)方法,如果你要返回json对象要用fastjson之类进行转换成json格式的字符串。

2.案例一:如果数据库对应的数据改变就向前端发送新的数据信息,解决长轮询的问题

场景:签到页面显示对应的签到信息,要根据信息的改变,如课程的签到状态,需要签到什么课程等信息的改变来在客户端实时更新这些信息。使用webSocket代替轮询解决这个问题。

下面是ui界面:

【1】在configure中增加代码,主要是为了通过配置类进而使得websocket获取请求头中的token
参考文章

@Configuration
@Slf4j
public class WebSocketConfig extends ServerEndpointConfig.Configurator {// 创建ServerEndpointExporter的Bean,用于自动注册WebSocket端点@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}/*** 建立握手时,连接前的操作* 在这个方法中可以修改WebSocket握手时的配置信息,并将一些额外的属性添加到用户属性中*/@Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {// 获取用户属性final Map<String, Object> userProperties = sec.getUserProperties();// 获取HTTP请求头信息Map<String, List<String>> headers = request.getHeaders();// 通过"Authorization"键从请求头中获取对应的token,并存储在用户属性中List<String> header1 = headers.get("Authorization");userProperties.put("Authorization", header1.get(0));}/*** 初始化端点对象,也就是被@ServerEndpoint所标注的对象* 在这个方法中可以自定义实例化过程,比如通过Spring容器获取实例*/@Overridepublic <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException {return super.getEndpointInstance(clazz);}/*** 获取指定会话的指定请求头的值** @param session     WebSocket会话对象* @param headerName  请求头名称* @return 请求头的值*/public static String getHeader(Session session, String headerName) {// 从会话的用户属性中获取指定请求头的值final String header = (String) session.getUserProperties().get(headerName);// 如果请求头的值为空或空白,则记录错误日志,并关闭会话if (StrUtil.isBlank(header)) {log.error("获取header失败,不安全的链接,即将关闭");try {session.close();} catch (IOException e) {e.printStackTrace();}}return header;}
}

【2】编写ServerEndpoint

这里主要有三个技术点:
技术点一:注入mapper或service
这里注入如果按controller的方式直接注入无法注入成功,需要设置成静态变量然后写个方法赋值。
技术点二:获取请求头中的token,通过配置可以使用session.getUserProperties().get("")获取请求头
技术点三:建立线程,每隔一段时间检查数据库并更新客户端的数据

@Slf4j
@Component
@ServerEndpoint(value = "/websocket/studentNowAttendances/{userId}",configurator = WebSocketConfig.class)
public class CourseAttendanceEndPoint {//技术点一:注入mapper或service//这里注入如果按controller的方式直接注入无法注入成功,需要设置成静态变量然后写个方法赋值。private static StudentMapper studentMapper;@Autowiredpublic void setStudentMapper (StudentMapper studentMapper){CourseAttendanceEndPoint.studentMapper = studentMapper;}private static CourseAttendanceService courseAttendanceService;@Autowiredpublic void setCourseAttendanceService (CourseAttendanceService courseAttendanceService){CourseAttendanceEndPoint.courseAttendanceService = courseAttendanceService;}private static RedisCache redisCache;@Autowiredprivate void setRedisCache (RedisCache redisCache){CourseAttendanceEndPoint.redisCache = redisCache;}/*** 与某个客户端的连接对话,需要通过它来给客户端发送消息*/private Session session;/*** 标识当前连接客户端的用userId*/private String studentId;/*** 用于存所有的连接服务的客户端,这个对象存储是安全的* 注意这里的key和value,设计的很巧妙,value刚好是本类对象 (用来存放每个客户端对应的MyWebSocket对象)*/private static ConcurrentHashMap<String, CourseAttendanceEndPoint> webSocketSet = new ConcurrentHashMap<>();//线程private ScheduledExecutorService scheduler;//存储该学生用户获取到的课程信息private StudentAttendanceNow lastCourseInfo = new StudentAttendanceNow();/*** 群发* @param message*/public void groupSending(String message){for (String name : webSocketSet.keySet()){try {webSocketSet.get(name).session.getBasicRemote().sendText(message);}catch (Exception e){e.printStackTrace();}}}/*** 指定发送* @param studentId* @param message*/public void appointSending(String studentId,String message){System.out.println(webSocketSet.get(studentId).session);try {webSocketSet.get(studentId).session.getBasicRemote().sendText(message);}catch (Exception e){e.printStackTrace();}}/*** 连接建立成功调用的方法* session为与某个客户端的连接会话,需要通过它来给客户端发送数据*/@OnOpenpublic void OnOpen(Session session, EndpointConfig config,@PathParam("userId") String userId){log.info("----------------------------------");//技术点二:获取请求头中的token,通过配置可以使用session.getUserProperties().get("")获取请求头// 获取用户属性//获取HttpSession对象final String Authorization = (String) session.getUserProperties().get("Authorization");System.out.println(Authorization);Claims claims= JwtUtil.parseJwt(Authorization);userId=claims.getSubject();QueryWrapper<Student> studentQueryWrapper=new QueryWrapper<Student>();studentQueryWrapper.eq("userid",userId);Student student=studentMapper.selectOne(studentQueryWrapper);//为这个websocket的studentId和session变量赋值,因为后面还要用到studentId=student.getId().toString();// studentId是用来表示唯一客户端,如果需要指定发送,需要指定发送通过studentId来区分this.session=session;//这句话一定要写,不然后面就会报错session为空//将studentId及其对应的websocket实例保存在属于类的webSocketSet中,便于后续发送信息时候可以知道要发送给哪个实例webSocketSet.put(studentId,this);//打印websocket信息log.info("[WebSocket] 连接成功,当前连接人数为:={}",webSocketSet.size());log.info("----------------------------------");log.info("");//技术点三:建立线程,每隔一段时间检查数据库并更新客户端的数据//建立线程,每个一段时间检查客户端对应在websocket中的数据有没有改变,有改变将信息重新发给客户端scheduler = Executors.newScheduledThreadPool(1);scheduler.scheduleAtFixedRate(this::checkDatabaseAndUpdateClients, 0, 5, TimeUnit.SECONDS);}//每个一段时间检查客户端对应在websocket中的数据有没有改变,有改变将信息重新发给客户端private void checkDatabaseAndUpdateClients() {// 模拟查询最新的课程信息// 假设从数据库或其他数据源中查询得到最新的课程信息System.out.println("666");try{//获取当前的信息StudentAttendanceNow currentCourseInfo = courseAttendanceService.getStudentAttendanceNow(studentId);//与之前的信息进行比较,如果说当前数据与上次存储的数据相比发生了改变,那就替换掉原来的数据并把新的数据发送给客户端if(lastCourseInfo.equals(currentCourseInfo)==false){lastCourseInfo=currentCourseInfo;System.out.println(studentId);appointSending(studentId, JSON.toJSONString(currentCourseInfo));}}catch (Exception e){//防止没有查询到数据等异常System.out.println(e);}}/*** 连接关闭调用的方法*/@OnClosepublic void OnClose(){//退出时将用户从记录中删除,并在log中打印退出信息webSocketSet.remove(this.studentId);log.info("[WebSocket] 退出成功,当前连接人数为:={}",webSocketSet.size());// 关闭定时任务scheduler.shutdown();}/*** 收到客户端消息后调用的方法*/@OnMessagepublic void OnMessage(Session session,String message){}/*** 发生错误时调用* @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error){log.info("发生错误");error.printStackTrace();}}

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

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

相关文章

Docker 的快速使用

ubuntu安装 centos安装 安装完毕之后执行一下这条命令&#xff0c;可以避免每次使用docker命令都需要sudo权限 sudo usermod -aG docker $USER阿里云docker镜像加速 DockerHub 遇到不懂或者不会使用的命令可以使用docker --help查看文档 docker --help 如&#xff1a; dock…

JavaWeb 文件上传和下载

目录 一、文件上传 1.文件上传和下载的使用说明 : 2.文件上传基本原理 : 3.文件上传经典案例 : 3.1 页面实现: 3.2 servlet实现 : 3.3 工具类实现 : 3.4 运行测试 : 3.5 注意事项 : 二、文件下载 1.文件下载基本原理 : 2.文件下载经典案例 : 2.1 准备工作 2.2 页面…

关于C语言参数传递的

一、C语言参数传递是整体带入 #include <stdio.h> #define DF(a,b) (a2*b) int main() { int s5; int k DF((s1),(s-3)); printf("%d",k); }输出结果 原因&#xff1a; #define DF(a,b) (a2*b) int k DF((s1),(s-3)); //等效 int k DF((s1)2 * (s-3)); …

useEffect 不可忽视的 cleanup 函数

在 react 开发中&#xff0c; useEffect 是我们经常会使用到的钩子&#xff0c;一个基础的例子如下&#xff1a; useEffect(() > {// some code here// cleanup 函数return () > {doSomething()} }, [dependencies])上述代码中&#xff0c; cleanup 函数的执行时机有如下…

代码随想录笔记--栈与队列篇

目录 1--用栈实现队列 2--用队列实现栈 3--有效的括号 4--删除字符串中的所有相邻重复项 5--逆波兰表达式求值 6--滑动窗口的最大值 7--前k个高频元素 1--用栈实现队列 利用两个栈&#xff0c;一个是输入栈&#xff0c;另一个是输出栈&#xff1b; #include <iostrea…

Windows 重新映射 CapsLock 大写锁定到 Ctrl

Windows 重新映射 CapsLock 大写锁定到 Ctrl 本要点中的这些方法适用于我的美国键盘布局。我不确定其他布局。如果出现问题&#xff0c;请恢复您的更改&#xff1b;删除您创建的注册表项&#xff08;并重新启动&#xff09;。 强烈推荐 方法5 ctrl2cap&#xff0c;因为不会影…

Modbus通信协议

Modbus通信协议 一、概述 Modbus通信协议是一种工业现场总线协议标准&#xff0c;常用的Modbus协议有以下三种类型&#xff1a;Modbus TCP、Modbus RTU、Modbus ASCll。 Modbus通信协议解决了通过串行线路在电子设备之间发送信息的问题。该协议在遵循该协议的体系结构中实现主…

Elasticsearch 7.6 - API高阶操作篇

ES 7.6 - API高阶操作篇 分片和副本索引别名添加别名查询所有别名删除别名使用别名代替索引操作代替插入代替查询 场景实操 滚动索引索引模板创建索引模板查看模板删除模板 场景实操一把索引的生命周期数据迁移APIGEO(地理)API索引准备矩形查询圆形查询多边形查询 自定义分词器…

小白学Go基础01-Go 语言的介绍

Go 语言对传统的面向对象开发进行了重新思考&#xff0c;并且提供了更高效的复用代码的手段。Go 语言还让用户能更高效地利用昂贵服务器上的所有核心&#xff0c;而且它编译大型项目的速度也很快。 用 Go 解决现代编程难题 Go 语言开发团队花了很长时间来解决当今软件开发人员…

ThinkPHP 文件上传 fileSystem 扩展的使用

ThinkPHP 文件上传 ThinkPHP 文件上传 扩展 filesystem一、安装 FileSystem 扩展二、认识 filesystem 配置文件 config/filesystem.php三、上传验证&#xff08;涉及到验证器的知识点&#xff09;四、文件上传demo ThinkPHP 文件上传 扩展 filesystem ThinkPHP 为我们 提供了 …

C语言每日一练---Day(14)

本专栏为c语言练习专栏&#xff0c;适合刚刚学完c语言的初学者。本专栏每天会不定时更新&#xff0c;通过每天练习&#xff0c;进一步对c语言的重难点知识进行更深入的学习。 今日练习题关键字&#xff1a;统计每个月兔子的总数 数列的和 &#x1f493;博主csdn个人主页&#x…

Stable Diffusion 多视图实践

此教程是基于秋叶的webui启动器 1.Stable Diffsuion 使用多视图需要准备一个多角度open pose 图 我给大家提供一个可使用的。 2.需要添加图片到到controlnet当中,不要选择预处理器,选择模型为openpose的模型,然后需要点选同步图片尺寸。 3.然后填写关键字可以参照一下这个…

zookeeper 理论合集

目录 系统背景 集群结构 多个节点之间的角色 节点的状态 为什么引入 Observer 存储结构 ZNode 节点结构 ZNode 创建类型 内存数据存储 数据持久化 zookeeper 的容量大小 数据同步 消息广播 崩溃恢复 如何保证顺序一致性 核心流程 Leader 选举流程 脑裂问题 …

【nginx】access.log按照时间分割

access.log 大的网络访问下没有几天文件就变得非常大了&#xff0c;一直累计也不是办法啊 查看文件大小 du -sh *access.log 13G 按照时间把access.log分割一下 修改 nginx.conf 修改前的文件 修改后的文件 增加的内容 map $time_iso8601 $logdate {~^(?<ymd>\d{4}…

【ArcGIS Pro二次开发】(65):进出平衡SHP转TXT、TXT转SHP

最近一个小伙伴提了这么一个需求&#xff0c;需要把TXT和SHP进行互转。 这种TXT文件其实遇到了好几个版本&#xff0c;都有一点小差异。之前已经做过一个TXT转SHP的工具&#xff0c;但好像不适用。于是针对这个版本&#xff0c;做了互转的2个工具。 【SHP转TXT】 一、要实现的…

响应式图片与 CSS image-set

响应式图片 前置知识 art direction problem光栅图像与矢量图像 raster image and vector images img 能否担此重任 sizessrcset实际看一看 picture: img 的好姐妹 source实际看一看 CSS image-set 语法兼容性 其他注意事项 响应式图片 图片在网页中占据了 超过 60% 的浏览带…

nodepad++ 插件的安装

nodepad 插件的安装 一、插件安装二、安装插件&#xff1a;Json Viewer nodepad 有 插件管理功能&#xff0c;其中有格式化json以及可以将json作为树查看的插件&#xff1a; Json Viewer 一、插件安装 1、首先下载最新的notepad 64位【https://notepad-plus.en.softonic.com…

VTK——使用ICP算法进行模型配准

ICP算法 迭代最近点&#xff08;Iterative Closest Point&#xff0c;ICP&#xff09;算法是一种用于两个三维形状之间几何对齐&#xff08;也叫做配准&#xff09;的计算方法。通常&#xff0c;这两个形状至少有一个是点云数据。ICP算法用于最小化源点云与目标点云之间点到点…

java企业工程管理系统源码之提高工程项目管理软件的效率

高效的工程项目管理软件不仅能够提高效率还应可以帮你节省成本提升利润 在工程行业中&#xff0c;管理不畅以及不良的项目执行&#xff0c;往往会导致项目延期、成本上升、回款拖后&#xff0c;最终导致项目整体盈利下降。企企管理云业财一体化的项目管理系统&#xff0c;确保…

Golang设计模式

Golang设计模式 Golang设计模式简介Golang工厂设计模式Golang单例设计模式Golang抽象工厂设计模式Golang建造者模式 (Builder Pattern)Golang 原型模式(Prototype Pattern)Golang适配器模式Golang 桥接模式&#xff08;Bridge Pattern&#xff09;Golang装饰器模式(Decorator …