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*/@Beanpublic 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);/*** 链接成功调用的方法*/@OnOpenpublic 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();}}/*** 链接关闭调用的方法*/@OnClosepublic void onClose() {try {webSockets.remove(this);log.info("【websocket消息】连接断开,总数为:" + webSockets.size());} catch (Exception e) {}}/*** 收到客户端消息后调用的方法** @param message*/@OnMessagepublic void onMessage(String message) {log.info("【websocket消息】收到客户端消息:" + message);}/*** 发送错误时的处理** @param session* @param error*/@OnErrorpublic 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 {@Resourceprivate 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");//消息idobj.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");//消息idobj.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");//消息idobj.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");//实际业务中,这里需要从系统里获取当前当前登录的用户idvar 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发送与接收的消息
在这里插入图片描述

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

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

相关文章

pycharm import的类库修改后要重启问题的解决方法

通过将以下行添加到pycharm中的settings-> Build,Excecution,Deployment-> Console-> Python Console中&#xff0c;可以指示Pycharm在更改时自动重新加载模块&#xff1a; %load_ext autoreload %autoreload 2

ubuntu 设置系统时间矫正

1、安装ntpdate&#xff0c;同步标准时间 2、修改时区 3、在.profile文件中写入上面提示的信息&#xff0c;保存退出、更新配置文件或者重启生效 3.1、或者配合上面的cp那条命令&#xff0c;用下面的命令保存到底层 $ hwclock --systohc 4、重启之后&#xff0c;查看日期时间已…

中间件上云部署 rocketmq

中间件上云部署 rocketmq rocketmq部署一、rokectmq介绍二、rokectmq特性三、使用rocketmq理由四、rocketmq 核心概念五、rocketmq角色六、rocketmq集群部署方式七、rocketmq集群部署7.1 环境说明7.2 构建rocketmq镜像7.3 获取rocketmq-dashboard镜像7.4 rocketmq部署描述文件编…

linux安装mysql以及使用navicat连接mysql

目录 一、下载mysql 二、安装mysql 三、使用Navicat连接MySQL 四、常见问题 1、启动服务时报错 Failed to start mysql.service: Unit not found. 的解决方法。 2、登录过程出现&#xff1a;access denied for user’root’‘localhost’(using password:Yes) 的解决方…

Redis的缓存问题

说起Redis的缓存&#xff0c;我们知道前端发出的请求到后端&#xff0c;后端先从Redis中查询&#xff0c;如果查询到了则直接返回&#xff0c;如果Redis中未查询到&#xff0c;就去数据库中查询&#xff0c;如果数据库中存在&#xff0c;则返回结果并且更新到Redis缓存当中&…

《遗留系统现代化》读书笔记(基础篇)

目录 为什么要对遗留系统进行现代化&#xff1f; 什么是遗留系统&#xff1f; 遗留系统的现代化价值 总结 遗留系统的四化建设 代码现代化 架构现代化 DevOps 现代化 团队结构现代化 总结 本文地址&#xff1a;《遗留系统现代化》读书笔记&#xff08;基础篇&#xff…

通讯录(纯C语言实现)

相信大家都有过通讯录&#xff0c;今天我来带大家实现以下最简单的通讯录&#xff0c;通过本篇文章&#xff0c;相信可以让大家对C语言有进一步的认识。 话不多说&#xff0c;我们先放函数的实现 #define _CRT_SECURE_NO_WARNINGS 1 #include "Contact.h"int Chea…

高时空分辨率、高精度一体化预测技术之风、光、水能源自动化预测教程

详情点击链接&#xff1a;高时空分辨率、高精度一体化预测技术之风、光、水能源自动化预测 第一&#xff1a;预测平台及安装 一、高精度气象预测基础 综合气象观测数值模拟模式&#xff1b; 全球预测模式、中尺度数值模式&#xff1b; 二、自动化预测平台 Linux系统 Crontab…

记一次rabbitmq消息发送成功,消费丢失问题

记一次rabbitmq消息发送成功&#xff0c;消费丢失问题 背景 测试数据归档&#xff0c;偶现数据未归档 排查 idea线上调试&#xff0c;log日志&#xff0c;数据库消息发送记录&#xff0c;代码分块重复执行看哪块出的问题&#xff0c;结果均无问题&#xff0c;最后使用rabbi…

AI销售工具:驱动销售团队效率和个性化服务的未来

在数字化时代&#xff0c;AI销售工具成为推动销售行业发展的重要力量。这些创新工具融合了人工智能技术和销售流程&#xff0c;以提高销售团队的效率和提供个性化服务为目标。随着科技的不断进步&#xff0c;AI销售工具正引领着销售行业走向一个更加智能和高效的未来。 AI驱动的…

数据结构 ~ 栈、队列

栈 一个后进先出的数据结构、JS中没有栈&#xff0c;可以使用 Array 模拟 const stack [] stack.push(1) // 入栈 stack.push(2) // 入栈 const item1 stack.pop() // 出栈 const item2 stack.pop() // 出栈以上代码可以使用 nodeJs 断点调试&#xff08;F5启动&#xff0…

【Linux】进程信号

文章目录 1. 信号的理解1.1 什么是进程信号1.2 见见进程信号1.3 信号的发送与记录1.4 信号的处理1.4.1 信号的三种处理方式1.4.2 核心转储 2. 信号的产生2.1 终端按键产生信号2.2 系统调用产生信号2.3 软件异常产生信号2.4 硬件异常产生信号 3. 信号的保存3.1 信号其他相关概念…

电子锁语音芯片方案,低功耗声音提示ic,WT588F02B-8S

随着科技的不断发展&#xff0c;电子锁已成为现代社会中&#xff0c;安全性和便利性并存的必备设备。如何为电子锁行业增添智能化、人性化的功能已成为行业内的热门话题。 在这个迅速发展的市场中&#xff0c;深圳唯创知音推出了一款语音交互方案——WT588F02B-8S 低功耗声音提…

UnxUtils工具包,Windows下使用Linux命令

1. 前言 最近写批处理多了&#xff0c;发现Windows下的bat批处理命令&#xff0c;相比Linux的命令&#xff0c;无论是功能还是多样性&#xff0c;真的差太多了。但有时候又不得不使用bat批处理&#xff0c;好在今天发现了一个不错的工具包&#xff1a;UnxUtils&#xff0c;这个…

Python多线程 threading 和多进程 multiprocessing

1. 并发 vs 并行 线程是程序执行的最小单位&#xff0c;一个进程可以由一个或多个线程组成&#xff0c;各个线程之间也是交叉执行。 并发&#xff0c;相当于单核CPU&#xff0c;宏观同时执行&#xff0c;微观高速切换 交替执行。多线程、高并发这些词语更多地出现在服务端程序…

06-C++ 基本算法 - 二分法

&#x1f4d6; 前言 在这个笔记中&#xff0c;我们将介绍二分法这种基本的算法思想&#xff0c;以及它在 C 中的应用。我们将从一个小游戏猜数字开始&#xff0c;通过这个案例来引出二分法的概念。然后我们将详细讲解什么是二分法以及它的套路和应用。最后&#xff0c;我们还会…

在 3ds Max 中创建逼真的玻璃材质

推荐&#xff1a; NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 尽管本教程基于 3ds Max&#xff0c;但相同的设置适用于许多其他 3D 产品。 注意&#xff1a;单击每个步骤中的缩略图可查看更大的屏幕截图&#xff0c;其中包括视口和用户界面的相关部分。 步骤 1由于本教…

广西学子复读15年,不服从分配。网友:完全是浪费时间

广西学子复读15年&#xff0c;不服从分配。网友&#xff1a;完全是浪费时间 唐尚珺的复读行为引起了网友们的不同解读。有人认为他是一个执念深重的人&#xff0c;目标是考上清华北大&#xff0c;但这个说法是否真实&#xff0c;我们无法确定。无论如何&#xff0c;我们必须认识…

electron+vue3全家桶+vite项目搭建【24】设置应用图标,打包文件的图标

文章目录 引入实现步骤测试结果 引入 demo项目地址 在electron中&#xff0c;我们可以通过electron-builder的配置文件来设置打包后的应用图标 实现步骤 因为mac环境下的图标需要特殊格式&#xff0c;这里我们可以利用electron-icon-builder进行配置 1.引入相关依赖 # 安…

GPT 如此强大,我们可以利用它实现什么?

GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一种基于Transformer结构的预训练语言生成模型&#xff0c;由OpenAI研发。它可以生成高质量的自然语言文本&#xff0c;取得了很好的效果&#xff0c;被广泛应用于各个领域。以下是一些利用GPT实现的应用。 一…