linux内核select/poll,epoll实现与区别

Linux内核select/poll,epoll实现与区别

在Linux内核中,select、poll和epoll是三种常用的网络I/O多路复用机制。其中select和poll是早期的实现方式,epoll是较新的实现方式,相比于前两者具有更好的性能。本文将从多个方面进行介绍,以帮助读者更好地了解它们的实现和区别。

select

select是Unix中最古老的网络I/O多路复用技术,也是目前应用最广泛的多路复用技术。它的实现方式是通过对一组文件描述符进行轮询,当其中某个或某些文件描述符可以进行 I/O 操作时就进行通知。

select的函数接口如下:

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

其中,nfds是待监视的最大文件描述符加一;readfds、writefds、exceptfds用来判断哪些文件描述符可读、可写、出现异常;timeout表示超时时间。

select的缺点是每次调用时都要传递待监视的全部文件描述符集合,系统调用开销较大。而且,文件描述符数量较大时,会耗费大量的内存和 CPU 资源。

poll

poll是一种更加高效的网络I/O多路复用技术,它是在select的基础上进一步优化而来的。

poll的函数接口如下所示:

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

其中,fds代表一组文件描述符和等待的事件,nfds为文件描述符数量,timeout为超时时间。

poll的优点在于可以避免select的缺点,因为poll时只监视需要监视的文件描述符集合,需要轮询的次数也会更少。

epoll

epoll是Linux内核2.5.44以后才开始引入的,是一种新的高效的I/O事件通知机制。和select、poll相比,epoll的性能更优,很好地解决了select和poll在处理大量文件描述符时的效率问题。

epoll的函数接口如下所示:

int epoll_create(int size);

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

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

其中,epoll_create用来创建一个epoll监听器,size表示监听器支持的最大文件描述符数量。epoll_ctl则用来对文件描述符进行注册、修改或删除操作。epoll_wait则等待I/O事件的发生。

epoll的优点在于它支持"边缘触发"(edge-triggered)和"水平触发"(level-triggered),可以更加灵活地应对事件。当某个文件可读时,只发出一次通知,而不是像select、poll那样会一直通知,有助于减少轮询次数和CPU负载。此外,epoll还可以支持大量的文件描述符,可获得更高的并发性。

下面提供一个示例来说明select、poll和epoll的实现和区别:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/poll.h>
#include <sys/epoll.h>

#define MAXLINE 1024
#define OPEN_MAX 100

void use_select() {
    int i, listenfd, connfd, sockfd;
    int nready, efd;
    ssize_t n;
    char buf[MAXLINE];
    socklen_t clilen;
    struct sockaddr_in cliaddr, servaddr;
    struct timeval tv;

    int maxi = 0;
    int client[FD_SETSIZE];

    fd_set rset, allset;
    /* set default port and ip familiy */
    int port = 8888;
    int family = AF_INET;

    listenfd = socket(family, SOCK_STREAM, 0);
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = family;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);

    bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    listen(listenfd, 20);

    maxfd = listenfd;
    maxi = -1;
    for(i = 0; i < FD_SETSIZE; ++i) {
        client[i] = -1;
    }
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);

    printf("start select loop\n");

    for( ; ; ) {
        rset = allset;
        tv.tv_sec = 5;
        tv.tv_usec = 0;

        nready = select(maxfd+1, &rset, NULL, NULL, &tv);
        if(nready == -1) {
            perror("select error");
            break;
        }

        if(nready == 0) {
            printf("timeout..\n");
            continue;
        }

        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;
                }
            }

            if(i == FD_SETSIZE) {
                printf("too many clients\n");
            }

            FD_SET(connfd, &allset);
            if(connfd > maxfd) {
                maxfd = connfd;
            }

            if(i > maxi) {
                maxi = i;
            }

            if(--nready <= 0) {
                continue;
            }
        }

        for(i = 0; i <= maxi; ++i) {
            if((sockfd = client[i]) < 0) {
                continue;
            }

            if(FD_ISSET(sockfd, &rset)) {
                if((n = read(sockfd, buf, MAXLINE)) == 0) {
                    close(sockfd);
                    FD_CLR(sockfd, &allset);
                    client[i] = -1;
                } else {
                    write(sockfd, buf, n);
                }

                if(--nready <= 0) {
                    break;
                }
            }
        }
    }
}

void use_poll() {
    int i, maxi, listenfd, connfd, sockfd;
    int nready;
    ssize_t n;
    char buf[MAXLINE];
    socklen_t clilen;
    struct sockaddr_in cliaddr, servaddr;

    struct pollfd client[OPEN_MAX];
    int maxfd;
    /* set default port and ip familiy */
    int port = 8888;
    int family = AF_INET;

    listenfd = socket(family, SOCK_STREAM, 0);
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = family;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);

    bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    listen(listenfd, 20);

    maxfd = 0;
    for(i = 0; i < OPEN_MAX; ++i) {
        client[i].fd = -1;
    }
    client[0].fd = listenfd;
    client[0].events = POLLRDNORM;

    maxi = 0;
    printf("start poll loop\n");
    for( ; ; ) {
        nready = poll(client, maxi+1, 5000);
        if(nready < 0) {
            perror("poll error");
            break;
        }

        if(nready == 0) {
            printf("timeout..\n");
            continue;
        }

        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;
                    break;
                }
            }

            if(i == OPEN_MAX) {
                printf("too many clients\n");
            }

            client[i].events = POLLRDNORM;
            if(i > maxi) {
                maxi = i;
            }

            if(--nready <= 0) {
                continue;
            }
        }

        for(i = 1; i <= maxi; ++i) {
            if((sockfd = client[i].fd) < 0) {
                continue;
            }

            if(client[i].revents & (POLLRDNORM|POLLERR)) {
                if((n = read(sockfd, buf, MAXLINE)) < 0) {
                    if(errno == ECONNRESET) {
                        close(sockfd);
                        client[i].fd = -1;
                    } else {
                        perror("read error");
                    }
                } else if(n == 0) {
                    close(sockfd);
                    client[i].fd = -1;
                } else {
                    write(sockfd, buf, n);
                }

                if(--nready <= 0) {
                    break;
                }
            }
        }
    }
}

void use_epoll() {
    int i, maxi, listenfd, connfd, sockfd;
    ssize_t n;
    char buf[MAXLINE];
    socklen_t clilen;
    struct sockaddr_in cliaddr, servaddr;

    struct epoll_event ev, events[20];
    int epfd;
    /* set default port and ip familiy */
    int port = 8888;
    int family = AF_INET;

    listenfd = socket(family, SOCK_STREAM, 0);
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = family;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);

    bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    listen(listenfd, 20);

    epfd = epoll_create(256);
    ev.data.fd = listenfd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);

    printf("start epoll loop\n");
    for( ; ; ) {
        int nready = epoll_wait(epfd, events, 20, 5000);
        if(nready < 0) {
            perror("epoll_wait");
            break;
        }

        if(nready == 0) {
            printf("timeout\n");
            continue;
        }

        for(i = 0; i < nready; ++i) {
            if(events[i].data.fd == listenfd) {
                clilen = sizeof(cliaddr);
                connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen);

                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;
                }

                if((n = read(sockfd, buf, MAXLINE)) < 0) {
                    if(errno == ECONNRESET) {
                        close(sockfd);
                        events[i].data.fd = -1;
                    } else {
                        perror("read error");
                    }
                } else if(n == 0) {
                    close(sockfd);
                    events[i].data.fd = -1;
                } else {
                    write(sockfd, buf, n);
                }
            }
        }
    }
}

int main(int argc, char **argv) {
    use_select();
    //use_poll();
    //use_epoll();
    return 0;
}

上述代码中,提供了一个TCP服务器的实现,并分别使用了select、poll和epoll三种多路复用技术。在测试过程中,可以更好地了解select、poll和epoll的实现和不同之处。

结论

在实现TCP服务器时,使用select、poll和epoll进行多路复用时均可以将 CPU 资源分配给更多的连接,并使程序的处理方式更加灵活。但是,在文件描述符数量较多时,select和poll的效率会有比较大的下降,而epoll则能很好地避免这个问题,并且支持边沿触发和水平触发等更高级的事件模型,使得程序的性能有了大幅提升。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:linux内核select/poll,epoll实现与区别 - Python技术站

(0)
上一篇 2023年5月22日
下一篇 2023年5月22日

相关文章

  • mysqldump备份还原和mysqldump导入导出语句大全详解

    mysqldump备份还原和mysqldump导入导出语句大全详解 1. mysqldump备份 1.1 备份单个数据库 使用以下命令备份单个数据库: mysqldump -h localhost -u root -p database_name > backup_file.sql 其中,localhost代表MySQL服务器的地址,root是数据库用…

    database 2023年5月22日
    00
  • Mysql中如何查看执行计划

    在 Mysql 中查看执行计划,可以帮助我们优化查询语句,提高查询效率。下面是具体的步骤: 在执行查询语句之前,先使用 “EXPLAIN” 关键字查看 SQL 语句的执行计划。 EXPLAIN SELECT * FROM table_name WHERE condition; 这会输出一张表格,其中包含了 MySQL 优化器如何执行查询语句的详细信息。 执行…

    database 2023年5月22日
    00
  • Python3与SQLServer、Oracle、MySql的连接方法

    环境: python3.4 64bit pycharm2018社区版 64bit Oracle 11 64bit SQLServer· Mysql 其中三种不同的数据库安装在不同的服务器上,通过局域网相连 步骤1:在pycharm上安装相应的包,可通过pip或者其他方式 步骤2:import这些包 import pymysql,pymssql,cx_Orac…

    MySQL 2023年4月16日
    00
  • PowerShell 自动备份oracle并上传到ftp

    为了详细讲解“PowerShell 自动备份Oracle并上传到FTP”的完整攻略,请按照以下步骤进行操作: 1. 安装必要的软件 为了实现该功能,需要安装以下软件: Oracle Instant Client (用于连接和备份Oracle数据库) WinSCP(用于上传备份文件到FTP服务器) PowerShell(用于编写和执行PowerShell脚本)…

    database 2023年5月22日
    00
  • 在oracle 数据库中查看一个sql语句的执行时间和SP2-0027错误

    要在Oracle数据库中查看一个SQL语句的执行时间和SP2-0027错误,需要以下步骤: 打开SQL*Plus命令行界面。 在SQL*Plus命令行界面中输入以下命令: SET TIMING ON; SET AUTOTRACE TRACEONLY; 其中,SET TIMING ON命令用于开启计时器,SET AUTOTRACE TRACEONLY命令用于开…

    database 2023年5月21日
    00
  • Linux系统下实现远程连接MySQL数据库的方法教程

    下面是“Linux系统下实现远程连接MySQL数据库的方法教程”的完整攻略: 准备工作 安装MySQL服务端和客户端: sudo apt install mysql-server mysql-client 配置MySQL服务端允许远程登录: 找到 /etc/mysql/mysql.conf.d/mysqld.cnf 文件,将以下内容的注释取消,并将其中的 b…

    database 2023年5月22日
    00
  • MySQL数据库表空间回收的解决

    MySQL数据库表空间回收是一个重要的问题,它关系到数据库的空间效率和性能。当数据库中的表被删除或者表中的数据被删除时,MySQL并不会立即将表占用的磁盘空间释放出来,而是将这些空闲的磁盘空间标记为“已用”,等待下一次写入操作时再用到。 这样,就会造成数据库的空间浪费,同时也会影响数据库的性能。为了解决这个问题,我们可以采取如下方法: 方法一:利用OPTIM…

    database 2023年5月19日
    00
  • Redis TTL 为0

    地址: http://get.jobdeer.com/7297.get 一次Redis TTL 为0的问题排查 事情是这样的,今天中午业务突然RTX上找我,说一个新建的Twemproxy集群数据查询的时候出了问题,Redis的TTL返回为0,让我帮忙看一看:  当时听完就觉得问题很诡异,按照之前的经验来说,Redis的TTL怎么也不可能为0啊,见:http:…

    Redis 2023年4月12日
    00
合作推广
合作推广
分享本页
返回顶部