select、pselect、poll和epoll的区别

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

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

 1 #include    “unp.h”

 2 

 3 int main(int argc, char **argv)

 4 {

 5     int                 listenfd, connfd;

 6     pid_t               childpid;

 7     socklen_t           clilen;

 8     struct sockaddr_in  cliaddr, servaddr;

 9     void                sig_chld(int);

10 

11     listenfd = Socket(AF_INET, SOCK_STREAM, 0);

12 

13     bzero(&servaddr, sizeof(servaddr));

14     servaddr.sin_family      = AF_INET;

15     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

16     servaddr.sin_port        = htons(SERV_PORT);

17 

18     Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

19 

20     Listen(listenfd, LISTENQ);

21 

22     Signal(SIGCHLD, sig_chld);  /* must call waitpid() */

23 

24     for ( ; ; )

25     {

26         clilen = sizeof(cliaddr);

27         if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0)

28         {

29             if (errno == EINTR)

30                 continue;       /* back to for() */

31             else

32                 err_sys(“accept error”);

33         }

34 

35         if ( (childpid = Fork()) == 0)

36         {    /* child process */

37             Close(listenfd);    /* close listening socket */

38             str_echo(connfd);   /* process the request */

39             exit(0);

40         }

41 

42         Close(connfd);          /* parent closes connected socket */

43     }

44 }

 

    从以上代码,我们可以知道,这个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.

你可能感兴趣的:(select)