操作系统如何高效处理网络请求:IO多路复用技术

在处理大量请求时,各个引擎都会采用线程池的方法,并发处理这些请求,但当一万个请求来的时候,我们要创建一万个线程来处理吗,很显然不会,那假如我创建一千个线程,那一线程该如何处理这个十个请求呢?IO多路复用技术就是来解决一个线程处理多个请求的问题的。

首先,IO多路复用技术是由各个引擎通过C++代码调用操作系统提供的特定api来实现,而特定的api大致有三个,分别为select,poll,epoll,通过这三个api,也实现了三种不同的IO多路复用技术,其中select发明最早,性能最差,而发明最晚,性能最好的是epoll。

系统调用知识前提

多路复用技术涉及大量的系统调用,其中三者都需要使用的系统调用,具体使用和作用如下

// socket()系统调用
// 参数:
// domain:协议族(如AF_INET表示IPv4)。
// type:socket类型(如SOCK_STREAM表示TCP)。
// protocol:协议(通常为0,由系统选择适当的协议)。
// 返回值:成功时返回该socket的文件描述符,失败时返回-1
// 作用:创建一个socket服务来监听端口
int socket(int domain, int type, int protocol);// bind()系统调用
// 参数:
// sockfd:socket的文件描述符。
// addr:本地地址。
// addrlen:地址长度。
// 返回值:成功时返回0,失败时返回-1
// 作用:将socket绑定到一个本地地址(IP地址和端口)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);// listen()系统调用
// 参数:
// sockfd:socket的文件描述符。
// backlog:挂起连接队列的最大长度。
// 返回值:成功时返回0,失败时返回-1
// 作用:将socket设置为被动模式,准备接受连接请求
int listen(int sockfd, int backlog);// accept()系统调用
// 参数:
// sockfd:监听socket的文件描述符。
// addr:指向客户端地址结构的指针。
// addrlen:地址结构的长度指针。
// 返回值:成功时返回新的socket文件描述符,失败时返回-1
// 作用:当主socket监听到一个请求时,创建一个新的socket,并通过当前
//      系统调用,将新的socket和客户端建立链接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);// connect()系统调用
// 参数:
// sockfd:socket文件描述符。
// addr:服务器地址。
// addrlen:地址长度。
// 返回值:成功时返回0,失败时返回-1
// 作用:作用了accept类似,不过是发送网络请求时
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);// read()系统调用
// 参数:
// fd:文件描述符。
// buf:接收数据的缓冲区指针。
// count:缓冲区的长度。
// 返回值:成功时返回读取的字节数,失败时返回-1
// 作用:从文件描述符读取数据
ssize_t read(int fd, void *buf, size_t count);// write()系统调用
// 参数:
// fd:文件描述符。
// buf:发送数据的缓冲区指针。
// count:缓冲区的长度。
// 返回值:成功时返回写入的字节数,失败时返回-1
// 作用:向文件描述符写入数据
ssize_t write(int fd, const void *buf, size_t count);
// recv()系统调用
// 参数:
// sockfd:socket文件描述符。
// buf:接收数据的缓冲区指针。
// len:缓冲区的长度。
// flags:接收标志。
// 返回值:成功时返回读取的字节数,失败时返回-1
// 作用:从socket接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);// send()系统调用
// 参数:
// sockfd:socket文件描述符。
// buf:发送数据的缓冲区指针。
// len:缓冲区的长度。
// flags:发送标志。
// 返回值:成功时返回发送的字节数,失败时返回-1
// 作用:向socket发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);// close() 系统调用
// 参数:
// fd:文件描述符。
// 返回值:成功时返回 0,失败时返回 -1
int close(int fd);

而三者分别特有的系统调用如下

//===================selectIO多路复用技术特有的系统调用===================
// select()系统调用
// 参数:
// nfds:监视的文件描述符的范围(最大文件描述符加一)。
// readfds:指向一组文件描述符集合,这些描述符将被监视是否可读。
// writefds:指向一组文件描述符集合,这些描述符将被监视是否可写。
// exceptfds:指向一组文件描述符集合,这些描述符将被监视是否有异常。
// timeout:指定select等待的最大时间。如果为NULL,select将无限等待。
// 返回值:成功时返回就绪文件描述符的数量,超时时返回0,失败时返回-1
// 作用:监视多个文件描述符,等待它们变为可读、可写或发生异常
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);//===================selectIO多路复用技术特有的宏操作===================
// FD_ZERO() 宏
// 参数:
// set:指向要清空的文件描述符集合。
// 作用:清空文件描述符集合
void FD_ZERO(fd_set *set);// FD_SET() 宏
// 参数:
// fd:要添加的文件描述符。
// set:指向文件描述符集合。
// 作用:将文件描述符添加到集合中
void FD_SET(int fd, fd_set *set);// FD_CLR() 宏
// 参数:
// fd:要从集合中删除的文件描述符。
// set:指向文件描述符集合。
// 作用:从集合中删除文件描述符
void FD_CLR(int fd, fd_set *set);// FD_ISSET() 宏
// 参数:
// fd:要检查的文件描述符。
// set:指向文件描述符集合。
// 返回值:如果文件描述符在集合中则返回非零值,否则返回零。
// 作用:检查文件描述符是否在集合中
int FD_ISSET(int fd, fd_set *set);//===================pollIO多路复用技术特有的系统调用===================
// poll()系统调用
// 参数:
// fds:指向一个pollfd结构数组。
// nfds:数组中文件描述符的数量。
// timeout:等待的最大时间(毫秒)。负值表示无限等待。
// 返回值:成功时返回就绪文件描述符的数量,超时时返回0,失败时返回-1
// 作用:类似于select,但使用不同的数据结构,扩展性更好
int poll(struct pollfd *fds, nfds_t nfds, int timeout);//===================epollIO多路复用技术特有的系统调用===================
// epoll_create()系统调用
// 参数:
// size:建议的监听的文件描述符数量(通常被忽略)。
// 返回值:成功时返回新的epoll实例的文件描述符,失败时返回-1
// 作用:创建一个新的epoll实例
int epoll_create(int size);// epoll_create1() 系统调用
// 参数:
// flags:epoll 实例的创建标志(如 EPOLL_CLOEXEC)。
// 返回值:成功时返回新的 epoll 实例的文件描述符,失败时返回 -1
// 作用:创建一个新的 epoll 实例,可以指定标志
int epoll_create1(int flags);// epoll_ctl()系统调用
// 参数:
// epfd:epoll实例的文件描述符。
// op:操作类型(如EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL)。
// fd:要监视的文件描述符。
// event:指向epoll事件结构的指针。
// 返回值:成功时返回0,失败时返回-1
// 作用:控制epoll实例,注册、修改或删除监视的文件描述符
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);// epoll_wait()系统调用
// 参数:
// epfd:epoll实例的文件描述符。
// events:指向epoll事件结构数组的指针。
// maxevents:数组中事件的最大数量。
// timeout:等待的最大时间(毫秒)。负值表示无限等待。
// 返回值:成功时返回发生事件的文件描述符数量,失败时返回-1
// 作用:等待epoll实例中的文件描述符发生事件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

了解了系统调用后,接下来便可以具体的学习三者的原理和代码实现了

selectIO多路复用技术:

这里我们只演示一个简单的select的用例,只接收客户端传来的数据,不进行返回。

首先,我们需要创建一个socket来监听请求,当客户端发送请求被我们接受到时,我们不能用当前的socket来进行连接,因为我们不止处理这一个连接,所以我们要调用socket系统调用创建一个新的socket来和客户端的socket来建立连接,如下图

由于我们并不是一个线程处理一个请求,所以我们并不能对于请求资源的准备进行等待,所以我们要想办法来监听哪个socket的资源已经接收到到了,我们就把线程资源给谁,select的解决办法是定义了一个1024位的结构,用来保存socket的缓冲区(所谓的缓冲区实际上就是socket在他的源码中定义的一些变量,分配的内存,用户态的缓冲区也一样,就是我们自己写程序时定义的变量分配的内存)的文件描述符信息,其中文件描述符是非负整数,如果是几,就将第几位变成1,这是缺陷之一,由于设计较早,最高只能保存1024,这在如今是完全不够用的,那么什么是文件描述符信息呢?

文件描述符信息是一个进程当前使用的资源(计算机的内存和硬盘都叫资源)的标识符,是一个非负整数。

客户端传入的资源通过网卡复制进socket的缓冲区,而这个结构就是保存这个缓存的的文件描述符,通过select的系统调用,监听文件表示符变为可读(就是缓冲区内已经有数据了),而文件表示符变为可读的过程就是内核去监控这块缓存区,等待其中存在内容(不一定是所有内容,有一点就传一点),就将文件表示符变为可读,select返回大于0的值(0表示超时,小于0表示错误)。然后就调用recv系统调用,来将客户端传来的数据,存入我们的缓冲区(变量)。

从图中我们可以看到,本地socket的缓存区也被保存的fd_set中,这是因为当有新的连接被socket监听到时,他会保存到他的缓存区中,所以我们也需要监控他的缓冲区以处理新的连接请求到来。

以下是简单的代码实现,逻辑不够严谨,理解过程就好:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>#define PORT 8080
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024int main() {int server_fd, new_socket, client_socket[MAX_CLIENTS], max_sd, sd, activity, valread;struct sockaddr_in address;fd_set readfds;int addrlen = sizeof(address);char buffer[BUFFER_SIZE];// 初始化所有客户端socket为 0 (表示空闲)for (int i = 0; i < MAX_CLIENTS; i++) {client_socket[i] = 0;}// 创建服务器socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置服务器地址address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 绑定服务器socketif (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听服务器socketif (listen(server_fd, 3) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}std::cout << "Listening on port " << PORT << std::endl;while (true) {// 清空文件描述符集合FD_ZERO(&readfds);// 将服务器socket添加到集合中FD_SET(server_fd, &readfds);max_sd = server_fd;// 添加客户端socket到集合中for (int i = 0; i < MAX_CLIENTS; i++) {sd = client_socket[i];if (sd > 0) FD_SET(sd, &readfds);if (sd > max_sd) max_sd = sd;}// 使用 select 监视文件描述符集合activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);if ((activity < 0) && (errno != EINTR)) {std::cerr << "select error" << std::endl;}// 处理新的连接if (FD_ISSET(server_fd, &readfds)) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}std::cout << "New connection, socket fd is " << new_socket << std::endl;// 检查客户端数组是否有空位bool added = false;for (int i = 0; i < MAX_CLIENTS; i++) {if (client_socket[i] == 0) {client_socket[i] = new_socket;std::cout << "Adding to list of sockets as " << i << std::endl;added = true;break;}}// 如果没有空位,拒绝新的连接if (!added) {std::cerr << "Too many connections, rejecting new connection from "<< inet_ntoa(address.sin_addr) << ":" << ntohs(address.sin_port) << std::endl;close(new_socket);}}// 处理现有连接的数据for (int i = 0; i < MAX_CLIENTS; i++) {sd = client_socket[i];if (FD_ISSET(sd, &readfds)) {// 确保接收到完整的数据bool connection_closed = false;std::string total_data;do {valread = recv(sd, buffer, BUFFER_SIZE, 0);if (valread > 0) {total_data.append(buffer, valread);} else if (valread == 0) {// 对端关闭连接connection_closed = true;} else {perror("recv");close(sd);client_socket[i] = 0;break;}} while (valread > 0);}}}close(server_fd);return 0;
}

select缺陷很多,比如他的保存上线是1024,这在如今是远远不够的,他每次都要将结构清零,重新保存,这点也是性能损耗之一

pollIO多路复用技术:

他和select大致类似,但细节不同,他并不是简单保存文件描述符,而是保存了一个结构体如下:

struct pollfd {int fd;         // 文件描述符short events;   // 要监视的事件short revents;  // 返回的事件
};

poll系统调用也不再检测文件是否可读,而是检查revents,当其为POLLIN时,则证明可读

其中events的是指监听的是读还是写操作等,这说明poll不仅可以监听是否可读,还能监听是否可写

而poll也不是用1024位的结构记录文件描述符,而使用pollfd数组的方式,这样也没有了最大限制,其他的流程大致和select一样,这里就不画图了,实现代码如下:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <poll.h>
#include <vector>#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_CLIENTS 10int main() {int server_fd, new_socket, valread;struct sockaddr_in address;int addrlen = sizeof(address);char buffer[BUFFER_SIZE];// 创建服务器套接字if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置服务器地址address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 绑定服务器套接字if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听服务器套接字if (listen(server_fd, 3) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}std::cout << "Listening on port " << PORT << std::endl;// 创建 pollfd 结构数组std::vector<pollfd> fds;pollfd server_pollfd = {server_fd, POLLIN, 0};fds.push_back(server_pollfd);while (true) {// 调用 poll 系统调用int activity = poll(fds.data(), fds.size(), -1);if (activity < 0) {perror("poll error");break;}// 检查服务器套接字是否有新连接if (fds[0].revents == POLLIN) {if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {perror("accept");exit(EXIT_FAILURE);}std::cout << "New connection, socket fd is " << new_socket << std::endl;// 添加新连接到 pollfd 结构数组pollfd client_pollfd = {new_socket, POLLIN, 0};fds.push_back(client_pollfd);}// 检查现有连接是否有数据for (size_t i = 1; i < fds.size(); i++) {if (fds[i].revents == POLLIN) {valread = read(fds[i].fd, buffer, BUFFER_SIZE);if (valread == 0) {// 客户端断开连接std::cout << "Client disconnected, socket fd is " << fds[i].fd << std::endl;close(fds[i].fd);fds.erase(fds.begin() + i);i--; // 修正索引} else {buffer[valread] = '\0';std::cout << "Received: " << buffer << std::endl;}}}}close(server_fd);return 0;
}

epollIO多路复用技术:

epoll 是 Linux 2.6 版本内核提供的一种 I/O 事件通知机制,相比 select 和 poll,epoll 更加高效,特别适用于处理大量文件描述符的场景。epoll 提供了较高的性能,因为它在内核中使用了更为复杂的数据结构和算法,以减少在处理大量文件描述符时的开销。

epoll通过epoll_create系统调用在内核中创建一个eventpoll的结构,其中有三部分,分别是edyList(已就绪事件),rbr(未就绪事件的红黑树,通过eqoll_ctl系统调用推入信息),wq(保存等待已就绪事件的进程)。当rbr内保存的文件描述符有数据时,该结构体会从红黑树转移至已就绪队列,并且唤醒wq中的进程。如下图

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <vector>#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_EVENTS 10int main() {int server_fd, new_socket, valread;struct sockaddr_in address;int addrlen = sizeof(address);char buffer[BUFFER_SIZE];// 创建服务器socketif ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {perror("socket failed");exit(EXIT_FAILURE);}// 设置服务器地址address.sin_family = AF_INET;address.sin_addr.s_addr = INADDR_ANY;address.sin_port = htons(PORT);// 绑定服务器socketif (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {perror("bind failed");close(server_fd);exit(EXIT_FAILURE);}// 监听服务器socketif (listen(server_fd, 3) < 0) {perror("listen");close(server_fd);exit(EXIT_FAILURE);}std::cout << "Listening on port " << PORT << std::endl;// 创建 epoll 实例int epoll_fd = epoll_create(MAX_EVENTS);if (epoll_fd == -1) {perror("epoll_create");exit(EXIT_FAILURE);}// 添加服务器socket到 epoll 实例struct epoll_event event;event.events = EPOLLIN;event.data.fd = server_fd;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {perror("epoll_ctl");exit(EXIT_FAILURE);}std::vector<epoll_event> events(MAX_EVENTS);while (true) {int num_fds = epoll_wait(epoll_fd, events.data(), MAX_EVENTS, -1);if (num_fds == -1) {perror("epoll_wait");break;}for (int i = 0; i < num_fds; ++i) {if (events[i].data.fd == server_fd) {// 处理新的连接new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);if (new_socket == -1) {perror("accept");continue;}std::cout << "New connection, socket fd is " << new_socket << std::endl;// 添加新连接到 epoll 实例event.events = EPOLLIN;event.data.fd = new_socket;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket, &event) == -1) {perror("epoll_ctl");close(new_socket);}} else {// 处理现有连接的数据valread = read(events[i].data.fd, buffer, BUFFER_SIZE);if (valread == 0) {// 客户端断开连接std::cout << "Client disconnected, socket fd is " << events[i].data.fd << std::endl;close(events[i].data.fd);} else {buffer[valread] = '\0';std::cout << "Received: " << buffer << std::endl;send(events[i].data.fd, buffer, valread, 0);}}}}close(server_fd);close(epoll_fd);return 0;
}

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

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

相关文章

3GPP R18 Multi-USIM是怎么回事?(四)

前几篇主要是MUSIM feature NAS 部分内容的总结,这篇开始看RRC部分相关的内容,由于RRC部分内容过长,也分成了2篇。这篇就着重看下musim gap以及RRC触发UE离开RRC Connected mode相关的内容,直入正题, 上面的内容在overview中有提到,对应的是如下38.300中的描述。 处于网络…

Python -numpy 基础-------1

NumPy&#xff08;Numerical Python&#xff09;是Python的一个开源数值计算扩展库。它支持大量的维度数组与矩阵运算&#xff0c;此外也针对数组运算提供大量的数学函数库。NumPy的数组&#xff08;ndarray&#xff09;对象是一个快速且灵活的多维数组对象&#xff0c;用于存储…

黑龙江等保测评最新资讯:强化安全基线,赋能数字未来

在黑龙江省&#xff0c;随着数字化转型的不断深化&#xff0c;企业对其信息安全的关注也越来越高&#xff0c;而作为保护信息资产的一个重要环节的等保测评&#xff0c;也面临着新的机遇和挑战。 最新政策动向 最近&#xff0c;有关部门下发了《关于加强网络安全等级保护的指导…

基于3D开发引擎HOOPS平台的大型三维PLM系统的设计、开发与应用

产品生命周期管理&#xff08;Product Lifecycle Management&#xff0c;PLM&#xff09;系统在现代制造业中扮演着至关重要的角色。随着工业4.0和智能制造的推进&#xff0c;PLM系统从最初的CAD和PDM系统发展到现在的全面集成、协作和智能化的平台。本文将探讨基于HOOPS平台的…

【python】Numpy运行报错分析:IndexError与形状不匹配问题

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

森林防火,森林防火智能储水罐_鼎跃安全

森林防火是保护森林的重要措施&#xff0c;每年发生的森林火灾都严重威胁着自然安全&#xff0c;对社会经济和生态造成严重的破坏。为了切实有效地预防并扑灭森林火灾&#xff0c;森林防火智能储水罐已成为现代森林防火体系中的重要装备。 储水罐内置传感器和控制系统&#xff…

【CTFWP】ctfshow-web32

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 题目介绍&#xff1a;题目分析&#xff1a;payload&#xff1a;payload解释&#xff1a;flag 题目介绍&#xff1a; <?php/* # -*- coding: utf-8 -*- # Autho…

【每日刷题Day85】

【每日刷题Day85】 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 125. 验证回文串 - 力扣&#xff08;LeetCode&#xff09; 2. 43. 字符串相乘 - 力扣&#xff08;L…

DC系列靶场---DC 2靶场的渗透测试(二)

漏洞利用及探测 rbash逃逸 虽然我们现在已经可以执行切换路径命令了&#xff0c;但是发现还有是很多命令不能用。 我想看看一下目标主机的所有用户&#xff0c;是不能执行的。 那我们就用到了当前shell逃逸。第一种情况&#xff1a;/ 被允许的情况下&#xff1b;直接 /bin/s…

SpringBoot原理解析(二)- Spring Bean的生命周期以及后处理器和回调接口

SpringBoot原理解析&#xff08;二&#xff09;- Spring Bean的生命周期以及后处理器和回调接口 文章目录 SpringBoot原理解析&#xff08;二&#xff09;- Spring Bean的生命周期以及后处理器和回调接口1.Bean的实例化阶段1.1.Bean 实例化的基本流程1.2.Bean 实例化图例1.3.实…

go 协程池的实现

使用场景 这次需求是做一个临时的数据采集功能&#xff0c;为了将积压的数据快速的消耗完&#xff0c;但是单一的脚本消耗的太慢&#xff0c;于是乎就手写了一个简单的协程池&#xff1a; 为了能加快数据的收集速度为了稳定协程的数量&#xff0c;让脚本变得稳定 设计图如下…

微服务分布式事务

1、分布式事务是什么&#xff1f; 微服务架构中的分布式事务是指在多个服务实例之间保持数据一致性的机制。由于微服务通常涉及将业务逻辑拆分成独立的服务&#xff0c;每个服务可能有自己的数据库&#xff0c;因此当一个业务操作需要跨多个服务进行时&#xff0c;确保所有服务…

sbti科学碳目标倡议是什么

在科学界、工业界以及全球政策制定者的共同努力下&#xff0c;一个名为“科学碳目标倡议”&#xff08;Science Based Targets initiative&#xff0c;简称SBTi&#xff09;的全球性合作平台应运而生。这一倡议旨在推动企业和组织设定符合气候科学要求的减排目标&#xff0c;以…

问题记录-SpringBoot 2.7.2 整合 Swagger 报错

详细报错如下 报错背景&#xff0c;我将springboot从2.3.3升级到了2.7.2&#xff0c;报了下面的错误&#xff1a; org.springframework.context.ApplicationContextException: Failed to start bean documentationPluginsBootstrapper; nested exception is java.lang.NullPo…

信息收集Part3-资产监控

Github监控 便于收集整理最新exp或poc 便于发现相关测试目标的资产 各种子域名查询 DNS,备案&#xff0c;证书 全球节点请求cdn 枚举爆破或解析子域名对应 便于发现管理员相关的注册信息 通过Server酱接口接收漏洞信息 https://sct.ftqq.com/ https://github.com/easych…

2024.7.23(DNS正向解析)

回顾&#xff1a; # 安装 samba yum -y install samba # 自建库&#xff0c;只下载&#xff0c;不安装 yum -y install --downloadonly --downloaddir./soft/ # 配置samba vim /etc/samba/smb.conf # 配置 [xxxxxxxname] commentdasdffsffdslfdjsa path/share …

h5点击电话号跳转手机拨号

需要使用到h5的 <a>标签 我们首先在<head>标签中添加代码 <meta name"format-detection" content"telephoneyes"/>然后再想要的位置添加代码 <a href"tel:10086"> 点击拨打&#xff1a;10086 </a> 这样功能就实现…

系统架构设计师教程 第4章 信息安全技术基础知识-4.3 信息安全系统的组成框架4.4 信息加解密技术-解读

系统架构设计师教程 第4章 信息安全技术基础知识-4.3 信息安全系统的组成框架 4.3 信息安全系统的组成框架4.3.1 技术体系4.3.1.1 基础安全设备4.3.1.2 计算机网络安全4.3.1.3 操作系统安全4.3.1.4 数据库安全4.3.1.5 终端安全设备4.3.2 组织机构体系4.3.3 管理体系4.4 信息加…

redis命令超详细

redis数据结构介绍 redis是一个key-value的数据库&#xff0c;key一般是String类型&#xff0c;但是value的类型有很多&#xff1a; 基本类型&#xff1a;String,Hash,List,Set,SortedSet(可排序的不能重复的集合) 特殊类型&#xff1a;GEO,BitMap,HyperLog等 文档官网&…

emr部署hive并适配达梦数据库

作者&#xff1a;振鹭 一、达梦 用户、数据库初始化 1、创建hive的元数据库 create tablespace hive_meta datafile /dm8/data/DAMENG/hive_meta.dbf size 100 autoextend on next 1 maxsize 2048;2、创建数据库的用户 create user hive identified by "hive12345&quo…