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

从 select 到 epoll:拆解 I/O 多路复用的演进与实战

目录

一、引言:为什么需要 I/O 多路复用?

二、select

1.函数介绍

2.原理

3.样例代码

4.优缺点总结

三、poll

1.函数介绍

2.样例代码

3.优缺点总结

四、epoll

1.函数介绍

2.原理

3.LT和ET两种工作模式

4.优缺点总结

五、核心机制对比:select vs poll vs epoll

六、高频面试题解析

七、结语


一、引言:为什么需要 I/O 多路复用?

I/O 多路复用(I/O Multiplexing)是为了解决 高并发场景下传统阻塞式或非阻塞式 I/O 模型的效率缺陷 而诞生的核心技术。阻塞式IO,无论是单线程阻塞式IO还是多线程阻塞式IO,在数据未就绪时线程会被挂起,直到数据就绪后才恢复执行。而且,多线程还涉及到线程切换,以及内存开销。线程切换那是需要时间的,频繁的切换会导致效率降低。至于非阻塞式的IO,轮询高频发生,浪费CPU资源。试想这样的场景:当你有一万个网络连接需要同时处理时,为每个连接开一个线程是否可行?答案显然是否定的,抛开线程上下文切换,单从内存上看,Linux默认一个线程大小是8MB,所以内存消耗80000MB / 1024 ≈ 78G。

二、select

多路复用(也叫多路转接)三剑客之一,单进程就可以监视多个文件描述符的可读、可写和异常状态。

1.函数介绍

select函数原型:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

1)nfds:监视的最大文件描述符+1。

2)readfds、writefds、 exceptfds分别表示可读、可写、异常文件描述符的集合。

3)timeout,用来设置等待时间,可避免一直阻塞。

4)返回值

● 返回值大于0,表示监视的文件描述符中已就绪的个数。

● 返回值等于0,表示并没有文件描述符就绪,超过timeout时间。

● 返回值小于0,表示发生错误。

5)timeval结构体,第一个成员变量单位是秒,第二个单位是微秒。

2.原理

select底层是用三张位图来存放对应的对应的文件描述符集合的,并且提供了一套对位图的操作方法。

       void FD_CLR(int fd, fd_set *set);    / /将指定文件描述符移出集合,对应位设为0
       int  FD_ISSET(int fd, fd_set *set);   / /检查fd是否在集合中(对应位是否为1),返回非0表示存在
       void FD_SET(int fd, fd_set *set);    / /将指定文件描述符加入集合,对应位设为1
       void FD_ZERO(fd_set *set);           / /清空集合,将位图中所有位设为0

select底层是会把集合给修改的,例如设置进可读文件描述符集合的是0011 0000,当6号文件描述符可读然后函数返回后,可读文件描述符集合被修改成了0010 0000,没有事件发生的fd = 5被清空。如果我们下次调用还要关心fd = 5的话,需要再次设置进可读文件描述符集合中,需要频繁的重复设置,所以实践中干脆用一个辅助数组来存放这些下次还要关心的fd(避免丢失),每次在调用select之前都遍历一遍辅助数组来设置这些fd,在返回时亦是如此,需要遍历整个位图才知道哪些fd就绪了,这是导致效率比较低的一个原因。

3.样例代码

#include <iostream>
#include <sys/select.h>
#include <unistd.h>int main()
{fd_set read_fds;FD_ZERO(&read_fds);FD_SET(0, &read_fds);//检测标准输入while(true){struct timeval timeout;timeout.tv_sec = 2;//设置超时时间为2秒int ret = select(1, &read_fds, nullptr, nullptr, &timeout);if(ret < 0){perror("select");continue;}if(ret == 0){std::cout << "timeout" << std::endl;continue;}if(FD_ISSET(0, &read_fds)){char buffer[1024] = { 0 };int n = read(0, buffer, sizeof(buffer) - 1);if(n > 0){std::cout << buffer << std::endl;}}//内核会修改位图,需要重新设置进去FD_ZERO(&read_fds);FD_SET(0, &read_fds);}return 0;
}

4.优缺点总结

先说优点,select的跨平台兼容性比较好,几乎主流的操作系统都是兼容的,这相对于epoll来说是个优点(epoll只能在Linux上用)。

缺点有:

文件描述符数量的上限低,通常是1024个,这对于动辄上万个连接的高并发场景来说是远远不够的。

②其次,select需要做不少的遍历工作,用户需要遍历返回后的位图,查找哪些是已经就绪的,然而有时候,遍历一千个文件描述符才有屈指可数的几个就绪也是有的。这就导致了很多无效的遍历。在内核层面,每次调用select函数时亦是如此,需要遍历所有的监听描述符判断是否就绪。这样,随着监听描述符的增多,效率就会不断下降。

③同时,在每一次调用select函数时,需要将fd集合从用户态拷贝到内核态,返回时内核在将结果拷贝回用户态

④除了拷贝上的时间开销,还有内核在将处理结果拷贝到用户态是,会覆盖原有的数据,如果下次还想监听的描述符因此被覆盖的话,下次还要再重新设置。

三、poll

1.函数介绍

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

1)fds是poll函数监听的结构列表,每一个元素中包含三个成员变量:文件描述符、监听事件的集合、返回的事件集合。pollfd结构体如下:

// pollfd 结构

struct pollfd {

int fd;              /* file descriptor */

short events;  /* requested events */

short revents; /* returned events */

};

2)nfds表示fds的长度。

3)timeout表示超时时间,单位是毫秒。 

4)返回值含义和select一样。

2.样例代码

#include <iostream>
#include <poll.h>
#include <unistd.h>int main()
{struct pollfd poll_fd;poll_fd.fd = 0;         //监听标准输入poll_fd.events = POLLIN;//可读事件while(true){int ret = poll(&poll_fd, 1, 3000);if(ret < 0){perror("poll");continue;}if(ret == 0){std::cout << "timeout" << std::endl;continue;}if(poll_fd.revents == POLLIN)//判断返回的事件{char buffer[1024] = { 0 };int n = read(0, buffer, sizeof(buffer) - 1);if(n > 0){std::cout << buffer << std::endl;}}}return 0;
}

3.优缺点总结

先说优点,首先值得一提的是,poll突破了文件描述符数量的限制。使用结构体数组(struct polled[ ])而非位图(fd_set),可监听任意数量的文件描述符,仅受系统资源的限制。第二点,相对于select来说,poll做到了事件和返回结果的解耦(events和revents),不会再像select那样,从内核拷贝到用户时会覆盖原有数据,进而需要再次设置。

缺点是,调用poll时还是需要将数据拷贝到内核,返回时从内核拷贝到用户。而且也需要遍历整个struct polled[ ]数组,检查哪些fd已就绪。

四、epoll

epoll是 Linux 特有的高效 I/O 多路复用机制,专为解决select和poll的性能瓶颈而设计,尤其适合处理高并发问题。按照man手册的说法,epoll是为了处理大量句柄而做了改进的poll。所谓句柄,就是epoll_create函数返回的文件描述符。

1.函数介绍

int epoll_create(int size);

这个函数的作用就是创建一个epoll的句柄。size参数已被忽略,可任意传值。这个句柄(也就是文件描述符)用完以后,需要调用close函数关闭。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

● epoll的事件注册函数。

● 第一个参数是epoll的句柄,即epoll_create函数的返回值。

● 第二个参数表示动作,用三个宏来表示。

        EPOLL_CTL_ADD:注册新的fd

        EPOLL_CTL_MOD:修改已经注册fd的监听事件

        EPOLL_CTL_DEL:删除fd

● 第三个参数是要监听的fd。

● 第四个参数是告诉内核要监听什么事件。

至此,可能比较迷的就是struct epoll_event是什么鬼?下面是它的结构:

struct epoll_event {uint32_t     events;  // 监控的事件类型(如 EPOLLIN、EPOLLOUT)epoll_data_t data;    // 用户数据(常保存 fd 或回调函数指针)
};typedef union epoll_data {void    *ptr;int      fd;        // 常用:与事件关联的 fduint32_t u32;uint64_t u64;
} epoll_data_t;

下面看看epoll_ctl函数是怎么用的。

struct epoll_event ev;
ev.data.fd = fd;
ev.events = EPOLLIN;
int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);//epoll_fd为句柄

 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

● 等待事件触发。

● 第一个参数是epoll句柄。

● 输出参数,存放就绪事件的结构体数组

maxevents,events数组大小(单次返回的最多事件数)

● 超时时间(ms),-1 表示阻塞直到事件触发

● 返回值:>0:触发的事件数量  0:超时   -1:错误

函数使用样例:

epoll_event events[1000];
int nfds = epoll_wait(epoll_fd, events, sizeof(events) / sizeof(events[0], -1);

2.原理

epoll原理围绕红黑树、就绪列表、回调函数机制这三者展开。当调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这两个结构体中有两个关键的成员——红黑树和就绪列表。

struct eventpoll{
....
/*红黑树的根节点,这颗树中存储着所有添加到 epoll 中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过 epoll_wait 返回给用户的满足条件的事件。就绪列表*/
struct list_head rdlist;
....
};

通过epoll_ctl方法添加的事件,最终就会被挂到红黑树中。而所有添加到epoll中的事件都会与设备驱动程序建立回调关系,然后在监控的事件发生时调用这个回调方法。这个回调方法在内核中叫做ep_poll_callback,它会把发生的事件添加到就绪列表rdlist中。然后,当调用epoll_wait方法时,只需要检查eventpoll中的就绪列表rdlist是否有元素即可。如果就绪列表不为空,那么就将发生的事件拷贝给用户,同时将事件的数量返回给用户。 与select和poll相比,epoll就不用去遍历所有的事件,导致一些无效的遍历和判断。下面继续深入到一些细节问题。

 红黑树中的每个结点,都是一个epitem结构体。下面看看这个结构体长啥样。

struct epitem{
struct rb_node rbn;       //红黑树节点
struct list_head rdllink; //双向链表节点
struct epoll_filefd ffd;  //事件句柄信息
struct eventpoll *ep;     //指向其所属的 eventpoll 对象
struct epoll_event event; //期待发生的事件类型
}

总结一下:

红黑树的作用就是存储所有需要监控的文件描述符(fd)及其关联的事件(epoll_event) 。

就绪列表的作用就是临时存储已触发的fd(就绪事件)。当事件触发时,由回调函数ep_poll_callback将对应的结点添加到就绪列表中。当调用epoll_wait方法时,将就绪事件拷贝给用户然后清空就绪列表。

 重谈epoll_create、epoll_ctl、epoll_wait三个函数。

1)当调用epoll_create时,创建eventpoll结构体,然后初始化红黑树的根节点和就绪列表表头。

2)当调用epoll_ctl(epfd, EPOLL_CTL_ADD, fd, event)时,内核首先会检查红黑树中是否已经存在对应的fd结点。如果不存在,那就新        建epitem结点并入树。然后设置该fd对应的回调函数ep_poll_callback,绑定到其底层设备驱动。

3)当调用epoll_wait时,就去检查就绪列表,如果不为空,则拷贝到用户态,然后清空就绪列表。

3.LT和ET两种工作模式

LT(Level-Triggered,水平触发),只要文件描述符fd满足可读/可写的条件,epoll_wait会持续通知应用层,直到数据被完全处理。

当fd的接收缓冲区有数据时,会触发EPOLLIN事件,只要数据未读完,每次调用epoll_wait方法都会重复报告此fd的可读事件。同理,如果发送缓冲区未满(可写入),则持续触发EPOLLOUT。

优点:容错性强,未处理的事件不会被遗漏,LT模式下会持续通知上层应用。而且编程比较简单。

缺点:若应用层未及时处理完数据,频繁触发事件会增加epoll_wait的调用次数。一个高负载的fd可能会导致epoll_wait长期处于忙碌状态。

ET(Edge-Triggered,边缘触发),仅在fd状态变化时触发通知(比如从不可读变为可读),后续如果数据未读完也不在通知,直到下一次状态变化(比如数据由少变多)。

当接收缓冲区从空变为非空时,触发一次EPOLLIN,之后即使仍有数据未读完也不在重复通知。因此应用层必须在一次通知中处理完所有数据,否则可能永远无法收到下一个状态变化的通知,进而丢失数据。

优点:仅在状态变化时触发一次,减少了epoll_wait的调用频率。同时一次性把接收缓冲区中的数据全部读完,这样发送方可以收到更大的窗口大小通知,进而可以发送更多的数据,提高网络的吞吐量。

缺点:编程复杂,如果处理数据不当,可能丢失数据。

特性

LT(水平触发)

ET(边缘触发)

触发条件

数据未处理完则持续触发

仅当状态变化时触发一次

系统调用次数

可能较多(重复触发)

较少(严格依赖状态变化)

编程复杂度

高(需非阻塞 IO + 循环处理)

容错性

高(未处理完会再次触发)

低(漏处理可能导致事件丢失)

适用场景

简单场景、对性能不敏感的延迟容忍型

高并发、延迟敏感型(如高频交易系统)

4.优缺点总结

优点:

1)epoll有着高效的事件通知机制(基于回调函数),epoll_wait直接返回给用户就绪事件列表,无需遍历所有的文件描述符,性能与活跃连接数无关,对比select和poll的O(n)遍历,epoll在高并发场景下性能卓越。

2)支持大并发连接,文件描述符数量仅受系统资源限制。相比与select,这是一个优点。

3)事件触发模式灵活,根据不同的场景,有LT(默认)和ET两种模式可选。

4)无重复数据拷贝。select和poll在每次调用时,都需要将文件描述符集从用户空间拷贝到内核空间,而epoll在添加对应文件描述符和它对应的事件后,再调用epoll_wait时,无需再传入监听的文件描述符集,进而没有重复拷贝。

缺点:仅限Linux平台,代码可移植性差。

五、核心机制对比:select vs poll vs epoll

指标

select

poll

epoll

最大并发连接数

1024

10k+(受内存限制)

100k+(内存依赖)

时间复杂度

O(n)

O(n)

O(1)

内存拷贝开销

每次调用传递全量 fd

同 select

注册后无需重复传递

触发模式

仅 LT

仅 LT

LT 和 ET

适用平台

跨平台

类 Unix

Linux

六、高频面试题解析

epoll 为什么用红黑树不用哈希表?

1)红黑树性能稳定可靠。哈希表查找的平均时间复杂度为O(1),但是在有大量哈希冲突,可能退化到O(N)。内核需要保证稳定的响应时间,无法接受性能波动,这对于高并发场景非常关键。红黑树作为自平衡的二叉搜素树,插入、查找、删除操作均严格保证O(logN)的时间复杂度,性能可预测且稳定。

2)红黑树具有更高效的动态操作。哈希表可能需要扩容,扩容就需要重新计算哈希值并迁移数据,导致短暂的性能抖动。红黑树天然支持动态的增删结点,无需全局调整结构,可以平滑的支持扩展。

3)红黑树的内存利用率比较高而且不存在冲突。在稀疏连接的场景下,哈希表中可能存在不少空桶,导致浪费。红黑树是按需分配结点内存的,不存在额外的浪费。而且红黑树是以fd来作为键值的,天然就避免了冲突。 

ET 模式必须配非阻塞 socket 吗?为什么? 

在ET(边缘触发)模式下使用非阻塞socket是强烈建议且几乎必须的。

1)ET模式下,要求上层一次读完数据,为了确保一次读完数据,需要通过循环读取,比如循环的调用read。但是,在接收缓冲区中的数据读完以后,再次调用read会导致阻塞,进而线程被挂起,直到下一波数据的到达。线程一旦被挂起,就无法去处理其他事件,严重影响并发性能。

2)非阻塞可以维持事件循环的高效性。非阻塞socket在每次IO操作后立即返回,使得应用可以快速处理完当前事件后,继续通过epoll_wait监听其他就绪事件,保持事件循环的高吞吐。如果是阻塞式的socket,单次处理可能长时间占用线程,导致其他就绪时间延迟处理,降低系统整体的响应速度。

如果 fd 频繁增减,epoll 是否仍高效? 

 依然高效,理由如下:

1)epoll使用红黑树作为管理fd的底层结构,其增删查操作均严格保证O(logN)的时间复杂度(n是当前监听的fd数量)。即使fd频繁增删,单次操作的性能仍被控制在可控范围内。对比select和poll这种基于线性扫描的模型,性能随fd数量的增长线性下降。而epoll的 O(log n) 操作显著优于这种线性开销。

2)epoll事件通知机制的天然优势——无轮询开销。epoll通过事件驱动的方式通知就绪的fd(基于回调机制),而非遍历整个集合。fd的频繁增删不会直接关联事件通知的开销。epoll_wait只返回就绪的fd。

七、结语

多路转接在面试中还是比较常见的,还请诸位慎重。

路漫漫其修远兮,吾将上下而求索!如有错误,请不吝指出,感谢支持!


完结~

相关文章:

从 select 到 epoll:拆解 I/O 多路复用的演进与实战

目录 一、引言&#xff1a;为什么需要 I/O 多路复用&#xff1f; 二、select 1.函数介绍 2.原理 3.样例代码 4.优缺点总结 三、poll 1.函数介绍 2.样例代码 3.优缺点总结 四、epoll 1.函数介绍 2.原理 3.LT和ET两种工作模式 4.优缺点总结 五、核心机制对比&…...

Go后端架构探索:从 MVC 到 DDD 的演进之路

Go语言 MVC 与 DDD 分层架构详细对比 MVC和DDD是后台开发两种流行的分层架构思想&#xff0c;MVC&#xff08;Model-View-Controller&#xff09;是一种设计模式&#xff0c;主要用于分离用户界面、业务逻辑和数据模型&#xff0c;便于分层解耦&#xff0c;而DDD&#xff08;领…...

【力扣hot100题】(017)矩阵置零

还是挺简单的&#xff0c;使用哈希表记录需要置换的行列即可&#xff0c;这样就可以避免重复节省时间。 class Solution { public:void setZeroes(vector<vector<int>>& matrix) {unordered_set<int> row;unordered_set<int> line;for(int i0;i&l…...

One Commander 3,文件管理新体验

One Commander 3 是一款集多功能于一体 Windows 10/11的文件管理工具&#xff0c;其设计目的在于为用户带来多元化的操作体验。这款工具通过支持多栏界面布局&#xff0c;让用户能够迅速且高效地组织和管理文件。此外&#xff0c;它还提供了多主题选项和多种图标集&#xff0c;…...

Ubuntu 下 nginx-1.24.0 源码分析

main 函数在 src\core\nginx.c int ngx_cdecl main(int argc, char *const *argv) {ngx_buf_t *b;ngx_log_t *log;ngx_uint_t i;ngx_cycle_t *cycle, init_cycle;ngx_conf_dump_t *cd;ngx_core_conf_t *ccf;ngx_debug_init();if (ngx_strerror_in…...

c# ftp上传下载 帮助类

工作中FTP的上传和下载还是很常用的。如下载打标数据,上传打标结果等。 这个类常用方法都有了:上传,下载,判断文件夹是否存在,创建文件夹,获取当前目录下文件列表(不包括文件夹) ,获取当前目录下文件列表(不包括文件夹) ,获取FTP文件列表(包括文件夹), 获取当前目…...

Java进阶——静态代理与动态代理

代理模式是一种常用的设计模式&#xff0c;为其他对象提供一种代理以控制对这个对象的访问。代理模式就像是一个中间人&#xff0c;客户端通过代理来间接访问目标对象&#xff0c;可以在不修改目标对象的基础上&#xff0c;对目标对象的功能进行增强或扩展。代理模式主要分为静…...

VS Code 中 .history`文件的来源与 .gitignore`的正确使用

引言 在使用 VS Code 进行 Git 版本控制时&#xff0c;有时会发现项目中多出一个 .history 目录&#xff0c;并被 Git 识别为未跟踪文件。本文将解释 .history 的来源&#xff0c;并提供 .gitignore 的正确配置方法&#xff0c;确保开发环境的整洁性。 1. .history 文件的来源…...

非手性分子发光有妙招:借液晶之力,实现高不对称圆偏振发光

*本文只做阅读笔记分享* 一、圆偏振发光研究背景与挑战 圆偏振发光&#xff08;CPL&#xff09;材料在3D显示、光电器件等领域大有用处&#xff0c;衡量它的一个重要指标是不对称发光因子&#xff08;glum&#xff09;。早期CPL材料的glum值低&#xff0c;限制了实际应用。为…...

解释器模式_行为型_GOF23

解释器模式 解释器模式&#xff08;Interpreter Pattern&#xff09;是一种行为型设计模式&#xff0c;核心思想是定义语言的文法规则&#xff0c;并构建一个解释器来解析和执行该语言中的表达式。它类似于“翻译器”——将符合特定语法规则的文本&#xff08;如数学公式、脚本…...

OTN(Optical Transport Network)详解

OTN&#xff08;光传送网&#xff09;是一种基于**波分复用&#xff08;WDM&#xff09;**的大容量光传输技术&#xff0c;结合了SDH的运维管理优势和WDM的高带宽特性&#xff0c;广泛应用于骨干网、城域核心层及数据中心互联&#xff08;DCI&#xff09;。 1. OTN 的基本概念 …...

YOLOv8+ Deepsort+Pyqt5车速检测系统

该系统通过YOLOv8进行高效的目标检测与分割&#xff0c;结合DeepSORT算法完成目标的实时跟踪&#xff0c;并利用GPU加速技术提升处理速度。系统支持模块化设计&#xff0c;可导入其他权重文件以适应不同场景需求&#xff0c;同时提供自定义配置选项&#xff0c;如显示标签和保存…...

【干货】前端实现文件保存总结

⚠️⚠️文前推荐一下&#x1f449; 前端必备工具推荐网站(图床、API和ChatAI、智能AI简历、AI思维导图神器等实用工具): 站点入口&#xff1a;http://luckycola.com.cn/ 前端实现文件保存实现总结 在Web开发中&#xff0c;文件下载是常见的交互需求。本文将系统总结前端实现文…...

并发编程之FutureTask.get()阻塞陷阱:深度解析线程池CPU飚高问题排查与解决方案

FutureTask.get方法阻塞陷阱&#xff1a;深度解析线程池CPU飚高问题排查与解决方法 FutureTask.get()方法阻塞陷阱&#xff1a;深度解析线程池CPU飚高问题排查与解决方法1、情景复现1.1 线程池工作原理1.2 业务场景模拟1.3 运行结果1.4 发现问题&#xff1a;线程池没有被关闭1.…...

DGNN-YOLO:面向遮挡小目标的动态图神经网络检测与追踪方法解析

一、算法结构与核心贡献 1.1 文章结构 采用经典五段式结构: ​引言:分析智能交通系统(ITS)中小目标检测与追踪的挑战,提出研究动机。​相关工作:综述小目标检测(YOLO系列、Faster R-CNN)、目标追踪(SORT、Transformer)和图神经网络(GNN)的进展。​方法论:提出DG…...

在Ubuntu中固定USB设备的串口号

获取设备信息 lsusb # 记录设备的Vendor ID和Product ID&#xff08;例如&#xff1a;ID 0403:6001&#xff09;# 获取详细属性&#xff08;替换X和Y为实际设备号&#xff09; udevadm info -a /dev/ttyUSBX 结果一般如下 创建udev规则文件 sudo gedit /etc/udev/rules.d/us…...

javaSE————文件IO(2)、

文件内容的读写——数据流 我们对于文件操作使用流对象Stream来操作&#xff0c;什么是流对象呢&#xff0c;水流是什么样的&#xff0c;想象一下&#xff0c;水流的流量是多种的&#xff0c;可以流100ml&#xff0c;也可以流1ml&#xff0c;流对象就和水流很像&#xff0c;我…...

前端常问的宏观“大”问题详解(二)

JS与TS选型 一、为什么选择 TypeScript 而不是 JavaScript&#xff1f; 1. 静态类型系统&#xff1a;核心优势 TypeScript 的静态类型检查能在 编译阶段 捕获类型错误&#xff08;如变量类型不匹配、未定义属性等&#xff09;&#xff0c;显著减少运行时错误风险。例如&…...

[创业之路-343]:创业:一场认知重构与组织进化的双向奔赴

目录 前言&#xff1a;关键词&#xff1a; 一、重构企业认知框架&#xff1a; 1、认知框架的顶层设计——六大维度生态模型 2、认知重构的精密设计——五层结构化模型 第一层&#xff1a;战略层&#xff08;脑&#xff09; 第二层&#xff1a;运营层&#xff08;躯干&…...

智慧电力:点亮未来能源世界的钥匙

在科技日新月异的今天&#xff0c;电力行业正经历着前所未有的变革。智慧电力&#xff0c;作为这一变革的核心驱动力&#xff0c;正逐步改变着我们对电力的认知和使用方式。它不仅是电力行业的一次技术革新&#xff0c;更是推动社会可持续发展、实现能源高效利用的重要途径。 智…...

架构师面试(二十三):负载均衡

问题 今天我们聊微服务相关的话题。 大中型微服务系统中&#xff0c;【负载均衡】是一个非常核心的组件&#xff1b;在微服务系统的不同位置对【负载均衡】进行了实现&#xff0c;下面说法正确的有哪几项&#xff1f; A. LVS 的负载均衡一般通过前置 F5 或是通过 VIP keepa…...

CSS3学习教程,从入门到精通, CSS3 列表控制详解语法知识点及案例代码(24)

CSS3 列表控制详解 CSS 列表控制的语法知识点及案例代码的详细说明&#xff0c;包括 list-style-type、list-style-image、list-style-position 和 list-style 的用法。 1. list-style-type 属性 list-style-type 属性用于设置列表项标记的类型。 语法 list-style-type: v…...

NSSCTF(MISC)—[justCTF 2020]pdf

相应的做题地址&#xff1a;https://www.nssctf.cn/problem/920 binwalk分离 解压文件2AE59A.zip mutool 得到一张图片 B5F31内容 B5FFD内容 转换成图片 justCTF{BytesAreNotRealWakeUpSheeple}...

坚持“大客户战略”,昂瑞微深耕全球射频市场

北京昂瑞微电子技术股份有限公司&#xff08;简称“昂瑞微”&#xff09;是一家聚焦射频与模拟芯片设计的高新技术企业。随着5G时代的全面到来&#xff0c;智能手机、智能汽车等终端设备对射频前端器件在通信频率、多频段支持、信道带宽及载波聚合等方面提出了更高需求&#xf…...

LiteDB 数据库优缺点分析与C#代码示例

LiteDB 是一个轻量级的 .NET NoSQL 嵌入式数据库,完全用 C# 开发,支持跨平台(Windows、Linux、MacOS),并提供类似于 MongoDB 的简单 API。它以单文件形式存储数据,类似于 SQLite,支持事务和 ACID 特性,确保数据的一致性和可靠性。 优缺点分析 优点: 轻量级与嵌入式:…...

上海SMT贴片技术解析与行业趋势

内容概要 随着长三角地区电子制造产业集群的快速发展&#xff0c;上海作为核心城市正引领着SMT贴片技术的革新浪潮。本文聚焦表面组装技术在高密度互连、微间距贴装等领域的突破性进展&#xff0c;通过解析焊膏印刷精度控制、元件定位算法优化等核心工艺&#xff0c;展现上海企…...

HTML5和CSS3的一些特性

HTML5 和 CSS3 是现代网页设计的基础技术&#xff0c;它们引入了许多新特性和功能&#xff0c;极大地丰富了网页的表现力和交互能力。 HTML5 的一些重要特性包括&#xff1a; 新的语义化标签: HTML5 引入了一些重要的语义化标签如 <header>, <footer>, <articl…...

Linux系统中快速安装docker

1 查看是否安装docker 要检查Ubuntu是否安装了Docker&#xff0c;可以使用以下几种方法&#xff1a; 方法1&#xff1a;使用 docker --version 命令 docker --version如果Docker已安装&#xff0c;输出会显示Docker的版本信息&#xff0c;例如&#xff1a; Docker version …...

每日c/c++题 备战蓝桥杯(最长上升子序列)

点击题目链接 题目描述 给出一个由 n(n≤5000) 个不超过 1e6 的正整数组成的序列。请输出这个序列的最长上升子序列的长度。 最长上升子序列是指&#xff0c;从原序列中按顺序取出一些数字排在一起&#xff0c;这些数字是逐渐增大的。 输入格式 第一行&#xff0c;一个整数…...

蓝桥杯—质数

质数 质数是一个只有1和它本身2个因数 代码实现 //求质数 #include<bits/stdc.h> using namespace std; bool zhishu(int n) {if(n1){cout<<"1不是质数";return false;}else if(n>1){for(int i2;i<sqrt(n);i){if(n%i0){cout<<n<<&q…...