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

select、poll和epoll的区别

文章目录

    • 概要
    • 一、多路复用I/O模型的诞生
        • 1.1 多线程或进程方式
        • 1.2 通过数组,链表等方式保存socket fd,不断轮询
    • 二、select
    • 三、poll
    • 四、epoll
    • 五、小结
    • 六、参考

概要

在Unix五种I/O模型一文中,提到了I/O多路复用模型,其在Linux下有3种实现方式:select、poll、epoll,本文主要深入介绍下它们各自特点。

事先说明:I/O多路复用模型,select和poll核心就是【轮询+内核I/O事件就绪通知】,epoll的核心是内核I/O事件就绪通知。

多路:多个socket连接(即多个客户端连接)
复用:允许内核监听多个socket描述符,一旦发现进程指定的一个或多个scoket的I/O事件就绪(TCP三次握手成功、可读,可写),就通知该进程

要想更好的了解,最好根据代码来说,下面是代码的基本框架:

void main(int argc, char **argv)
{int listenfd, connfd;struct sockaddr_in srv_addr;//创建socket套接字if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);return;}//设置绑定地址的内容memset(&srv_addr, 0, sizeof(srv_addr));srv_addr.sin_family = AF_INET; //ipv4srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);//ip 0.0.0.0srv_addr.sin_port = htons(8888); //端口//绑定地址if (bind(listenfd, (struct sockaddr *)&srv_addr, sizeof(srv_addr)) == -1){printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);return;}//listenif (listen(listenfd, SOMAXCONN) == -1) //指定监听的套接字描述符、TCP半连接和全连接队列大小{printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);return;}//【在这个区域分别使用阻塞,多线程,select,poll,epoll等多种方式实现连接】close(listenfd); //关闭listen socketreturn;
}

一、多路复用I/O模型的诞生

之所以诞生多路复用I/O模型,肯定是旧的I/O模型无法满足需要了,首先回顾下基础的阻塞I/O模型:

代码如下:

    char buff[MAXLNE];int n;struct sockaddr_in cli_addr;socklen_t len = sizeof(client);if ((connfd = accept(listenfd, (struct sockaddr *)&cli_addr, &len)) == -1){printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);return;}while(1){n = recv(connfd, buff, MAXLNE, 0); //阻塞if (n > 0){//加上字符串的尾部,以便显示和转发buff[n] = '\0';printf("recv msg: %s \n", buff);send(connfd, buff, n, 0);}else if (n == 0){close(connfd); //关闭client socket连接}else //n==-1{printf("recv errno: %d\n", errno);}}

此时while在accept API之后,那么只能处理一个client,并维持长连接。
那么while在accept API之前会如何呢,显而易见,此时能处理多个client,但只能处理每个client一条消息,不能未出长连接。

如果想与多个client维持长连接,该如何做呢?于是基础阻塞I/O模型有了以下两种方式:

  • 多线程或进程;
  • 通过数组,链表等方式保存socket fd,不断轮询;
1.1 多线程或进程方式

代码如下(以多进程为例):

	signal(SIGCHLD, sig_child); //注册子进程退出处理函数pid_t child_pid;while(1){struct sockaddr_in cli_addr;socklen_t len = sizeof(client);if ((connfd = accept(listenfd, (struct sockaddr *)&cli_addr, &len)) == -1){printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);return;}if ((child_pid = fork()) == 0) //为每个client派生一个子进程处理{ close(listenfd);str_echo(connfd);exit(0);}}

子进程处理客户端请求函数

void str_echo(int connfd)
{int n;char buff[MAXLNE];again:while ((n = recv(connfd, buf, MAXLINE)) > 0){printf("recv msg: %s \n", buff);send(sockfd, buf, n);}if (n < 0 && errno == EINTR){goto again;}	else if (n == 0){close(connfd); //结束return;  //退出进程}else //n==-1{printf("recv errno: %d\n", errno);}
}

子进程退出函数

void sig_child(int signo)
{pid_t pid;int stat;//等待所有子进程退出while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)printf("child %d exit\n", pid);return;
}

但是这方式有个弊端,每个client由一个进程/线程去处理,系统开销相当大,很难维持大量客户端。

1.2 通过数组,链表等方式保存socket fd,不断轮询

这种模式是将客户端socket fd通过数组,链表等方式保存下来,然后不断地轮询,如果客户端太多,轮询也是很慢的。

伪代码如下:

int client_fds[FD_SETSIZE];
int sockfd;
while(1)
{int connfd = accept() //阻塞for (i = 0; i < FD_SETSIZE; i++) { if (client[i] < 0) {client_fds[i] = connfd;break;}}for (i = 0; i < FD_SETSIZE; i++) {if ((sockfd = client_fds[i]) <= 0){continue; }n = recv(sockfd, buf, MAXLINE)//阻塞if(n > 0){printf("recv msg: %s \n", buff);send(sockfd, buf, n);}else if (n < 0 ){printf("recv fd:%d, errno: %d\n", sockfd, errno);}	else// n == 0{close(connfd); //结束client_fds[i] = 0; //标记一下}	}
}  

可以看到通过client_fds数组将客户端连接的描述符保存下来,后续对其进行轮询,来达到与多个client维持长连接的目的。
但accept,recv等函数都是阻塞的,如果此时I/O事件(比如accept的TCP三次握手成功,recv的可读,send的可写)没就绪,那岂不永远卡死了。所以我们需要一种机制,告诉我们client_fds数组和监听socket listenfd中哪些socket有I/O就绪事件,基于此多路复用I/O模型诞生了,没错,该模型本质就是告诉进程哪些socket有I/O就绪事件,然后我们基于此去轮询那些有I/O就绪事件的scoket,这样就不会卡住了。

PS:这种方式是没有实际应用的,主要是为了引出多路复用I/O模型。

那select、poll、epoll是如何告诉进程哪些socket有I/O就绪事件呢?下面依次探究下吧。

二、select

select api函数(还有个pselect函数,不是很常用,不过二者核心逻辑是一样的):

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

参数:
nfds:最大的文件描述符数量加1的值
readfds:监听可读集合
writefds:监听可写集合
exceptfds:监听异常集合
timeout:超时时间
返回值:
大于0,表示I/O就绪事件的socket描述符个数;
等于0表示没有描述符有状态变化,并且调用超时;
小于0表示出错,此时全局变量errno保存错误码。

我们使用Glibc库select或pselect函数时,都会走Linux内核do_select函数,可以看到本质就是轮询readfds、writefds、exceptfds这三个集合,每次调用会轮询两次:

  1. 第一次会将当前进程(本质是I/O事件就绪时的回调函数)加入到readfds、writefds、exceptfds这三个集合中socket的等待队列中(socket有I/O事件就会触发回调函数),然后就通过poll_schedule_timeout函数挂起;
  2. 一旦有某个socket描述符I/O事件就绪,会立即通知进程,就会开始第二次轮询,本次轮询会确定readfds、writefds、exceptfds这三个集合中到底是哪些socket描述符有就绪的I/O事件;
  3. 明明两次,为啥会有三呢,这里主要是在do_select最后会调用一次poll_freewait函数,该函数会将当前进程从readfds、writefds、exceptfds这三个集合中socket的等待队列中移除。

从上面描述就可以看出,select主要工作:维护所有socket的监听(添加【步骤1】和移除【步骤三】)、判定是否有I/O事件就绪的socket
该方式虽相比基于阻塞I/O的多进程/线程方式能更便捷的实现与多个客户端维持长连接了,但缺点多多:

  1. 由于无法准确识别哪些socket描述符I/O事件就绪,所以会进行无差别轮询,时间复杂度O(N);
  2. Linux下readfds、writefds、exceptfds这三个集合大小默认1024,所以维持长连接的客户端数量是有限的,源码如下:
    typedef __kernel_fd_set fd_set
    __kernel_fd_set
#undef __FD_SETSIZE
#define __FD_SETSIZE	1024typedef struct {unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))];
} __kernel_fd_set;

可以看到无论32位还是64位操作系统下fds_bits大小都是1024位,注意fd_set集合是通过位运算标识第几个scoket描述符有无I/O事件。

  1. 每次调用select函数,都需要把所有fd_set从用户空间拷贝到内核空间,如果fd_set比较大,对性能影响就非常大;

优点:

  1. 相比基于阻塞I/O的多进程/线程方式,更便捷的实现与多个客户端维持长连接;
  2. 相比非阻塞I/O,主动轮询socket是否有I/O事件,调整为等待内核通知,这样一次系统调用就实现多个client事件的管理,更有优势。

综合来看相比基于阻塞I/O的多进程/线程方式优势并不大。

三、poll

poll api函数:

struct pollfd {int   fd;        //要监听的文件描述符short events;    //要监听的事件short revents;   //事件结果
};
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数:
fds:这是一个数组,每一个数组元素表示要监听的文件描述符以及相应的事件
nfds:数组的个数
timeout:超时时间
返回值:
大于0:表示结构体数组fds中有fd描述符的状态发生变化,或可以读取、或可以写入、或出错。并且返回的值表示这些状态有变化的socket描述符的总数量;此时可以对fds数组进行遍历,以寻找那些revents不空的描述符,然后判断这个里面有哪些事件以读取数据。
等于0:表示没有描述符有状态变化,并且调用超时。
小于0:此时表示有错误发生,此时全局变量errno保存错误码。

我们使用Glibc库poll函数时,会走Linux内核do_sys_poll和do_poll函数,可以看到:

  • do_sys_poll函数会将fds数组转化为struct poll_list链表,根据代码可知结构如下图:
    poll_list结构图
  • do_poll函数被 do_sys_poll调用,其核心逻辑与do_select差不多,每次被调用会轮询两次:
    1):第一次会将当前进程(本质是I/O事件就绪时的回调函数)加入到struct poll_list链表中socket的等待队列中(socket有I/O事件就会触发回调函数),然后就通过poll_schedule_timeout函数挂起;
    2):一旦有某个socket描述符I/O事件就绪,会立即通知进程,就会开始第二次轮询,本次轮询会确定struct poll_list链表中到底是哪些socket描述符有就绪的I/O事件。
  • do_sys_poll调用do_poll函数结束后,还有一次循环,即poll_freewait函数,此时会将当前进程从struct poll_list链表中socket的等待队列中移除。

从上面描述就可以看出,相比select,唯一改进的地方在于没有了最大连接数的限制,但相应的也需要关注随着客户端连接数增加,轮询的效率和会急剧下降的问题。

四、epoll

epoll全名event poll,其api函数有:

  1. epoll_create,创建一个epoll实例struct eventpoll,其实其返回一个int值,可以称之为epoll描述符,Linux内核会管理该值与具体epoll实例的映射关系。对应内核源码do_epoll_create
//epoll 结构体,即epoll实例
struct eventpoll {/* 等待队列头,被sys_epoll_wait使用 */wait_queue_head_t wq;/* 保准准备就绪的文件描述符的一个链表 */struct list_head rdllist;/* 红黑树节点,epoll使用红黑树存储事件信息 */struct rb_root rbr;...
};
int epoll_create(int size);

参数:
size:一个int值,实际没有任何用,只要不小于等于0即可;
返回值:
小于0表示错误,此时全局变量errno保存错误码

  1. epoll_ctl,添加、删除、修改事件。对应内核源码do_epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数:
epfd:epoll_create函数的返回值,即epoll描述符;
op:事件类型,EPOLL_CTL_ADD(添加事件)、EPOLL_CTL_MOD(修改事件)、EPOLL_CTL_DEL(删除事件);
fd: socket描述符;
event:告诉内核需要监听哪个事件。
返回值:
小于0表示错误,此时全局变量errno保存错误码

  1. epoll_wait,等待I/O事件。对应内核源码do_epoll_wait
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

参数:
epfd:epoll_create函数的返回值,即epoll描述符;
events:表示准备就绪的事件数组;
maxevents: 要等待的最大事件数;
timeout:超时时间。
返回值:
大于0:表示事件就绪的socket个数。
等于0:表示没有描述符有状态变化,并且调用超时。
小于0:此时表示有错误发生,此时全局变量errno保存错误码。

根据api就可以观察出,相比select和poll,epoll拆成了三个,实际上是两个epoll_ctl和epoll_wait。即select和poll是将维护监听scoket描述符等待I/O事件合为一体的,epoll拆开了,epoll_ctl来维护监听scoket描述符,epoll_wait来等待I/O事件`。

为什么要这样做呢,在分析select和epoll缺点时,其实主要集中两点:

1:轮询:每次调用select或poll时都需要将进程加入到所有socket的等待队列中,等到socket有I/O事件立马唤醒进程,此时会轮询整个socket列表以确定哪些socket有I/O事件,最后会将进程从每个socket等队列中移除。这里涉及到对socket列表的三次轮询,随着socket列表的增加,会造成性能急剧下降的。
2:复制:每次调用select或poll时都要将整个socket列表从用户区复制到内核区,也是有一定开销的。

知道了缺点,该如何解决呢?

1:针对每次调用需要将将进程加入到所有socket的等待队列中,最后将进程从每个socket等队列中移除的操作,这部分交由epoll_ctl处理,按需对某个socket进行EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL操作;
2:针对每次调用都要将整个socket列表从用户区复制到内核区的问题,这部分交由epoll_create创建的epoll实例替代,epoll_ctl通过EPOLL_CTL_ADD操作,会被插入到epoll实例的红黑树中,也就是说只会被复制一次;
3:针对进程被唤醒后,还需要轮询整个socket列表以确定哪些socket有I/O事件的问题(即不知道哪些socket的I/O事件就绪,只能一个个遍历),调整成每个socket有I/O事件时会将该socket加入到epoll实例的rdllink双向链表中,进程调用epoll_wait时只需判断rdllink双向链表长度即可,大于0立即返回,等于0就挂起进程,等待被某个socketI/O事件唤醒。

所以说epoll通过对select/poll功能的拆分,解决了前两者的缺点,相对于前两者优点如下:

  1. 没有最大并发连接的限制,当然了还受操作系统限制,比如资源、配置等等;
  2. 性能高,没有了轮询,不会随着socket数量的增加而导致性能下降。
  3. epoll支持边缘触发(EPOLLET)和水平触发(EPOLLLT),前两者仅支持水平触发。

缺点:
目前缺点就是进程去读写I/O事件就绪的socket时,还需要将数据从内核区复制到用户区,当然了select/poll也有该问题,这一点需要异步I/O去解决了。目前epoll是够用的,Nginx,Redis都是基于epoll的,足以应对处理C10K,C100K问题。

LT(level triggered) 是 缺省 的工作方式 ,并且同时支持 block 和 no-block I/O。 在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操作。如果你不作任何操作,下次内核还会继续通知你的,所以,这种模式编程出错误可能性要小一点。 select/poll 都是这种模型的代表。
ET(edge-triggered) 是高速工作方式 ,只支持 no-block I/O 。在这种模式下,当描述符从未就绪变为就绪时,内核通过 epoll 告诉你,然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了 ( 比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个 EWOULDBLOCK 错误)。但是请注意,如果一直不对这个 socket做 IO 操作 ( 从而使它再次变成未就绪 ) ,内核不会发送更多的通知 (only once), 不过在 TCP 协议中, ET 模式的加速效用仍需要更多的 benchmark 确认。
Epoll 工作在 ET 模式的时候,必须使用非阻塞I/O,以避免由于一个文件句柄的阻塞读 / 阻塞写操作把处理多个文件描述符的任务饿死。

五、小结

select&poll&epoll区别
从整体来看,epoll的实现性能是比select/poll更好的,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调,一般情况下选epoll就对了。

本人研究Redis相关源码时,观察到其在Linux下就是通过epoll+EPOLLET+非阻塞I/O+Reactor设计模式组合来处理I/O事件,是其高性能的一个关键点。

六、参考

1]:Linux下实现单客户连接的tcp服务端
2]:从网络I/O模型到Netty,先深入了解下I/O多路复用
3]:epoll的本质
4]:深入了解epoll模型
5]:Linux epoll内核源码剖析

相关文章:

select、poll和epoll的区别

文章目录 概要一、多路复用I/O模型的诞生1.1 多线程或进程方式1.2 通过数组&#xff0c;链表等方式保存socket fd&#xff0c;不断轮询 二、select三、poll四、epoll五、小结六、参考 概要 在Unix五种I/O模型一文中&#xff0c;提到了I/O多路复用模型&#xff0c;其在Linux下有…...

gpt今日最新新闻:gpts的广泛应用

最近&#xff0c;OpenAI给ChatGPT带来了一个备受期待的更新——“GPT提及&#xff08;mentions&#xff09;”功能。这项创新不仅增强了ChatGPT的实用性&#xff0c;也为AI在日常业务中的运用开辟了新路径。在本文中&#xff0c;我将分享我对这项新功能的初步体验&#xff0c;并…...

【进入游戏行业选游戏特效还是技术美术?】

进入游戏行业选游戏特效还是技术美术&#xff1f; 游戏行业正处于蓬勃发展的黄金时期&#xff0c;科技的进步推动了游戏技术和视觉艺术的飞速革新。在这个创意和技术挑战交织的领域里&#xff0c;游戏特效和技术美术岗位成为了许多人追求的职业目标。 这两个岗位虽然紧密关联…...

(delphi11最新学习资料) Object Pascal 学习笔记---第4章第2.3节(常量参数)

4.2.3 常量参数 ​ 作为引用参数的替代&#xff0c;您可以使用const参数。由于您无法在例程内为const参数赋予新值&#xff0c;因此编译器可以优化参数传递。编译器可以选择与引用参数相似的方法&#xff08;或者在C术语中是const引用&#xff09;&#xff0c;但行为类似于值参…...

事件在状态流程图中的工作方式

什么是事件&#xff1f; 事件是一个Stateflow对象&#xff0c;它可以触发以下对象中一个动作&#xff1a; Simulink触发子系统 Simulink函数调用子系统 状态流程图 何时使用事件 当你想&#xff1a; 激活Simulink触发的子系统 激活Simulink函数调用子系统 在状态流程图…...

幻兽帕鲁能在Mac上运行吗?幻兽帕鲁Palworld新手攻略

幻兽帕鲁能在Mac上运行吗&#xff1f; 《幻兽帕鲁》目前还未正式登陆Mac平台&#xff0c;不过通过一些方法是可以让游戏在该平台运行的。 虽然游戏不能在最高配置下运行&#xff0c;但如果你安装了CrossOver这个软件&#xff0c;就可以玩了。这是为Mac、Linux和ChromeOS等设计…...

elementPlus实现动态表格单元格合并span-method方法总结

最近在做PC端需求的时候&#xff0c;需要把首列中相邻的同名称单元格合并。 我看了一下elementPlus官网中的table表格&#xff0c;span-method可以实现单元格合并。 我们先看一下官网的例子&#xff1a; 合并行或列 多行或多列共用一个数据时&#xff0c;可以合并行或列。 …...

视频上传 - 断点续传那点事

在上一篇文章中&#xff0c;我们讲解了分片上传的实现方式。在讲解断点续传之前&#xff0c;我要把上篇文章中留下的问题讲解一下。读过上一篇文章的小伙伴们都知道&#xff0c;对于分片上传来说&#xff0c;它的传输方式分为2种&#xff0c;一种是按顺序传输&#xff0c;一种是…...

Scala 和 Java在继承机制方面的区别

Scala 和 Java 都是面向对象编程语言&#xff0c;都支持类的继承机制。然而&#xff0c;尽管两者在基础概念上有很多相似之处&#xff0c;但在具体的实现和语法上&#xff0c;Scala 的继承机制有其独特之处。以下是 Scala 和 Java 在继承方面的一些主要区别&#xff1a; 多重继…...

spark sql上线前的调试工作实现

背景 每个公司应该都有大数据的平台的吧&#xff0c;平台的作用就是可以在上面执行各种spark sql以及定时任务&#xff0c;不过一般来说&#xff0c;由于这些spark sql的上线不经过测试&#xff0c;所以可能会影响到生产的数据&#xff0c;这种情况下大数据平台提供一个上线前…...

java -jar启动SpringBoot项目时配置文件加载位置与优先级

服务部署启动时,我们经常需要指定配置文件启动. 一般有四种,优先级如下 spring.config.location > spring.profiles.active > spring.config.additional-location > 默认的 application.yml 1.spring.config.location 外部配置文件优先级最高 一般配置文件在服务…...

每日一题 力扣LCP30.魔塔游戏

题目描述&#xff1a; 小扣当前位于魔塔游戏第一层&#xff0c;共有 N 个房间&#xff0c;编号为 0 ~ N-1。每个房间的补血道具/怪物对于血量影响记于数组 nums&#xff0c;其中正数表示道具补血数值&#xff0c;即血量增加对应数值&#xff1b;负数表示怪物造成伤害值&#x…...

iPhone搞机记录

-iPhone 8 或以上 设备进入DFU模式的方法&#xff1a; &#xff08;适用&#xff1a;iPhone 8/8 Plus、iPhone X 系列、iPad Pro3 (11-inch)/(12.9-inch)&#xff09; 1.保持设备处于开机或恢复模式下&#xff0c;插入数据线。 2.按一次设备的“音量加键”松开、再按一次“音量…...

Linux中共享内存(mmap函数的使用)

内存映射的基本使用 内存映射 概念&#xff1a; 使一个磁盘文件与内存中的一个缓冲区相映射&#xff0c;进程可以像访问普通内存一样对文件进行访问&#xff0c;不必再调用read,write。 mmap()的优点&#xff1a; 实现了用户空间和内核空间的高效交互方式 优化前&#xff1a;优…...

Golang与Erlang有什么差异

Golang和Erlang是两种备受关注的编程语言&#xff0c;它们各自具有独特的特点和优势。下面我将简单的探讨一下Golang和Erlang之间的差异&#xff0c;并且分析它们在并发模型、运行环境、函数式编程和领域特性等多个方面的不同之处。 并发模型 Golang使用goroutines和channels…...

cesium系列篇:Entity vs Primitive 源码解析(从Entity到Primitive)02

上篇文章中&#xff0c;我们介绍了使用viewer.entities.add添加entity之后的信号传递以及最后entity对象被传递到GeometryVisualizer&#xff1b; 这篇文章&#xff0c;我们则介绍如何在逐帧渲染的过程中根据GeometryVisualizer中的entity对象创建相应的primitive 这是下文中…...

golang windows 环境搭建 环境配置

golang windows 环境搭建 环境配置 Golang学习之路一环境搭建 MacBook Linux 树莓派raspberrypi安装Golang环境 官网下载地址: https://go.dev/dl/ https://golang.google.cn/dl/ 下载对应系统版本&#xff0c;例如windows 64位系统&#xff0c;下载&#xff1a;xxx.window…...

【Git】06 常用场景

文章目录 前言一、场景11.1 删除分支1.2 修改message信息1.2.1 最新一次commit的message1.2.2 过去commit的message 1.3 合并commit1.3.1 多个连续commit合并1.3.2 不连续commit合并 二、场景22.1 比较暂存区和HEAD所含文件的差异2.2 比较工作区和暂存区所含文件的差异2.3 将暂…...

docker下nacos(1.2.0)的持久化

一、创建数据库 运行以下代码自动创建数据库和表 CREATE DATABASE IF NOT EXISTS nacos_config /*!40100 DEFAULT CHARACTER SET utf8 */; USE nacos_config;SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------------------------- -- Table structure for config_…...

Win32 SDK Gui编程系列之--弹出式菜单

1.弹出式菜单 例如,在命令提示窗口中点击鼠标右键,会出现如下图所示的弹出菜单(下拉菜单)。 这种弹出式菜单的实现很简单。不创建菜单栏,用CreatePopupMenu函数创建的菜单是最顶端的菜单就可以了。 菜单的显示使用TrackPopupMenu函数进行。 例如,点击鼠标右键显示弹出…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

浪潮交换机配置track检测实现高速公路收费网络主备切换NQA

浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求&#xff0c;本次涉及的主要是收费汇聚交换机的配置&#xff0c;浪潮网络设备在高速项目很少&#xff0c;通…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...

【Linux】Linux安装并配置RabbitMQ

目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的&#xff0c;需要先安…...

归并排序:分治思想的高效排序

目录 基本原理 流程图解 实现方法 递归实现 非递归实现 演示过程 时间复杂度 基本原理 归并排序(Merge Sort)是一种基于分治思想的排序算法&#xff0c;由约翰冯诺伊曼在1945年提出。其核心思想包括&#xff1a; 分割(Divide)&#xff1a;将待排序数组递归地分成两个子…...