select、pselect、poll和epoll的区别

来源:互联网 发布:淘宝店铺号码怎么查找 编辑:程序博客网 时间:2024/04/29 13:38

 select、pselect、poll和epoll函数是unix中具有I/O复用的函数。什么是I/O复用?为什么要有I/O复用?以及在什么场合下使用I/O复用?既然都具有I/O复用的功能,那这几个函数又有什么样的区别与联系呢?在下面我会一一解释。

请看下面一段客户端代码:

#include    “unp.h”int main(int argc, char **argv){    int                 listenfd, connfd;    pid_t               childpid;    socklen_t           clilen;    struct sockaddr_in  cliaddr, servaddr;    void                sig_chld(int);    listenfd = Socket(AF_INET, SOCK_STREAM, 0);    bzero(&servaddr, sizeof(servaddr));    servaddr.sin_family      = AF_INET;    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);    servaddr.sin_port        = htons(SERV_PORT);    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));    Listen(listenfd, LISTENQ);    Signal(SIGCHLD, sig_chld);  /* must call waitpid() */    for ( ; ; )    {        clilen = sizeof(cliaddr);        if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0)        {            if (errno == EINTR)                continue;       /* back to for() */            else                err_sys(“accept error”);        }        if ( (childpid = Fork()) == 0)        {    /* child process */            Close(listenfd);    /* close listening socket */            str_echo(connfd);   /* process the request */            exit(0);        }        Close(connfd);          /* parent closes connected socket */    }}


    从以上代码,我们可以知道,这个TCP客户端在同时处理两个输入:标准输入和TCP套接口。现在假设我们遇到了以下问题:

    客户阻塞于(标准输入上的)fgets调用期间,服务器进程会被杀死。服务器TCP虽然正确地给客户TCP发送一个FIN,但是既然客户进程阻塞于从标准输入读入的过程,它直到从套接口读时才会看到这个文件结束符(可能已经过了很长时间)。我们需要有这样的能力:如果一个或者多个I/O条件满足(例如:输入已准备好,或者描述字可以承接更多的输出)时,我们就被通知到。这个能力称为I/O复用,是由函数select、poll、epoll支持的。

    一般输入操作有两个不同的阶段:

    1. 等待数据准备好;

    2. 从内核到进程拷贝数据。

1. select函数

int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

  • maxfdp1:表示要被测试的最大描述字加1(从参数maxfdp1命名我们可以略知一二);
  • readset、writeset、exceptset: 指定我们要让内核测试读、写和异常条件的描述字;

那么在设计上有一个问题,如何为这三个参数的每一个指定一个或多个描述字值。函数select使用描述字集,它一般是一个整数数组,每个整数中的每一位对应一个描述字。例如,用32位整数数组,那么数组的第一个元素的第一个元素对应于描述字0到31,数组的第二个元素对应于描述字32到63,以此类推。

每一个描述字是如何与fd_set集合中的每一个一一对应的呢?就是用到了下面的四个与select函数相关的宏:

   FD_ZERO(fd_set *fdset):清空fdset与所有描述字的联系。  

   FD_SET(int fd, fd_set *fdset):建立描述字fd与fdset的联系。  

   FD_CLR(int fd, fd_set *fdset):清除描述字fd与fdset的联系。  

   FD_ISSET(int fd, fd_set *fdset):检查fdset联系的描述字fd是否可读写,当>0表示可读写。

  • timeout: 它告诉内核花多长时间等待一组指定的描述字中的任一个准备好。

有三种可能:

a. 永远等待下去:仅在有一个描述字准备好I/O时才返回,为此,我们将参数timeout设置为空指针;

b. 等待固定时间:在有一个描述字准备好I/O时返回,但不超过由timeout参数所指定的时间。若超过时间则直接返回;

c. 根本不等待:检查描述字后立即返回,这称为轮询(polling)。为了实现这一点,参数timeout必须指向结构timeval,且定时器的值(由结构timeval指定的秒数和微秒数)必须为0

在前两者情况的等待中,如果进程捕获了一个信号并从信号处理程序返回,那么等待一般被中断。

中间三个参数readset,wirteset和exceptset指定我们要让内核测试读写和异常条件所需的描述字,参数maxfdp1说明了被测试的描述符的个数,它的值是要被测试的最大的描述符加1.

0 0
原创粉丝点击