I/O多路转接

  1. I/O多路转接select 函数
       /* According to POSIX.1-2001 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);    //清除其中的一位
       int FD_ISSET(int fd, fd_set *set);    //看其中的一位是否被设定
       void FD_SET(int fd, fd_set *set);    //设定其中的一位
       void FD_ZERO(fd_set *set);            //清零

    说明:
    该函数提供了一种在单个进程中监视多个文件描述符的方法。可以对三种类型的描述符集进行监视:可读(第2个参数:readfds)、可写(第3个参 数:writefds)、处于异常状态(第4个参数:exceptfds)的描述符。        从第2个参数起,参数都可以为空(NULL),当文件描述符集为空时,表示不监视其描述符的状态;如果是时间值为空,表示永远阻塞,直到描述符准备好。
    nfds 是三个文件描述符号中最大的描述符+1。这样就会在一定的范围内搜索需要检测的描述符,否则,将会在所有可选的fd_set中搜索。

*select 函数的使用
    fd_set rset, wset;
    int maxfd;
   
    FD_ZERO(&rset);        //不管定义哪一个集合都必须要先清零
    FD_ZERO(&wset);   
    //设置读集
    FD_SET(0, &rset);   
    FD_SET(3, &rset);
    //设置写集
    FD_SET(1, &wset);
    FD_SET(2, &wset);
   
    maxfd = 3 + 1;        //必须是FD_SET中最大的描述符 + 1
    if (select(maxfd, &rset, &wset, NULL, NULL) > 0) {
        if (FD_ISSET(0, &rset)) {    //看是哪一个准备好了读
            ...
        }
        if (FD_ISSET(1, &wset)) {    //看是哪一个准备好了写
            ...
        }
    }
       
*一般的使用模式
    ...
      maxfd = fromfd1;
    if (fromfd2 > maxfd)
        maxfd = fromfd2;

    for ( ; ; ) {
        FD_ZERO(&rset);
        FD_SET(fromfd1, &rset);
        FD_SET(fromfd2, &rset);
 
        if (((num = select(maxfd+1, &rset, NULL, NULL, NULL)) == -1) &&
                (errno == EINTR))    //若是被信号中断,自启动,linux下是自启动的可以不判断
            continue;
        if (num == -1)          //error happened
            return ntotal;
        if (FD_ISSET(fromfd1, &rset)) {
            nread = readwrite(fromfd1, tofd1);
            if (nread <= 0)
                break;
            ntotal += nread;
        }
        if (FD_ISSET(fromfd2, &rset)) {
            nread = readwrite(fromfd2, tofd2);
            if (nread <= 0)
                break;
            ntotal += nread;
        ...
   
   
* 关于select的返回值
    -1         出错
    0          没有描述符准备好,并超时       
    n>0        返回已准备好的描述符的数量,该值是三个描述符中已准备好的描述符之和,
            若一个描述既准备好读,又准备好了写,那么返回2。
   
*在什么样的情况下描述符准备好?
    .对于读集合(readfds)     中的一个描述符的读操作read不会阻塞,则此描述符准备好。
    .对于写集合(writefds)     中的一个描述符的写操作write不会阻塞,则此描述符准备好。
    .对于写集合(exceptfds)     中的一个描述符有一个未决异常状态,则此描述符准备好。
2. pselect 函数    man pselect   

3. poll函数
           
       #include <poll.h>
       int poll(struct pollfd *fds, nfds_t nfds, int timeout);

       #define _GNU_SOURCE
       #include <poll.h>

       int ppoll(struct pollfd *fds, nfds_t nfds,
               const struct timespec *timeout, const sigset_t *sigmask);
   
        结构说明:
           struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };
*poll函数说明
    参数
        fds是一个pollfd数组,说明关心的描述符和它的状态。
        nfds    给出要监视的描述符的数目
        timeout    是毫秒表示的时间值,是poll没有接受到事件的等待时间。
    返回值
        0     超时
        -1    错误
        成功    返回拥有事件的描述符的数目。                   

    与select函数不同,poll函数不时按照文件描述符的类型来组织信息的,而是通过pollfd数组来组织信息。每个数组元素指定一个描述符编号以及 一个对其关心的状态。
   

*poll函数的使用
void
monitorpoll(int fd[], int numfds)
{
    char buf[BUFSIZE];
    int bytesread;
    int i;
    int numnow = 0;
    int numready;
    struct pollfd *pollfd;

    for (i = 0; i < numfds; i++)
        if (fd[i] >= 0)
            numnow++;
    pollfd = (void *)calloc(numfds, sizeof(struct pollfd));
    if (pollfd == NULL)
        return ;
    for (i = 0; i < numfds; i++) {
        (pollfd + i)->fd = *(fd + i);
        (pollfd + i)->events = POLLRDNORM;
    }
    while(numnow > 0) {
        numready = poll(pollfd, numfds, -1);   
        if ((numready == -1 ) && (errno == EINTR))    //信号中断重启
            continue;
        else if (numready == -1)            //错误发生
            break;
        for (i = 0; i < numfds && numready > 0; i++) {
            if ((pollfd + i)->revents) {    //事件发生
                if ((pollfd + i)->revents & (POLLRDNORM | POLLIN)) {    //可读
                    bytesread = r_read(fd[i], buf, BUFSIZE);
                    numready --;
                    if (bytesread > 0)
                        docmd(buf, bytesread);
                    else
                        bytesread = -1;
                }
            } else if ((pollfd + i)->revents & (POLLERR | POLLHUP))  //错误发生
                bytesread = -1;   
            else
                bytesread = 0;
            if (bytesread == -1) {
                r_clos(fd[i]);
                (pollfd + i)->fd = -1;
                numnow --;
            }
        }
    }
    for (i = 0; i < numfds; i++)
        r_close(fd[i]);
    free(pollfd);
}

*poll 比select函数的优点
    .select函数每次使用时都要重新设置文件描述符集,而poll函数为输入和返回值设置了独立的变量,不需要每次调用后重置掩码。
    .poll的时间timeout的范围受限,但它跟容易使用。
    .与select函数不同,poll把错误当成返回的事件来处理。
    .与select函数不一样,poll不使用max_fd参数。

作者: aaron_xueli   发布时间: 2010-10-15