I/O复用之select

作用:

实现I/O的多路复用

该函数允许进程指示内核等待多个事件中的任何一个发生,并只有在一个或多个事件发生时或经历一段指定的时间后才唤醒它。进程将于select处阻塞,直到被检测的描述符有一个或多个发生了变化。我们可以使用它来监视多个文件描述符的状态变化。

函数原型:

int select(int maxfd,fd_set *rdset,fd_set *wrset,   
           fd_set *exset,struct timeval *timeout);  

参数:

参数maxfd是需要监视的最大的文件描述符值+1;rdset,wrset,exset分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合。struct timeval结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

下面的宏提供了处理这三种描述词组的方式:

FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);用来清除描述词组set的全部位

参数timeout为结构timeval,用来设置select()的等待时间,其结构定义如下:

struct timeval  
{  
    time_t tv_sec;//second  
    time_t tv_usec;//minisecond  
};  

这个参数有三种可能:

  • 永远等待下去:仅在一个描述符准备号I/O才返回。此时该参数为NULL
  • 等待一段固定时间:在有一个描述符准备好I/O时返回,但不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
  • 不等待:检查描述符后立即返回。这称为轮询(polling)。此时该参数必须指向一个timeval结构,且两个值为0.

函数返回值

执行成功: 返回文件描述词状态已改变的个数,如果返回0代表在描述词状态改变前已超过timeout时间。

执行失败: 当有错误时返回-1,错误原因存于errno中,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。错误值可能为:

  • EBADF 文件描述词为无效的或该文件已关闭
  • EINTR 此调用被信号所中断
  • EINVAL 参数n 为负值
  • ENOMEM 核心内存不足

示例

这里给出unp中基于select模型的echo服务器。

int main(int argc, char **argv)
{
    //服务器初始化
    int i, maxi, maxfd, listenfd, connfd, sockfd;
    int nready, client[FD_SETSIZE];
    ssize_t n;
    fd_set rset, allset;
    char buf[MAXLINE];
    socklen_t clilen;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0); //TCP
    struct sockaddr_in servaddr = InitSrvSockaddr(SERV_PORT), cliaddr;
    Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
    Listen(listenfd, LISTENQ);

    maxfd = listenfd; //此时listenfd为fd最大值
    maxi = -1;        //index into client array
    for (i = 0; i < FD_SETSIZE; ++i)
        client[i] = -1; //初始化client数组

    FD_ZERO(&allset);
    FD_SET(listenfd, &allset); //listenfd 加入allset

    for (;;)
    {
        rset = allset;
        nready = Select(maxfd + 1, &rset, NULL, NULL, NULL); //返回状态发生变化的描述符总数

        if (FD_ISSET(listenfd, &rset)) //listenfd可用,代表有客户端连接成功
        {
            clilen = sizeof(cliaddr);
            connfd = Accept(listenfd, (SA *)&cliaddr, &clilen);

            for (i = 0; i < FD_SETSIZE; ++i) //从开始遍历client数组,找到第一个为-1的位置,将客户的fd保存
                if (client[i] < 0)           //为-1代表可用
                {
                    client[i] = connfd;
                    break;
                }

            if (i == FD_SETSIZE) //已满
                err_quit("too many clients");

            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]) < 2)
                continue;
            if (FD_ISSET(sockfd, &rset))
            {
                if ((n = Read(sockfd, buf, MAXLINE)) == 0)
                {
                    Close(sockfd);
                    FD_CLR(sockfd, &allset);
                    client[i] = -1;
                }
                else
                    Writen(sockfd, buf, n);
                if (--nready <= 0) //就绪描述符数目为0就跳出,避免检查未就绪的描述符
                    break;
            }
        }
    }
}

参考自:
http://blog.csdn.net/turkeyzhou/article/details/8609360
《unix网络编程》

原文地址:https://www.cnblogs.com/cknightx/p/7440411.html