• 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.

  • 相关阅读:
    搭建docker镜像仓库(一):使用registry搭建本地镜像仓库
    C++ quick sort
    Ubuntu C++ uuid_generate vs Windows UuidCreate
    今天做错的笔试题:StringBuffer引用传参
    一般报java.lang.NullPointerException的原因有以下几种
    has a / is a 的区别
    1、一日一程序之C语言的Hanoi问题
    Java enum的用法详解
    OpenGL ES EAGLContext 和 EGLContext
    Windows OpenGL ES 图像对比度调节
  • 原文地址:https://www.cnblogs.com/jaydenhpj/p/3676113.html
Copyright © 2020-2023  润新知