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

yizhihongxing

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日

相关文章

  • 在SQL Server和Oracle中创建job

    创建Job是数据库管理员和开发人员必须掌握的技能之一,下面我将详细讲解如何在SQL Server和Oracle中创建Job。 在SQL Server中创建Job 步骤1:打开SQL Server Management Studio 首先,打开SQL Server Management Studio并连接到需要创建Job的数据库实例。 步骤2:新建Job 在S…

    database 2023年5月21日
    00
  • swoole+websocket+redis实现一对一聊天

    如同web端的QQ和微信一样,这是一个web端的聊天程序。 环境:linux(centos) + php7.2 + swoole扩展 + redis + mysql Redis 实现每个连接websocket的服务都唯一绑定一个用户。通过 用户账号 = websocket fd 存到redis中。 Mysql 实现离线消息池。如果一个用户不在线,则其他用户发…

    Redis 2023年4月11日
    00
  • 在Redhat9上安装Oracle 9.2

    下面是详细的Redhat9上安装Oracle 9.2的攻略: 准备工作 系统需求 Red Hat Linux Advanced Server 2.1, 3.0,或 Red Hat Enterprise Linux AS 3.0 具备 256MB 的内存,并保留 384MB 的虚拟内存空间 必须拥有 root 权限 软件需求 Oracle 9.2 安装程序 R…

    database 2023年5月22日
    00
  • Spark学习笔记(一)Spark初识【特性、组成、应用】

    Spark学习笔记(一)Spark初识:特性、组成与应用 什么是Spark? Spark是一种基于内存的大数据处理框架。它提供了一个分布式计算引擎,可在大规模数据集上迅速进行计算。Spark可以跨越多个计算平台,包括Hadoop、Mesos、Kubernetes等。 Spark的特性 Spark的特点可以总结如下: 更快的速度:Spark通过内存计算和更好的…

    database 2023年5月22日
    00
  • 虚拟机linux安装redis实现过程解析

    下面我将详细讲解“虚拟机linux安装redis实现过程解析”的完整攻略。 准备工作 在安装redis前,需要先安装虚拟机和Linux系统。我们这里以Vmware Workstation Pro虚拟机和Ubuntu 20.04 LTS Linux系统为例。 安装redis 步骤1:安装redis 打开终端,输入以下命令安装redis: sudo apt up…

    database 2023年5月22日
    00
  • pymysql 插入数据 转义处理方式

    当使用pymysql向MySQL数据库中插入数据时,需要注意字符串中可能含有引号、单引号、反斜杠等特殊字符,这些字符可能导致SQL语句语法出现错误。为了避免这种情况,需要使用转义处理方式,将特殊字符转换为可以被SQL语句安全接收的形式。 以下是pymysql插入数据的转义处理方式: 使用pymysql.escape_string()函数 pymysql.es…

    database 2023年5月22日
    00
  • linux mysql 数据库开启外部访问设置指南

    下面是详细的“linux mysql 数据库开启外部访问设置指南”。 介绍 默认情况下,MySQL数据库只允许本地(localhost)连接,而不允许通过网络连接。为了能够实现远程连接,需要进行一些设置。本文将讲解如何在 Linux 操作系统下,开启 MySQL 数据库的外部访问设置。 步骤 1. 打开 MySQL 配置文件 MySQL 配置文件一般是在 /…

    database 2023年5月22日
    00
  • mysql id 自增实现

    1、在mysql中建表                                              2、使用:   >insert into 表名 values(id,’www’,66); 连续运行5次后结果:                                                                 …

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