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日

相关文章

  • python利用微信公众号实现报警功能

    通过本次对话,我将为大家详细讲解如何利用Python和微信公众号实现报警功能。 目录 准备工作 注册微信公众号并获取相关信息 开发报警程序 测试报警程序 示例说明 总结 1. 准备工作 在利用Python实现微信公众号报警功能之前,需要准备好以下工具和环境: Python解释器:推荐使用Python3.X。 requests库:用于发送HTTP请求。 wxp…

    database 2023年5月22日
    00
  • MSSQL ISQL命令详解

    MSSQL ISQL命令详解 什么是ISQL? ISQL是指Interactive SQL,是Sybase和Microsoft SQL Server数据库管理系统中,用于交互式操作SQL的命令行工具。 ISQL命令格式 ISQL命令格式如下: isql [ -U login_id ] [ -P password ] [ -S server_name ] [ …

    database 2023年5月21日
    00
  • HTML5 Web Database 数据库的SQL语句的使用方法

    下面是详细讲解“HTML5 Web Database 数据库的SQL语句的使用方法”的完整攻略: 1. HTML5 Web Database简介 HTML5 Web Database是浏览器本地存储数据的一种方式,它能够在浏览器中创建一个SQL数据库,数据以表格的形式存储,并支持SQL语句进行增、删、改、查等操作。HTML5 Web Database使用方便…

    database 2023年5月21日
    00
  • 读取纯真IP数据库的公用组件接口QQWry.NET

    读取纯真IP数据库的公用组件接口QQWry.NET是一个可以在 .NET 平台(C# 或者 VB)上读取纯真IP库的公共组件,支持各种内网、外网、Windows、Linux 等各种环境下的 IP 查询操作。 下面是详细的使用攻略: 1. 下载QQWry.NET组件 QQWry.NET组件可以从官网(https://www.nuget.org/packages…

    database 2023年5月22日
    00
  • 如何使用Python连接和操作SQLite数据库?

    在Python中,可以使用sqlite3模块连接和操作SQLite数据库。以下是Python使用sqlite3模块连接和操作SQLite数据库的完整攻略,包括连接SQLite数据库、表、插入数据、查询数据、更新数据、删除数据等操作。 连接SQLite数据库 在Python中,可以使用sqlite3模块连接SQLite。以下是连接SQLite数据库的基本语法:…

    python 2023年5月12日
    00
  • linux性能调试之vmstat分析

    Linux性能调试之VMStat分析攻略 VMStat是Linux上的一个综合性能监控工具,可以监控系统的CPU、内存、虚拟内存、磁盘I/O等各方面的性能指标,是分析系统瓶颈和优化系统性能的重要工具之一。 使用VMStat进行性能监控 安装VMStat VMStat是Linux系统自带的工具,通常情况下无需进行安装。 启动VMStat 我们可以使用以下命令启…

    database 2023年5月22日
    00
  • MySQL如何建表及导出建表语句

    MySQL是一个常用的关系型数据库管理系统,能够提供方便的数据存储和管理功能。在MySQL中建表是一个非常关键的操作步骤,下面是建表及导出建表语句的详细攻略。 建表步骤 1. 登录MySQL 首先,我们需要通过命令行或图形界面登录到MySQL。在命令行中,可以通过以下命令登录MySQL: mysql -u root -p 其中,-u参数用于指定用户名,-p参…

    database 2023年5月21日
    00
  • mysql中的limit用法有哪些(推荐)

    MySQL中的LIMIT语句是用来限定查询结果返回的行数的。在使用SELECT语句查询数据时,可以使用LIMIT语句进行结果集的分页显示,也可以用于查询前N条或是查询某一区间内的记录。 基础用法 LIMIT语句后面可以跟两个参数:偏移量offset和获取的记录数count。offset表示从查询结果的第几行开始返回数据,count表示需要返回多少行数据。 语…

    database 2023年5月22日
    00
合作推广
合作推广
分享本页
返回顶部