WebSocket理论与实战
ztj100 2025-01-19 02:00 39 浏览 0 评论
一 WebSocket理论
什么是http请求
http链接分为短链接、长链接,短链接是每次请求都要三次握手才能发送自己的信息。即每一个request对应一个response。长链接是在一定的期限内保持链接(但是是单向的,只能从客户端向服务端发消息,然后服务端才能响应数据给客户端,服务端不可以主动给客户端发消息)。保持TCP连接不断开。客户端与服务器通信,必须要有客户端发起然后服务器返回结果。客户端是主动的,服务器是被动的。
WebSocket
WebSocket他是为了解决客户端发起多个http请求到服务器资源浏览器必须要经过长时间的轮训问题而生的,他实现了多路复用,他是全双工通信。在webSocket协议下客服端和浏览器可以同时发送信息。
建立了WebSocket之后服务器不必在浏览器发送request请求之后才能发送信息到浏览器。这时的服务器已有主动权想什么时候发就可以随时发送信息到浏览器。而且信息当中不必在带有head的部分信息了,与http的长链接通信来对比,这种方式,不仅能降低服务器的压力。而且信息当中也减少了部分多余的信息。
WebSocket与http关系
相同点:
- 都是基于tcp的,都是可靠性传输协议
- 都是应用层协议
不同点:
- WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息
- HTTP是单向的
- WebSocket是需要浏览器和服务器握手进行建立连接的
- 而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/12/6
* @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/6
* @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/6
* @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发送与接收的消息
相关推荐
- 离谱!写了5年Vue,还不会自动化测试?
-
前言大家好,我是倔强青铜三。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。Playwright是一个功能强大的端到...
- package.json 与 package-lock.json 的关系
-
模块化开发在前端越来越流行,使用node和npm可以很方便的下载管理项目所需的依赖模块。package.json用来描述项目及项目所依赖的模块信息。那package-lock.json和...
- Github 标星35k 的 SpringBoot整合acvtiviti开源分享,看完献上膝盖
-
前言activiti是目前比较流行的工作流框架,但是activiti学起来还是费劲,还是有点难度的,如何整合在线编辑器,如何和业务表单绑定,如何和系统权限绑定,这些问题都是要考虑到的,不是说纯粹的把a...
- Vue3 + TypeScript 前端研发模板仓库
-
我们把这个Vue3+TypeScript前端研发模板仓库的初始化脚本一次性补全到可直接运行的状态,包括:完整的目录结构所有配置文件研发规范文档示例功能模块(ExampleFeature)...
- Vue 2迁移Vue 3:从响应式到性能优化
-
小伙伴们注意啦!Vue2已经在2023年底正式停止维护,再不升级就要面临安全漏洞没人管的风险啦!而且Vue3带来的性能提升可不是一点点——渲染速度快40%,内存占用少一半,更新速度直接翻倍!还在...
- VUE学习笔记:声明式渲染详解,对比WEB与VUE
-
声明式渲染是指使用简洁的模板语法,声明式的方式将数据渲染进DOM系统。声明式是相对于编程式而言,声明式是面向对象的,告诉框架做什么,具体操作由框架完成。编程式是面向过程思想,需要手动编写代码完成具...
- 苏州web前端培训班, 苏州哪里有web前端工程师培训
-
前端+HTML5德学习内容:第一阶段:前端页面重构:PC端网站布局、HTML5+CSS3基础项目、WebAPP页面布局;第二阶段:高级程序设计:原生交互功能开发、面向对象开发与ES5/ES6、工具库...
- 跟我一起开发微信小程序——扩展组件的代码提示补全
-
用户自定义代码块步骤:1.HBuilderX中工具栏:工具-代码块设置-vue代码块2.通过“1”步骤打开设置文件...
- JimuReport 积木报表 v1.9.3发布,免费可视化报表
-
项目介绍积木报表JimuReport,是一款免费的数据可视化报表,含报表、大屏和仪表盘,像搭建积木一样完全在线设计!功能涵盖:数据报表、打印设计、图表报表、门户设计、大屏设计等!...
- 软开企服开源的无忧企业文档(V2.1.3)产品说明书
-
目录1....
- 一款面向 AI 的下一代富文本编辑器,已开源
-
简介AiEditor是一个面向AI的下一代富文本编辑器。开箱即用、支持所有前端框架、支持Markdown书写模式什么是AiEditor?AiEditor是一个面向AI的下一代富文本编辑...
- 玩转Markdown(2)——抽象语法树的提取与操纵
-
上一篇玩转Markdown——数据的分离存储与组件的原生渲染发布,转眼已经鸽了大半年了。最近在操纵mdast生成md文件的时候,心血来潮,把玩转Markdown(2)给补上了。...
- DeepseekR1+ollama+dify1.0.0搭建企业/个人知识库(入门避坑版)
-
找了网上的视频和相关文档看了之后,可能由于版本不对或文档格式不对,很容易走弯路,看完这一章,可以让你少踩三天的坑。步骤和注意事项我一一列出来:1,前提条件是在你的电脑上已配置好ollama,dify1...
- 升级JDK17的理由,核心是降低GC时间
-
升级前后对比升级方法...
- 一个vsCode格式化插件_vscode格式化插件缩进量
-
ESlint...
你 发表评论:
欢迎- 一周热门
-
-
MySQL中这14个小玩意,让人眼前一亮!
-
旗舰机新标杆 OPPO Find X2系列正式发布 售价5499元起
-
【VueTorrent】一款吊炸天的qBittorrent主题,人人都可用
-
面试官:使用int类型做加减操作,是线程安全吗
-
C++编程知识:ToString()字符串转换你用正确了吗?
-
【Spring Boot】WebSocket 的 6 种集成方式
-
PyTorch 深度学习实战(26):多目标强化学习Multi-Objective RL
-
pytorch中的 scatter_()函数使用和详解
-
与 Java 17 相比,Java 21 究竟有多快?
-
基于TensorRT_LLM的大模型推理加速与OpenAI兼容服务优化
-
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)