Linux--IO多路复用(select,poll,epoll)

news2024/9/21 2:47:43

IO多路复用——select,poll,epoll

IO多路复用是一种操作系统技术,旨在提高系统处理多个输入输出操作的性能和资源利用率。与传统的多线程或多进程模型相比,IO多路复用避免了因阻塞IO而导致的资源浪费和低效率问题。它通过将多个IO操作合并到一个系统调用中,允许程序同时等待多个文件描述符(如sockets、文件句柄等)变为可读或可写状态,然后再执行实际的IO操作。

在IO多路复用的实现中,常用的系统调用包括select()poll()epoll()。这些机制允许程序监视多个描述符,一旦某个描述符就绪(通常是读就绪或写就绪),程序就会被通知进行相应的读写操作。这个过程通常涉及两个阶段:

  1. 等待数据到达:程序等待数据从IO设备传输到内核空间。在这个阶段,IO多路复用的系统调用会阻塞,直到至少有一个描述符准备好进行IO操作。

  2. 数据复制:当一个或多个描述符就绪时,程序负责将数据从内核空间复制到用户空间(进程或线程的缓冲区)。这第二个阶段是实际的读写操作,它在IO多路复用的上下文中是同步的,因为程序需要自己执行数据的读写。

尽管select()poll()epoll()都是同步IO操作,但它们提供了一种有效的方式来处理并发IO,降低了系统开销,并提高了并发处理能力。与此不同,异步IO(AIO)模型进一步简化了IO操作,因为它允许操作系统自动处理数据从内核到用户空间的复制过程,无需程序显式调用读写操作。这意味着在异步IO模型中,读写操作由操作系统在后台完成,从而进一步提高了应用程序的效率和响应性。

a411a9791264d372df052436f83741e8.png

select

概述

  • 系统提供了select函数来实现多路复用输入/输出模型

  • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的

  • 程序会停在select函数等待,直到被监视的文件描述符有一个或者多个发生了状态改变。

c03874c465c6dc86eb3b53af27bd076b.png

函数

int select(int nfds, fd_set *readfds, fd_set *writefds,
    fd_set *exceptfds, struct timeval *timeout);
  • 函数参数:

参数说明
nfds是需要监视的最大的文件描述符值+1
readfds需要检测的可读文件描述符的集合
writefds需要检测的可写文件描述符的集合
exceptfds需要检测的异常文件描述符的集合
timeout当timeout等于NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件;
当timeout为0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
当timeout为特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。
返回——
> 0返回文件描述词状态已改变的个数
== 0代表在描述词状态改变前已超过timeout时间,没有返回
< 0错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的值变成不可预测,错误值可能为:
EBADF:文件描述词为无效的或该文件已关闭
EINTR:此调用被信号所中断
EINVAL:参数n 为负值
ENOMEM:核心内存不足
  • 其中:可读,可写,异常文件描述符的集合是一个fd_set类型,fd_set是系统提供的位图类型,位图的位置是否是1,表示是否关系该事件。例如:

输入时:假如我们要关心 0 1 2 3 文件描述符
    0000 0000->0000 1111 比特位的位置,表示文件描述符的编号
    比特位的内容 0 or 1 表示是否需要内核关心
输出时:
    0000 0100->此时表示文件描述符的编号
    比特位的内容 0 or 1哪些用户关心的fd 上面的读事件已经就绪了,这里表示2描述符就绪了
  • 系统提供了关于fd_set的接口,便于我们使用位图:

void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位

执行流程:

  1. 执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。

  2. 若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1) 。

  3. 若再加入fd=2,fd=1,则set变为0001,0011 。

  4. 执行select(6,&set,0,0,0)阻塞等待,表示最大文件描述符+1是6,监控可读事件,立即返回。

  5. 若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

优缺点

  • 优点:

  1. 可监控的文件描述符个数取决与sizeof(fd_set)的值。一般大小是1024,但是fd_set的大小可以调整。

  2. 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd。①是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。②是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

缺点:

  1. 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便。

  2. 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。

  3. 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。

  4. select支持的文件描述符数量太小。

实例

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/select.h>

const static int MAXLINE = 1024;
const static int SERV_PORT = 10001;

int main()
{
    int i , maxi , maxfd, listenfd , connfd , sockfd ;
    /*nready 描述字的数量*/
    int nready ,client[FD_SETSIZE];
    int n ;
    /*创建描述字集合,由于select函数会把未有事件发生的描述字清零,所以我们设置两个集合*/
    fd_set rset , allset;
    char buf[MAXLINE];
    socklen_t clilen;
    struct sockaddr_in cliaddr , servaddr;
    /*创建socket*/
    listenfd = socket(AF_INET , SOCK_STREAM , 0);
    /*定义sockaddr_in*/
    memset(&servaddr , 0 ,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    bind(listenfd, (struct sockaddr *) & servaddr , sizeof(servaddr));
    listen(listenfd , 100);
    /*listenfd 是第一个描述字*/
    /*最大的描述字,用于select函数的第一个参数*/
    maxfd = listenfd;
    /*client的数量,用于轮询*/
    maxi = -1;
    /*init*/
    for(i=0 ;i<FD_SETSIZE ; i++)
        client[i] = -1;
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);

    for (;;)
    {
        rset = allset;
        /*只select出用于读的描述字,阻塞无timeout*/
        nready = select(maxfd+1 , &rset , NULL , NULL , NULL);
        if(FD_ISSET(listenfd,&rset))
        {
            clilen = sizeof(cliaddr);
            connfd = accept(listenfd , (struct sockaddr *) & cliaddr , &clilen);
            /*寻找第一个能放置新的描述字的位置*/
            for (i=0;i<FD_SETSIZE;i++)
            {
                if(client[i]<0)
                {
                    client[i] = connfd;
                    break;
                }
            }
            /*找不到,说明client已经满了*/
            if(i==FD_SETSIZE)
            {
                printf("Too many clients , over stack .\n");
                return -1;
            }
            FD_SET(connfd,&allset);//设置fd
            /*更新相关参数*/
            if(connfd > maxfd) maxfd = connfd;
            if(i>maxi) maxi = i;
            if(nready<=1) continue;
            else nready --;
        }

        for(i=0 ; i<=maxi ; i++)
        {
            if (client[i]<0) continue;
            sockfd = client[i];
            if(FD_ISSET(sockfd,&rset))
            {
                n = read(sockfd , buf , MAXLINE);
                if (n==0)
                {
                    /*当对方关闭的时候,server关闭描述字,并将set的sockfd清空*/
                    close(sockfd);
                    FD_CLR(sockfd,&allset);
                    client[i] = -1;
                }
                else
                {
                    buf[n]='\0';
                    printf("Socket %d said : %s\n",sockfd,buf);
                    write(sockfd,buf,n); //Write back to client
                }
                nready --;
                if(nready<=0) break;
            }
        }

    }
    return 0;
}

poll

概述

  • poll和select实现原理基本类似

  • poll只为了解决select的两个硬伤:①等待的fd是有上限的,(底层类似链表储存实现,而不是位图)。②每次要对关心的fd进行事件重置,(pollfd结构包含了要监视的event和发生的event,使用前后不用初始化fd_set)。

函数

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

// pollfd结构
struct pollfd {
 int fd; /* file descriptor */
 short events; /* requested events */
 short revents; /* returned events */
};
  • 函数参数:

参数说明
fds是一个poll函数监听的结构列表. 每一个元素中, 包含了三部分内容: 文件描述符, 监听的事件集合, 返回的事件集合
nfds表示fds数组的长度
timeout表示poll函数的超时时间, 单位是毫秒(ms)
返回——
> 0表示poll由于监听的文件描述符就绪而返回
== 0表示poll函数等待超时
< 0表示出错

优缺点

  • 优点:

  1. pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式. 接口使用比 select更方便。

  2. poll并没有最大数量限制 (但是数量过大后性能也是会下降)。

缺点:

  1. 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

  2. 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中。

  3. 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态, 因此随着监视的描述符数量的增长, 其效率也会线性下降。

实例

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
 
#define MAXLINE  1024
#define OPEN_MAX  16 //一些系统会定义这些宏
#define SERV_PORT  10001
 
int main()
{
    int i , maxi ,listenfd , connfd , sockfd ;
    int nready;
    int n;
    char buf[MAXLINE];
    socklen_t clilen;
    struct pollfd client[OPEN_MAX];
 
    struct sockaddr_in cliaddr , servaddr;
    listenfd = socket(AF_INET , SOCK_STREAM , 0);
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 
    bind(listenfd , (struct sockaddr *) & servaddr, sizeof(servaddr));
    listen(listenfd,10);
    client[0].fd = listenfd;
    client[0].events = POLLRDNORM;
    for(i=1;i<OPEN_MAX;i++)
    {
        client[i].fd = -1;
    }
    maxi = 0;
 
    for(;;)
    {
        nready = poll(client,maxi+1,INFTIM);
        if (client[0].revents & POLLRDNORM)
        {
            clilen = sizeof(cliaddr);
            connfd = accept(listenfd , (struct sockaddr *)&cliaddr, &clilen);
            for(i=1;i<OPEN_MAX;i++)
            {
                if(client[i].fd<0)
                {
                    client[i].fd = connfd;
                    client[i].events = POLLRDNORM;
                    break;
                }
            }
            if(i==OPEN_MAX)
            {
                printf("too many clients! \n");
            }
            if(i>maxi) maxi = i;
            nready--;
            if(nready<=0) continue;
        }
 
        for(i=1;i<=maxi;i++)
        {
            if(client[i].fd<0) continue;
            sockfd = client[i].fd;
            if(client[i].revents & (POLLRDNORM|POLLERR))
            {
                n = read(client[i].fd,buf,MAXLINE);
                if(n<=0)
                {
                    close(client[i].fd);
                    client[i].fd = -1;
                }
                else
                {
                    buf[n]='\0';
                    printf("Socket %d said : %s\n",sockfd,buf);
                    write(sockfd,buf,n); //Write back to client
                }
                nready--;
                if(nready<=0) break; //no more readable descriptors
            }
        }
    }
    return 0;
}

epoll

概述

  • epoll:是为处理大批量句柄而作了改进的poll(真的是大改进)

  • epoll是IO多路复用技术,在实现上维护了一个用于返回触发事件的Socket的链表和一个记录监听事件的红黑树,epoll的高效体现在:

  1. 对监听事件的修改是 logN(红黑树)。

  2. 用户程序无需遍历所有的Socket(发生事件的Socket被放到链表中直接返回)。

  3. 内核无需遍历所有的套接字,内核使用回调函数在事件发生时直接转到对应的处理函数。

8dbb14ec649a268aecc8256acaf71f27.png

函数

  • epoll_create:创建一个epoll的句柄,用完之后, 必须调用close()关闭。

int epoll_create(int size);
  • epoll_ctl:它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型。

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

typedef union epoll_data
{
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

struct epoll_event
{
    uint32_t events;
    epoll_data_t data;
} EPOLL_PACKED;
  1. events参数的宏集合:

EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭)。
EPOLLOUT : 表示对应的文件描述符可以写。
EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来)。
EPOLLERR : 表示对应的文件描述符发生错误。
EPOLLHUP : 表示对应的文件描述符被挂断。
EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里
  1. 函数参数:

参数说明
epfdepoll_create()的返回值(epoll的句柄)
op表示动作,用三个宏来表示:
EPOLL_CTL_ADD :注册新的fd到epfd中
EPOLL_CTL_MOD :修改已经注册的fd的监听事件
EPOLL_CTL_DEL :从epfd中删除一个fd
fd需要监听的fd
event内核需要监听的事件
  • epoll_wait:收集在epoll监控的事件中已经发送的事件

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
参数说明
epfdepoll_create()的返回值(epoll的句柄)
events是分配好的epoll_event结构体数组。epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)
maxevents通知内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size
timeout超时时间 (毫秒,0会立即返回,-1是永久阻塞)
返回——
> 0返回对应I/O上已准备好的文件描述符数目
== 0表示已超时
< 0表示失败

执行流程:

6c287cb29f3ab1f41e4283b5269b21be.png

  1. 当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。

  2. 每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。

  3. 这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。

  4. 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法。

  5. 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

  6. 在epoll中,对于每一个事件,都会建立一个epitem结构体。

  7. 当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。

  8. 如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度是O(1)。

优缺点

  • 优点:

  1. 接口使用方便: 虽然拆分成了三个函数,但是反而使用起来更方便高效,不需要每次循环都设置关注的文件描述符,也做到了输入输出参数分离开。

  2. 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中,这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)。

  3. 事件回调机制: 避免使用遍历,而是使用回调函数的方式,将就绪的文件描述符结构加入到就绪队列中,epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪,这个操作时间复杂度O(1),即使文件描述符数目很多,效率也不会受到影响。

  4. 没有数量限制: 文件描述符数目无上限。

缺点:

  1. 不能跨平台,epoll 是 Linux 特有的 API,不太容易移植到其他操作系统上

实例

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/resource.h>
 
#define MAXLINE  1024
#define OPEN_MAX  16 //一些系统会定义这些宏
#define SERV_PORT  10001
 
int main()
{
    int i , maxi ,listenfd , connfd , sockfd ,epfd, nfds;
    int n;
    char buf[MAXLINE];
    struct epoll_event ev, events[20];  
    socklen_t clilen;
    struct pollfd client[OPEN_MAX];
 
    struct sockaddr_in cliaddr , servaddr;
    listenfd = socket(AF_INET , SOCK_STREAM , 0);
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
 
    bind(listenfd , (struct sockaddr *) & servaddr, sizeof(servaddr));
    listen(listenfd,10);
    
    epfd = epoll_create(256);
    ev.data.fd=listenfd; 
    ev.events=EPOLLIN|EPOLLET;
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
    
    for(;;)
    {
        nfds=epoll_wait(epfd,events,20,500); 
        for(i=0; i<nfds; i++)
        {
            if (listenfd == events[i].data.fd)
            {
                clilen = sizeof(cliaddr);
                connfd = accept(listenfd , (struct sockaddr *)&cliaddr, &clilen);
                if(connfd < 0)  
                {  
                    perror("connfd < 0");  
                    exit(1);  
                }
                ev.data.fd=connfd; 
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);                
            }
            else if (events[i].events & EPOLLIN)
            {
                if ( (sockfd = events[i].data.fd) < 0)  
                    continue;  
                n = recv(sockfd,buf,MAXLINE,0);
                if (n <= 0)   
                {    
                    close(sockfd);  
                    events[i].data.fd = -1;  
                }
                else
                {
                    buf[n]='\0';
                    printf("Socket %d said : %s\n",sockfd,buf);
                    ev.data.fd=sockfd; 
                    ev.events=EPOLLOUT|EPOLLET;
                    epoll_ctl(epfd,EPOLL_CTL_MOD,connfd,&ev);
                }
            }
            else if( events[i].events&EPOLLOUT )
            {
                sockfd = events[i].data.fd;  
                send(sockfd, "Hello!", 7, 0);  
                  
                ev.data.fd=sockfd;  
                ev.events=EPOLLIN|EPOLLET;  
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); 
            }
            else 
            {
                printf("This is not avaible!");
            }
        }
    }
    close(epfd);  
    return 0;
}

总结

  • selectpoll 是两种传统的 I/O 多路复用技术,它们允许服务器应用程序同时监控多个网络连接,以便在连接准备就绪时进行读写操作。尽管这两种技术在处理大量并发连接时非常有用,但随着连接数的增加,它们的性能会逐渐下降,因为它们需要在每次调用时遍历整个文件描述符集合,这在连接数非常多时会导致效率问题。

  • 为了解决这个问题,epoll 作为 selectpoll 的一种改进方案,在 Linux 系统中被引入。epoll 提供了一种更为高效的事件驱动模型,它可以显著提高处理大量并发连接的性能。与 selectpoll 不同,epoll 不会对整个文件描述符集合进行线性遍历,而是使用一组特殊的数据结构来跟踪哪些文件描述符已经准备好 I/O 操作。这种机制使得 epoll 能够快速地通知应用程序哪些连接是活跃的,而无需对所有连接进行不必要的检查。

  • epoll 的另一个优点是它能够处理大量文件描述符而不会显著增加资源消耗,这使得它非常适合需要处理成千上万甚至更多并发连接的高性能网络服务器。因此,在 Linux 系统上,epoll 常被视为 selectpoll 的替代方案,特别是在构建高性能网络应用程序时。

- END -

往期推荐:点击图片即可跳转阅读

dd2e621c8781f0abdcc8578f074741bc.jpeg

《linux--sysfs文件系统》

f84ff51a6c863e2f55ce975e1b8269b5.jpeg

《YY3568 Debian11+RT-Thread混合内核部署》

f4bdcfd3ea5deb2cbcb96dea5c4ac460.jpeg

《YY3568多核异构(Linux+RT-Thread)--启动流程》

9fc8bb14e4398c7a5cf31450fd857e95.png

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

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

相关文章

使用matplotlib可视化dataframe:让你的数据更生动有趣

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 1. 简介 在数据分析和可视化领域&#xff0c;matplotlib是一个非常强大的工具。它可以帮助将数据以图形的形式展示出来&#xff0c;使得数据更加直观、易于理解。本文将介绍如何使用matplotlib来可视化pandas中的Da…

小q的数列(c语言)

1./描述 //小q最近迷上了各种好玩的数列&#xff0c;这天&#xff0c;他发现了一个有趣的数列&#xff0c;其递推公式如下&#xff1a; // //f[0] 0 f[1] 1; //f[i] f[i / 2] f[i % 2]; (i > 2) // //现在&#xff0c;他想考考你&#xff0c;问&#xff1a;给你一个n&am…

趣映 v2.3.8 — 高级版,专注动画制作,自媒体变现

趣映是一款专注于为动画垂直账号提供全面视频编辑和制作功能的软件&#xff0c;支持从灵感创作到成片输出的全流程。海量模板助您一键制作爆款动画视频和动漫视频&#xff0c;适配各种场景。此版本已解锁高级功能&#xff0c;提供更丰富的编辑工具和模板。 链接&#xff1a;ht…

MySQL数据库60道面试题 ( 附答案 )

2024的4月招聘季如此卷&#xff0c;没点真本事真技术&#xff0c;没点面试经验&#xff0c;不了解点职场套路&#xff0c;如何过五关斩六将&#xff1f; 找工作最重要的一步自然是面试&#xff0c;马上跳槽季&#xff0c;网上出现了各种面试题&#xff0c;一时会让人眼花缭乱&…

外排序之⽂件归并排序实现

外排序之⽂件归并排序实现 外排序介绍 外排序&#xff08;External sorting&#xff09;是指能够处理极⼤量数据的排序算法。通常来说&#xff0c;外排序处理的数据不能 ⼀次装⼊内存&#xff0c;只能放在读写较慢的外存储器(通常是硬盘)上。外排序通常采⽤的是⼀种“排序-归 …

Spring+SpringWeb+MyBatis三大框架整合教程 实现先前后端交互搭建

目录 1. 创建项目 2. 导入所依赖的 jar 包 3. 配置MyBatis 4. 配置spring事务管理 5. 配置Spring * Spring配置代码解读 6. dao层 7. mappers映射文件 8. common层 9. service层 10. web层 11. 测试 ssm大合体&#xff01; 1. 创建项目 &#x1f4cd;创建一个J…

【C++ Primer Plus习题】6.7

问题: 解答: #include <iostream> #include <cctype> using namespace std;int main() {string words;int vowel 0;int consonant 0;int other0;cout << "请输入一个单词(q结束):";cin >> words;while (words!"q"){if (!isalph…

编成笔记-atan2函数学习分析

分析atan2函数 1. 前言 2. atan2函数分析 tanθy/x : 当(x,y) 在第一象限&#xff0c;0 < θ < π/2当(x,y) 在第二象限&#xff0c;π/2 < θ ≤ π当(x,y) 在第三象限&#xff0c;− π < θ < − π/2当(x,y) 在第四象限&#xff0c;− π/2 < θ <…

动态读取nacos中修改的项目配置文件

本项目用的还是springboot项目&#xff0c;咱们直接上代码 一&#xff1a;首先看下nacos中需要动态获取的属性 二&#xff1a;把需要动态读取的配置类中的属性整理一个实体类 mport lombok.Data; import org.springframework.boot.context.properties.ConfigurationPropert…

微信公众号等工具 3 — 使用 Markdown Nice 写文章

文章目录 操作步骤STEP 1. 进入微信公众号文章编辑界面STEP 2. Markdown Nice 将 3 个重要的功能嵌入到了微信公众号编辑器中STEP 3. 在 Markdown Nice 界面编辑内容STEP 4. 导入/粘贴/直接在编辑器中编辑 Markdown → 点击左下角的预览效果 操作步骤 STEP 1. 进入微信公众号文…

BUUCTF二维码1

九张撕碎二维码碎片。不会让人拼起来吧&#xff01;看了大神们得博客竟然是真的&#xff0c;这是ctf的题吗&#xff01;是考验人的耐性吧&#xff01; 我勉为其难讲一下PS怎么拼图&#xff0c;首先要把九张碎片抠图&#xff0c;背景变透明&#xff0c;ps可以但是太麻烦&#xf…

(亲测有效)spring cloud+Vue微服务项目云服务器部署(宝塔)

我的另一篇博客&#xff0c;有兴趣可以看看&#xff0c;部署思路都是一样的。 &#xff08;亲测有效&#xff09;SpringBootVue项目云服务器部署&#xff08;宝塔&#xff09;_springboot 宝塔部署-CSDN博客 目录 一、准备工作 购买云服务器 登录云服务器 安装宝塔 二、jdk…

项目技巧二

java中Date和mysql数据库datetime数据类型 数据库中的 datetime 类型&#xff1a; 大多数关系型数据库&#xff08;如 MySQL, SQL Server, PostgreSQL 等&#xff09;都提供了 datetime 类型&#xff0c;用于存储日期和时间信息。这些数据库中的 datetime 类型通常遵循 ISO 86…

金九银十跳槽季,最新自动化测试面试题合集

前言 Hello,大家好。金九银十也不远了&#xff0c;有的人盼望升职加薪&#xff0c;有的人立了新的Flag&#xff0c;有跳槽计划的该提上日程了。为解大伙的燃眉之急&#xff0c;今天分享自动化面试题预热一波&#xff0c;欢迎留言区补充评论&#xff01; 一、请描述一下自动化测…

sqli-labs靶场通关攻略(四十一到五十关)

sqli-labs-master靶场第四十一关 一&#xff0c;查看数据库 ?id-1 union select 1,2,database()-- 二&#xff0c;查看表名 ?id-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schemadatabase()-- 三&#xff0c;查看users表中…

python学习之路 - python对mysql的数据操作

目录 一、python对mysql的数据操作1、前期准备2、连接mysql3、创建表5、插入表4、查询表 一、python对mysql的数据操作 1、前期准备 使用python对mysql进行相关操作前&#xff0c;需要安装pymysql。执行pip install pymysql命令即可如果具体不知道如何操作&#xff0c;可以查…

导入pyBigWig包

今天复现论文时&#xff0c;看到了一种叫做bigwig格式的数据&#xff0c;创建和访问该格式文件需要用到pybigwig包&#xff0c;在此过程中遇到了一些问题&#xff0c;记录一下。 介绍 pybigwig的使用依赖于两个C库&#xff0c;所谓C库就是C语言编写的python库。 正如在pypi官…

聚类算法-DBSCAN

文章目录 一、DBSCAN介绍1.含义2.DBSCAN 的核心概念3.DBSCAN算法参数 二、代码实现1.数据预处理2.DBSCAN聚类3.计算轮廓系数4.全部代码 三、总结 一、DBSCAN介绍 1.含义 DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;是一种基…

llama-cpp-python编译失败,解决方案安装wheel文件

https://abetlen.github.io/llama-cpp-python/whl/cu121/llama-cpp-python/编译失败&#xff1a; 解决方案&#xff1a;使用轮子镜像

Clion/Vs中wcout输出中文不显示的解决办法

本来要写个输出所有窗口的代码&#xff0c;但是结果文字一直不输出&#xff0c;又试了试发现只是汉字不输出&#xff1a; #include <windows.h> #include <iostream> #include <vector> #include <string>std::vector<std::wstring> windowTitl…