当前位置: 首页 > news >正文

http/sse/websocket 三大协议演化历史以及 sse协议下 node.js express 服务实现打字机案例 负载均衡下的广播实现机制

背景

自从2022年底chatgpt上线后,sse就进入了大众的视野,之前是谁知道这玩意是什么?但是打字机的效果看起来是真的很不错,一度吸引了很多人的趋之若鹜,当然了这个东西的确挺好用,而且实现很简单,之前我用python的demo讲了一下SSE的概念,看起来有很多人看,但是并没有说明白这个原理,这次再彻底把这个原理给说明白,而且我发现通过node.js 的Express框架来说明这个概念更加简洁,所以今天就用Express框架来说明SSE概念,这样对前端同学更加友好。

之前的Python SSE的文章:

https://blog.csdn.net/wangsenling/article/details/130911465

https://blog.csdn.net/wangsenling/article/details/130490769

回看协议演化历史

学过计算机网络的人都知道socket连接就是全双工的,怎么到http协议这里就变成了单向的了?且连一次就over了,这其实是浏览器编程者故意而为,另外服务端根本不保留会话信息,处理完成直接就把这次请求的相关信息从内存清理了,所以才出现这种单向沟通,后来网络资源越来越便宜,才逐渐的走向了SSE和Websocket

HTTP 协议设计背景

HTTP 是在 1990 年代早期互联网环境中设计出来的,当时网络资源(如带宽和连接数)非常有限。基于这种限制,HTTP 协议被设计为无状态、短连接的模型,以尽可能地节省服务器和客户端的资源。这意味着:

  1. 请求-响应模型:HTTP 是典型的请求-响应协议,客户端发起请求,服务器处理并返回响应,随后连接立即关闭。这样设计的目的是为了快速释放资源,特别是服务器的连接数。

  2. 短连接:早期的 HTTP/1.0 协议默认每个请求完成后都立即关闭连接,这种方式减少了保持大量长连接对服务器造成的负担,但也带来了效率上的问题,特别是在需要频繁通信的情况下。

  3. 无状态:HTTP 的无状态设计使得每个请求都是独立的,服务器不必保留任何会话信息,从而进一步降低了对服务器资源的需求。这在资源稀缺的互联网早期非常重要。

HTTP 的局限性

随着网络应用的复杂性增加,HTTP 的短连接和无状态特性逐渐暴露出一些问题,特别是在需要实时更新双向通信的应用场景中,例如:

  • 实时聊天在线协作工具股票行情更新通知系统等。

  • 在这些场景下,HTTP 的传统请求-响应模型显得过于笨重,因为每次更新都需要客户端主动发起新的请求。

为了弥补这些局限,出现了一些技术,例如:

  • 轮询Polling:客户端定期向服务器发送请求,以获取最新的数据。这种方法虽然可以在一定程度上实现实时更新,但效率较低,因为大量请求可能只会获得很少或没有新数据。

  • 轮询(Long Polling:客户端发送请求后,服务器保持连接打开直到有新数据,然后返回响应。这减少了一些不必要的请求,但仍然需要不断建立新连接,并且是非标准化的。

SSE 的诞生

为了解决 HTTP 请求-响应模型中的不足,SSE 被引入。SSE 是基于 HTTP 协议之上的一种扩展,它允许服务器在一个长时间保持的连接中,不断地向客户端推送事件流。与 WebSocket 相比,SSE 更加轻量,并且完全基于 HTTP 协议,这使它具有很好的兼容性。

  • 长连接保持:SSE 通过建立一个 HTTP 长连接,使得服务器可以在连接保持的状态下,推送多个事件。这样一来,服务器可以在有新数据时立即推送给客户端,而客户端不必频繁发起新的请求。

  • 单向通信:SSE 仅允许服务器向客户端发送数据,这与 WebSocket 的全双工通信形成对比。虽然通信方式有限,但它简化了很多实时更新场景中的开发工作,并且避免了 WebSocket 的复杂性。

  • 兼容 HTTP:由于 SSE 基于 HTTP 协议,因此它能够很好地与现有的 HTTP 基础设施(如代理、防火墙等)配合工作,不容易遇到兼容性问题。

为什么 HTTP 自动关闭连接?

你提到的 HTTP 自动关闭连接是基于早期互联网的设计初衷——节省资源。在那个时代,保持长连接对于资源有限的服务器来说是一个很大的负担:

  • 资源节约:每个连接都占用系统的文件描述符、内存和 CPU 资源。如果每个客户端保持长时间连接,服务器很容易耗尽这些资源。因此,HTTP 通过每次请求结束后立即关闭连接来减少服务器负担。

  • 提高并发能力:通过让连接快速关闭,服务器可以同时处理更多的客户端请求,提高并发能力。

WebSocket 与 HTTP 的不同设计哲学

正如你提到的,WebSocket 是长连接全双工的,它在设计上更类似于低层的 TCP 套接字通信。这使得 WebSocket 更适合需要实时双向通信的应用,比如聊天、在线游戏等。

  • WebSocket 提供的长连接全双工通信:适用于需要双向持续交互的场景,但它不是基于 HTTP 的标准模型,因此需要在协议层上进行升级。

相比之下,HTTP 的设计初衷是基于短连接的请求-响应模型,更适合传统的静态内容传输场景。在现代互联网中,虽然有了更高效的 WebSocket,但 HTTP 的请求-响应模型仍然有其合理性,尤其在静态资源加载、API 请求等方面。

总结

SSE 的出现正是为了解决 HTTP 的局限性,提供一种简单、基于 HTTP 协议的长连接机制,适用于实时更新但无需复杂双向通信的场景。HTTP 早期设计为短连接的原因在于资源有限,自动关闭连接是为了节省资源。而像 WebSocket 这样的长连接协议则适用于实时性和双向通信要求较高的场景,因此两者都有各自的应用场景。

SSE就是HTTP协议下的一个协议补充

Demo效果

 

Gitee 源码地址:

https://gitee.com/sen2020/express-test

我们来看request和response的格式,请求核心就两个,返回核心是三个,不缓存,保持存活,事件流类型,只要发出去的http请求带着这些header参数,那么SSE协议连接就建立了,非常的简单。

 

结合之前的文档,你就能理解了,fetch只要拼凑出来这样header就可以建立SSE连接,不需要什么特殊的处理,几乎任何一个Web框架都支持,接下来用node.js的Express实现一个打字机的小demo

服务端代码

import express from 'express';
import fs from 'fs';
import readline from 'readline';
import { EventEmitter } from 'events';
import path from 'path';
import { fileURLToPath } from 'url';const app = express();
const port = 3000;// 获取当前文件的路径
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);// 创建事件管理器
const eventEmitter = new EventEmitter();// 设置静态文件目录来提供前端页面和资源
app.use(express.static(path.join(__dirname, 'public')));// SSE 路由,根据 ISBN 请求电子书
app.get('/events', (req, res) => {const { isbn } = req.query;if (!isbn) {res.status(400).send('Missing ISBN');return;}// 设置 headers 确保保持长连接res.setHeader('Content-Type', 'text/event-stream');res.setHeader('Cache-Control', 'no-cache');res.setHeader('Connection', 'keep-alive');// 确保每个连接只会被注册一次,避免重复订阅同一事件const onNewData = (data) => {res.write(`data: ${JSON.stringify(data)}\n\n`);};eventEmitter.on(isbn, onNewData);// 当客户端断开时,移除事件监听req.on('close', () => {eventEmitter.removeListener(isbn, onNewData);res.end();});// 开始读取并发送字符流readFileCharacterByCharacter(isbn);
});// 从文件中逐字符读取数据并触发事件
const readFileCharacterByCharacter = async (isbn) => {const filePath = path.join(__dirname, 'books', `${isbn}.txt`);// 如果文件不存在,则返回错误if (!fs.existsSync(filePath)) {eventEmitter.emit(isbn, { message: `Error: Book with ISBN ${isbn} not found.` });return;}const fileStream = fs.createReadStream(filePath);const rl = readline.createInterface({input: fileStream,crlfDelay: Infinity,});for await (const line of rl) {for (const char of line) {await new Promise(resolve => setTimeout(resolve, 50)); // 模拟打字机效果,每50ms发送一个字符eventEmitter.emit(isbn, { message: char });}eventEmitter.emit(isbn, { message: '\n' });}// 告诉前端书本内容已经结束eventEmitter.emit(isbn, { message: 'End of book.' });
};app.listen(port, () => {console.log(`SSE server and static files serving at http://localhost:${port}`);
});

html代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Book Reader</title><link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container"><h1>Book Reader</h1><div class="form-container"><input type="text" id="isbn" value="1234567890" placeholder="Enter ISBN"><button id="start">Start Reading</button></div><div id="content"></div>
</div>
<script src="scripts.js"></script>
</body>
</html>

前端JS代码

document.getElementById('start').addEventListener('click', () => {const isbn = document.getElementById('isbn').value.trim();if (!isbn) {alert('Please enter a valid ISBN');return;}const contentDiv = document.getElementById('content');contentDiv.innerHTML = ''; // 清空之前的内容// 如果已经有一个 EventSource,先关闭它if (window.eventSource) {window.eventSource.close();}// 创建一个新的 EventSource 连接window.eventSource = new EventSource(`/events?isbn=${isbn}`);let currentLine = ''; // 用于累积当前行的字符let paragraph = document.createElement('p'); // 创建一个段落元素contentDiv.appendChild(paragraph); // 初始添加一个段落window.eventSource.onmessage = (event) => {const data = JSON.parse(event.data);const char = data.message;// 检查是否接收到 "End of book." 消息if (char === 'End of book.') {paragraph = document.createElement('p');paragraph.textContent = 'End of book.';contentDiv.appendChild(paragraph);// 关闭 EventSource 连接window.eventSource.close();return; // 停止进一步处理}// 如果是换行符,渲染当前行并创建新的段落if (char === '\n') {paragraph = document.createElement('p'); // 创建新的段落contentDiv.appendChild(paragraph); // 添加新段落到内容区currentLine = ''; // 清空当前行} else {// 累积字符到当前行并更新当前段落内容currentLine += char;paragraph.textContent = currentLine; // 逐字符更新当前段落内容}// 自动滚动到底部contentDiv.scrollTop = contentDiv.scrollHeight;};window.eventSource.onerror = () => {console.log('EventSource connection closed.');window.eventSource.close();};
});

CSS样式表

body {font-family: Arial, sans-serif;background-color: #f4f4f4;margin: 0;padding: 0;display: flex;justify-content: center;align-items: center;height: 100vh;
}.container {text-align: center;background-color: #fff;padding: 20px;border-radius: 8px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}.form-container {margin-bottom: 20px;
}#isbn {padding: 10px;font-size: 16px;width: 200px;margin-right: 10px;
}button {padding: 10px 15px;font-size: 16px;cursor: pointer;
}#content {text-align: left;margin-top: 20px;max-height: 300px;overflow-y: auto;background-color: #f9f9f9;padding: 15px;border: 1px solid #ddd;
}

目录结构

 package.json配置

 

SSE的应用场景注意事项

  1. php这种语言是无法搞这个事情了,因为一个连接需要一个进程,一台主机4核4G,估计也只能建立200个进程就是极限了,再长期保持,就是扯淡,所以你必须用异步IO的Web框架来实现SSE的连接,才可以大大提高连接数。

用SSE做推送-负载均衡场景怎么破?

  1. 首先我们要知道的是,一个Web服务一旦启动,就是一个独立的进程,你想往这个进程中塞东西,要么你能拿到这个进程对外的变量app或者server,这个大概率不可能,因为启动时是不对外提供这种变量的,那怎么办?

    1. 这肯定就是进程间通信的事情了,这里不做深入探讨,无非就是管道,共享内存,队列等等,还有个套接字?套接字是啥?说实话我既懂又不懂

    2. 但是这个进程可以注册到一个端口上,这样外界就可以发消息进来,这也合理,就像人有个耳朵一样,如果一个人聋了,你当然是没办法跟他说话的。

    3. 可以让这个进程自己提供一个http服务,这样大家调用时就通用了,通过这个http接口,我们就可以将信息发送给这个服务里去

  2. 拿来做服务端推送机制,那就是要在服务器级别收集client,如果是负载均衡方式启动了多台服务器怎么办?

    1. 刚好1列表就讲到这个问题,每个独立服务都将自己的IP:端口号存放在redis中,同时也罢自己的收集到的client创建个编码塞到redis里去,例如user_id:client-ip-port 这种对应,这样如果有一个广播需要广播一批人,那么就能从redis中找到这批人,然后再提取他们所在的ip:port,然后把消息发给这些服务,这些服务接收到user_id后,再在自己Map表中找到client,然后一个个发送数据过去,就实现了负载均衡下的消息推送,你学废了吗?

    2. 类似hyperf框架,这种开了多个进程来承接不同的client,玩法是一样,找到user_id:ip:port:process_id,就可以实现同样的广播效果,不过这玩意自己实现起来有点麻烦,我也没玩过,看了下调度就是这样实现的。

Express实现单服务的广播机制代码

import express from 'express';
import fs from 'fs';
import readline from 'readline';
import { EventEmitter } from 'events';
import path from 'path';
import { fileURLToPath } from 'url';const app = express();
const port = 3000;// 获取当前文件的路径
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);// 创建事件管理器
const eventEmitter = new EventEmitter();// 全局连接管理器
let clients = [];// 设置静态文件目录来提供前端页面和资源
app.use(express.static(path.join(__dirname, 'public')));// SSE 路由,客户端连接到服务器
app.get('/events', (req, res) => {const headers = {'Content-Type': 'text/event-stream','Cache-Control': 'no-cache','Connection': 'keep-alive',};res.writeHead(200, headers);// 新的客户端连接,加入全局连接管理器clients.push(res);// 清理客户端断开连接时的处理req.on('close', () => {clients = clients.filter(client => client !== res);res.end();});
});// 广播新书上架通知给所有客户端
const broadcastNewBook = (bookTitle) => {clients.forEach(client => {client.write(`data: ${JSON.stringify({ message: `New book available: ${bookTitle}` })}\n\n`);});
};// 上架新书时调用此函数
app.post('/new-book', express.json(), (req, res) => {const { title } = req.body;if (!title) {res.status(400).send('Book title is required');return;}// 广播新书消息给所有客户端broadcastNewBook(title);res.status(200).send(`New book "${title}" broadcasted to all clients.`);
});// 启动服务器
app.listen(port, () => {console.log(`SSE server running at http://localhost:${port}`);
});

Express中如何一个启动一个服务同时支持http/websocket两种协议?

  1. 这个要想明白一个事情,Express本身分为两个模块,其他Web框架也是这样的,一个是协议处理模块app,另外,也即当http发送过来请求时,浏览器会根据你输入的协议http://ws:// 在request的header中追加一个upgrade:websocket,有了这个标识,被Express的http模块识别到之后,它调用的app,就是ws的模块的app,所有的逻辑都走这边,刚才我们已经了解到http的关闭是由app模块发的close来控制的,只要这边不发close,这个连接就会一直保持着,因为连接就是socket,socket就是全双工的,所以,只要ws模块不主动发close过来,那么这个连接就可以保持长连接,同时ws模块会把这个连接的信息记录下来,以方便后续不断地再接收数据,再发回数据,这就是两个协议可以共用一个端口的机制。

  2. 将两种协议整合在一个Express下有什么好处?他们连接对象都是在一个进程中,因为启动时就启动了一个进程,这个进程启动了http模块和ws模块,所以两者共用一个一套上下文,因此如果你在Express启动前创建一个clients = [],将连接过来的所有ws client都塞进去,那就意味着?

    1. 意味着你可以通过发送http请求给Express服务,来实现广播效果,是不是爽歪歪?

 

效果截图

服务端代码:

import express from 'express';
import { WebSocketServer } from 'ws';
import path from 'path';
import { fileURLToPath } from 'url';const app = express();
const port = 3000;// 使用 JSON 中间件解析 POST 请求体
app.use(express.json());// 获取当前文件的路径
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);// 配置静态文件目录
app.use(express.static(path.join(__dirname, 'public/ws')));app.use((req, res, next) => {res.setHeader("Content-Security-Policy", "connect-src 'self' http://localhost:3000");next();
});// 创建 WebSocket 服务器并与 Express 集成
const wss = new WebSocketServer({ noServer: true });// 保存所有 WebSocket 客户端
let clients = [];// WebSocket 连接处理
wss.on('connection', (ws) => {console.log('Client connected');clients.push(ws);// 处理消息ws.on('message', (message) => {console.log(`Received message: ${message}`);});// 当客户端断开连接时,移除它ws.on('close', () => {clients = clients.filter(client => client !== ws);console.log('Client disconnected');});
});// 广播消息给所有 WebSocket 客户端
const broadcastMessage = (message) => {clients.forEach(client => {if (client.readyState === client.OPEN) {client.send(message);}});
};// 上架新书的 HTTP 接口
app.post('/new-book', (req, res) => {const { title } = req.body;if (!title) {return res.status(400).send('Book title is required');}const message = `New book available: ${title}`;broadcastMessage(message);res.status(200).send(`New book "${title}" has been broadcasted to all WebSocket clients.`);
});// 处理升级请求,WebSocket 连接时需要的处理逻辑
app.server = app.listen(port, () => {console.log(`Server is running on port ${port}`);
});app.server.on('upgrade', (request, socket, head) => {wss.handleUpgrade(request, socket, head, (ws) => {wss.emit('connection', ws, request);});
});

Html 代码

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>WebSocket Client</title>
</head>
<body>
<h1>WebSocket Book Notification</h1>
<div id="content"></div><script>const contentDiv = document.getElementById('content');// 创建 WebSocket 连接const ws = new WebSocket('ws://localhost:3000');// 处理接收到的消息ws.onmessage = (event) => {const paragraph = document.createElement('p');paragraph.textContent = `Received: ${event.data}`;contentDiv.appendChild(paragraph);};// 处理 WebSocket 连接打开ws.onopen = () => {console.log('WebSocket connected');};// 处理 WebSocket 连接关闭ws.onclose = () => {console.log('WebSocket disconnected');};
</script>
</body>
</html>

js发送广播的请求fetch

fetch('http://localhost:3000/new-book', {method: 'POST',headers: {'Content-Type': 'application/json'},body: JSON.stringify({title: 'The Great Adventure WSSSSSSSS'})
})
.then(response => response.text())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

相关文章:

http/sse/websocket 三大协议演化历史以及 sse协议下 node.js express 服务实现打字机案例 负载均衡下的广播实现机制

背景 自从2022年底chatgpt上线后&#xff0c;sse就进入了大众的视野&#xff0c;之前是谁知道这玩意是什么&#xff1f;但是打字机的效果看起来是真的很不错&#xff0c;一度吸引了很多人的趋之若鹜&#xff0c;当然了这个东西的确挺好用&#xff0c;而且实现很简单&#xff0…...

智能时代新宠:2024年录音转文字软件

无论是学生群体记录课堂笔记&#xff0c;职场人士整理会议纪要&#xff0c;还是自媒体创作者捕捉灵感火花&#xff0c;录音转文字软件都以其独特的便利性和高效性赢得了广泛的好评。今天&#xff0c;就让我们一起探索那些深受大家喜爱的录音转文字工具吧。 1.365在线转文字 链…...

【Python机器学习】树回归——使用Python的tkinter库创建GUI

机器学习给我们提供了一些强大的工具&#xff0c;能从未知数据中抽取出有用的信息。因此&#xff0c;能否这些信息以易于人们理解的方式呈现十分重要。如果人们可以直接与算法和数据交互&#xff0c;将可以比较轻松的进行解释。其中一个能够同时支持数据呈现和用户交互的方式就…...

谷歌浏览器网页底图设置为全黑

输入网址&#xff1a;chrome://flags/ 搜索dark&#xff0c;选择Enabled&#xff0c;重启浏览器即可...

Unity | AmplifyShaderEditor插件基础(第二集:模版说明)

目录 一、前言 二、核心模版和URP模版 1.区别介绍 2.自己的模版 三、输出节点 1.界面 2.打开OutPut 3.ShderType 4.ShaderName 5.Shader大块内容 6.修改内容 四、预告 一、前言 内容全部基于以下链接基础以上讲的。 Unity | Shader基础知识&#xff08;什么是shader…...

【Linux入门】Linux常见指令

目录 前言 一、Linux基本指令 1.ls指令 2.pwd命令 3.cd 指令 4.touch指令 5.mkdir指令 6.rmdir指令 && rm 指令 7.man指令 8.cp指令 9.mv指令 10.cat 11.date 12.top 13.shutdown-关机 14.重要的几个热键 二、Linux扩展指令 总结 前言 Linux指令是在…...

startData

某音startData 记得加入学习群&#xff1a; python爬虫&js逆向3 714283180...

CV每日论文--2024.7.24

1 、AutoAD-Zero: A Training-Free Framework for Zero-Shot Audio Description 中文标题&#xff1a;T2V-CompBench&#xff1a;组合文本到视频生成的综合基准AutoAD-Zero&#xff1a;零样本音频描述的免训练框架 简介&#xff1a;我们的目标是以无需训练的方式为电影和电视剧…...

大语言模型的简易可扩展增量预训练策略

前言 原论文&#xff1a;Simple and Scalable Strategies to Continually Pre-train Large Language Models翻译文件已整理至Github项目Some-Paper-CN&#xff0c;欢迎大家Star&#xff01; 摘要 大语言模型&#xff08;LLMs&#xff09;通常需要在数十亿个tokens上进行预训…...

python学习之异常

在编程中&#xff0c;异常是指程序运行时发生的错误或异常情况&#xff0c;它们可能会打断程序的正常流程。不同的编程语言定义了自己的一套异常类型。在Python中&#xff0c;异常是基于类和对象的&#xff0c;所有的异常都继承自内置的BaseException类。 以下是Python中一些常…...

多张图像实现全景无痕拼接操作

目录 ​编辑 1&#xff0c;图像拼接的作用 2&#xff0c;实现步骤 3&#xff0c;效果展示 1&#xff0c;图像拼接的作用 视觉扩展&#xff1a;通过拼接&#xff0c;可以将多个视角的图像合并&#xff0c;创造出比单张图片更广阔的视野。 数据整合&#xff1a;在科学研究和地…...

在阿里云ecs上构建一个WordPress博客网站

1、购买ECS 使用抢占式实例&#xff0c;RDS 使用按量付费 2、在安全组的出入方向添加80端口 3、购买一个公网IP绑定该ecs 4、云数据库rds选择按量付费 5、创建一个名为test_user的普通账号 6、创建数据库 7、设置RDS实例白名单 8、远程登录ecs实例 9、安装apache服务及其扩展包…...

安卓应用开发学习:查看手机传感器信息

一、引言 在手机app的开发中经常会用到手机的传感器&#xff0c;在《Android App 开发进阶与项目实战》一书的第10章就介绍了传感器的一些功能和用法。要想使用传感器&#xff0c;首先得知道手机具备哪些传感器。书中有传感器类型取值的说明&#xff0c;并提供了一个查看手机传…...

C语言字符串缺陷

目录 补缺&#xff1a; 正题开始&#xff1a; 思考&#xff1a; 解决方案&#xff1a; 1.string类 2.redis库 简介&#xff1a; 对于处理字符串的好处&#xff1a; 下期预告&#xff1a;内容待定 补缺&#xff1a; 在上期内容的结尾我留下了一个问题&#xff0c;这个问…...

分布式场景中的常见的技术问题及解决,如分布式锁、分布式事务、分布式 session、分布式任务调度

目录 一、分布式锁 二、分布式事务 三、分布式Session 四、分布式任务调度 在分布式场景中&#xff0c;常见的技术问题及其解决方案涉及多个方面&#xff0c;包括分布式锁、分布式事务、分布式session和分布式任务调度。以下是对这些问题的详细探讨&#xff1a; 一、分布式…...

Android笔试面试题AI答之Kotlin(9)

文章目录 39.Kotlin中List与MutableList的区别&#xff1f;ListMutableList使用场景示例 40. Kotlin中实现单例的几种常见方式&#xff1f;1. 懒汉式&#xff08;线程不安全&#xff09;2. 懒汉式&#xff08;线程安全&#xff09;3. 饿汉式4. 双重校验锁&#xff08;DCL, Doub…...

C# 不一样的洗牌算法---Simd指令

洗牌算法&#xff0c;以随机打乱数组中元素的位置 测试数据创建 int[] _data; Random rng new Random(); protected override void CreateData() {_data new int[_size];for (int i 0; i < _data.Length; i){_data[i] i;} } 普通打乱数组元素位置 protected overrid…...

LVGL系列3--纯物理(外部)按键,数字键盘控制控件

LVGL系列 一、LVGL移植 LVGL系列1–AT32移植LVGL_V8具体步骤 LVGL系列2–linux lvglv8 vscode 移植 LVGL系列3–纯物理(外部)按键&#xff0c;数字键盘控制控件 文章目录 LVGL系列一、LVGL移植 一、背景方式一&#xff1a;自定义事件发送与处理函数方式二&#xff1a;利用l…...

FPGA开发——UART回环实现之接收模块的设计

一、简介 因为我们本次进行串口回环的实验的对象是FPGA开发板和PC端&#xff0c;所以在接收和发送模块中先编写接收模块&#xff0c;这样可以在后面更好的进行发送模块的验证。&#xff08;其实这里先编写哪个模块&#xff09;都不影响&#xff0c;这里看自己心情&#xff0c;反…...

Debezium系列之:记录一次SQLServer数据库数据不采集,恢复采集造成下游承压的情况,以及相对应的详细解决方案

Debezium系列之:记录一次SQLServer数据库数据不采集,恢复采集造成下游承压的情况,以及相对应的详细解决方案 一、背景二、查看CDC表情况三、 排查数据库是否开启代理四、排查表是否开启CDC五、下游承压情况六、解决方案一、背景 Connector状态正常,但几十台SQLServer数据库…...

Linux线程基础学习记录

0.线程特点 &#xff08;1&#xff09;.线程共享资源&#xff1a;一个进程下的多个线程共享以下资源 可执行的指令 静态数据 进程中打开的文件描述符 当前工作目录 用户ID 用户组ID &#xff08;2&#xff09;.线程私有资源&#xff1a; 线程ID PC(程序计数器&#xff09;和相…...

【Python学习-UI界面】PyQt5 小部件12-QStackedWidget 多页显示

功能和 QTabWidget 类似&#xff0c;它也有助于高效利用窗口的客户区域。 QStackedWidget 提供了一个窗口堆栈&#xff0c;每次只能查看一个窗口。它是建立在 QStackedLayout 之上的一个有用的布局。 样式如下: 右键可以变型为QTabWidget...

Mybatis中好用的元对象反射工具类 - MetaObject

一、前言 在获取map对象或者是其他深层嵌套对象&#xff0c;如果你的做法是挨个取出判空然后继续再向下查找&#xff0c;那么可以看看本文的方案&#xff0c;它或许能让你打开新的思路。 作为一名java开发人员&#xff0c;Mybatis几乎是我们无法避开的ORM框架&#xff0c;如果你…...

javaEE WebServlet、SpringWebMVC、SpringBoot实现跨域访问的4种方式及优先级,nginx配置跨域

文章目录 1. 前置知识2. 原理和解决方案总结2.1. 跨域不通过原理流程图2.2. 实现原理&#xff1a;添加以下http响应头2.3. 四种跨域实现方式及优先级&#xff08;从高到低&#xff09; 3. 具体实现代码3.1. 跨域全局配置方式-Filter(全适用)3.2. 跨域全局配置方式-SpringMvc3.3…...

深入理解JavaScript性能优化:从基础到高级

引言 在当今快速发展的Web世界中,性能已经成为衡量应用质量的关键指标。随着Web应用复杂度的不断提升,JavaScript作为前端开发的核心语言,其性能优化变得尤为重要。本文旨在全面深入地探讨JavaScript性能优化的各个方面,从基础概念到高级技巧,帮助开发者构建高效、流畅的Web应用…...

java+springboot实现定时任务

由于是初级程序员&#xff0c;基于注解的形式实现了一个简单的定时任务&#xff1b; 1. 使用Scheduled注解 Spring的Scheduled注解是一种非常简单和便捷的实现定时任务的方式。通过在方法上添加Scheduled注解&#xff0c;我们可以指定方法在特定的时间间隔或固定的时间点执行…...

1.3 数据库的发展历史与演变

欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;欢迎订阅相关专栏&#xff1a; 工&#x1f497;重&#x1f497;hao&#x1f497;&#xff1a;野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典&#xff1a;收集整理全网各大IT互联网公司技术、项目、HR面试真题.…...

The field file exceeds its maximum permitted size of 1048576 bytes

场景: 再系统后台上传解析对账文件时大小超过1M就会报错 分析: 排查错误时了解MultipartFile默认上传大小就是1M,但是发现项目配置文件配置了上传大小100M,但是这个大小没有生效 因为项目启动并没有使用到这个配置大小并把他应用到file配置里面,经过测试发现只需要增加配置…...

【Es】python es操作

表 因为es是集群所以es_hosts是列表 from elasticsearch import Elasticsearch ES_HOSTS ["127.0.0.1:9200"] ES_HTTP_AUTH "******************"# 连接Es es Elasticsearch(hostsES_HOSTS ,http_authES_HTTP_AUTH ,maxsize60,timeout30,max_retries3…...

吃透前端文件上传与文件相关操作 多文件上传 大文件切片上传 拖拽上传 后续还会更新 断点续传等等

最近在学文件上传的操作,所以想把学习到东西写成一文章 这片文章是我以小白视角 慢慢学习并熟悉前端文件相关操作的流程总结出来的 前端文件上传 我首先想到是 <input type"file">**选择文件**</input>如果我们想限制上传文件的格式,大小或进行裁剪分片…...