zoukankan      html  css  js  c++  java
  • 第六章 IO复用:select和poll函数

    1. 概述

    I/O复用使用场合:

      • 当客户处理多个描述符(通常是交互式输入和网络套接字)时,必须要使用I/O复用;
      • 一个客户同时处理多个套接字(比较少见); 
      • 如果一个TCP服务器既要处理监听套接字,又要处理已连接的套接字,一般要使用I/O复用;
      • 如果一个服务器要处理多个服务或者多个协议,一般要使用I/O复用

    另外I/O复用并非只限于网络编程,还有许多重要程序也会用到这项技术。

    2. I/O模型

    • 阻塞式I/O模型
    • 非阻塞式I/O模型
    • I/O复用模型
    • 信号驱动I/O模型
    • 异步I/O模型

    3. select函数

    1)函数作用:允许进程指示内核等待多个事件中的一个发生, 并只在有一个或多个事件发生,或通过定时唤醒它。(前面说的同时处理socket描述符和等待用户输入就符合这个情况)(Berkeley的实现允许任何描述符的I/O复用)

    2)函数定义

    #include <sys/time.h>
    #include <sys/select.h>
    int select (int maxfdp1, fd_set *readset, fd_set *writeset, fd_set exceptset, const struct timeval *timeout);

    struct timeval
    {
    long tv_sec;
    long tv_usec;
    }

    参数介绍:

    timeout:告知内核等待所指定的描述符中任何一个就绪的时间。其有三种可能:

      • 永远等下去,此时值设置为NULL;
      • 等一段固定的时间。即等待时间不超过所指定的timeout时间值;
      • 根本不等待。此时称为轮询,timeout结构总的秒、微妙都设置为0;

    exceptset:目前支持的异常条件只有两个

      • 某个套接字的带外数据到达(24章讨论);
      • 某个已置为分组模式的伪终端,存在可以从其主终端读取的控制状态信息(本书不涉及)

    readset,writeset:我们要让内核读和写的描述符;

    maxfdp1: 指定待测描述符的个数,具体值从0开始到maxfdp1-1(FD_SETSIZE常值为fd_set描述符的总数)。

    返回值:若有就绪描述符则为其个数,超时返回0,出错为-1.

    select可以作为定时器,此时中间三个描述符集设置为NULL,这个定时器比sleep还有精确,sleep以秒为单位,其以微妙为单位。

     

    3)fd_set类型的相关操作

    fd_set rset; //注意新定义变量一定要用FD_ZERO初始化,其自动分配的值不可意料,会导致不可意料的后果。
    void FD_ZERO(fd_set *fdset); //initialize the set: all bits off
    void FD_SET(int fd, fd_set *fdset); //turn on the bit for fd in fdset
    void FD_CLR(int fd, fd_set *fdset); //turn off the bits for fd in fdset
    int FD_ISSET(int fd, fd_set *fdset); //is the bit for fd on in fdset

    4) 描述符的就绪条件

      • 有数据可读(读);
      • 关闭连接的读一半(TCP收到FIN)(读);
      • 给监听套接口准备好新连接(读);
      • 有待处理的错误(读写)
      • 有可用的写空间(写);
      • 关闭连接写一半(写);
      • tcp带外数据(异常)

    5)select的最大描述符数:不同系统可能不同,使用大描述符集时,要考虑移植性。

    6)例子:第五章中说到的同时处理fputs、read的情况

    View Code
    void str_cli_1(FILE *fp, int sockfd)
    {
        char sendline[MAXLINE];
        char readline[MAXLINE];
        
        fd_set rset;
        FD_ZERO(&rset);
        
        while(1)
        {
            FD_SET(fileno(fp), &rset);
            FD_SET(sockfd, &rset);
            int maxfdp1 = MAX(fileno(fp), sockfd) + 1;
            
            if( select(maxfdp1, &rset, NULL, NULL, NULL) <=0 )
            {
                printf("%s:%d select error\n", __FUNCTION__, __LINE__);
                return;
            }
            
            if( FD_ISSET(sockfd, &rset) )
            {
                if( read(sockfd, readline, MAXLINE) == 0)
                {
                    printf("%s:%d recv data error\n", __FUNCTION__, __LINE__);
                    return;
                }
                fputs(readline,stdout);
            }
            
            if( FD_ISSET(fileno(fp), &rset) )
            {
                if( NULL == fgets(sendline, MAXLINE, fp) )
                {
                    printf("%s:%d fgets error\n", __FUNCTION__, __LINE__);
                    return;
                }
                if( write(sockfd, sendline, strlen(sendline)) != strlen(sendline) )
                {
                    printf("%s:%d send data error\n", __FUNCTION__, __LINE__);
                    return ;
                }
            }
        }
    }

    例子中有了select当服务器进程退出后,客户端就不会阻塞于fgets函数,客户端收到FIN后read会马上返回错误。(此例子还有问题)

    4. 批量输入

        如果客户端持续向服务器发送数据,发送完数据后客户端马上调用close关闭连接。此时客户端可能仍有请求去往服务器的路上,或是仍有服务器的应答在返回客户段的路上。这样如果调用close全关闭就会造成问题。我们需要一个半关闭TCP的一种方法。即下面将说到的shutdown。

        混合使用select和stdio比较容易发生错误,要非常小心。

    5. shutdown函数

    1)close函数的限制

      • close把描述符的引用计数减一,要到计数为0时才关闭套接字。shutdown可以不管引用计数就激发TCP的正常连接终止。
      • close终止读和写两个方向的数据传送。但是有时我们需要告知对方我们已经把数据发送完毕,即使我们还有数据需要接收。

    2)shutdown函数定义

    #include <sys/socket.h>
    //返回值:成功0,出错-1
    int shutdown(int sockfd, int howto);

    howto参数值说明:

      • SHUT_RD:关闭连接的读这一半,套接字没有数据可以接收了,而且套接字接收缓冲区中现有的数据将被丢弃;
      • SHUT_WR:关闭连接的写这一半,对于TCP这叫半关闭,当前留在套接字发送缓冲区的数据将被发送完,紧跟TCP正常的连接终止系列。
      • SHUT_RW:关闭整个连接。

    6. 修正3中最后面的str_cli_1例子

    View Code
    void str_cli_2(FILE *fp, int sockfd)
    {
        char sendline[MAXLINE];
        char readline[MAXLINE];
        
        fd_set rset;
        FD_ZERO(&rset);
        
        int stdineof = 0;
        while(1)
        {
            if(stdineof == 0)
            {
                FD_SET(fileno(fp), &rset);
            }
            FD_SET(sockfd, &rset);
            int maxfdp1 = MAX(fileno(fp), sockfd) + 1;
            
            if( select(maxfdp1, &rset, NULL, NULL, NULL) <=0 )
            {
                printf("%s:%d select error\n", __FUNCTION__, __LINE__);
                return;
            }
            
            if( FD_ISSET(sockfd, &rset) )
            {
                int rn = 0;
                if( (rn = read(sockfd, readline, MAXLINE)) == 0)
                {
                    if(stdineof == 1)
                    {
                        return;
                    }
                    printf("%s:%d recv data error\n", __FUNCTION__, __LINE__);
                    return;
                }
                write(fileno(stdout), readline, rn);
            }
            
            if( FD_ISSET(fileno(fp), &rset) )
            {
                int rn = 0;
                if( (rn = read(fileno(fp), sendline, MAXLINE)) == 0 )
                {
                    stdineof = 1;
                    shutdown(sockfd, SHUT_WR); //send fin
                    FD_CLR(fileno(fp), &rset);
                    printf("%s:%d read fileno(fp) error\n", __FUNCTION__, __LINE__);
                    continue;
                }
                if( write(sockfd, sendline, rn) != rn )
                {
                    printf("%s:%d send data error\n", __FUNCTION__, __LINE__);
                    return ;
                }
            }
        }
    }

    7. 使用select的TCP服务器程序

    View Code
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <signal.h>
    #include <sys/socket.h>
    #include <sys/select.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    
    #define SRV_PORT 8888
    #define MAXLINE 4096
    
    void str_echo(int fd);
    void sig_chld(int signo);
    
    int main(int argc, char **argv)
    {
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if(listenfd < 0)
        {
            perror("create socket error.");
        }
    
        struct sockaddr_in srvaddr;
        bzero(&srvaddr, sizeof(srvaddr));
        srvaddr.sin_family = AF_INET;
        srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        srvaddr.sin_port = htons(SRV_PORT);
        
        if(bind(listenfd, (struct sockaddr*)&srvaddr, sizeof(srvaddr)) < 0)
        {
            perror("bind error.");
        }
        
        if(listen(listenfd, 1023) < 0)
        {
            perror("listen error.");
        }
    
        int maxfd = listenfd;
        int maxi = -1;
        
        int client[FD_SETSIZE];
        int i=0;
        for(i=0; i<FD_SETSIZE; i++)
        {
            client[i] = -1;
        }
        
        fd_set rset, allset;
        FD_ZERO(&allset);
        FD_SET(listenfd, &allset);
        
        struct sockaddr_in cliaddr;
        
        for(; ;)
        {
            rset = allset;
            int nready = select(maxfd+1, &rset, NULL, NULL, NULL);
            if(FD_ISSET(listenfd, &rset))
            {
                socklen_t clilen = sizeof(cliaddr);
                int connfd;
                if((connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen)) < 0)
                {
                    if(errno == EINTR)
                    {
                        continue;
                    }
                    else
                    {
                        perror("accept error.");
                    }
                }
                for(i=0; i < FD_SETSIZE; i++)
                {
                    if(client[i] < 0)
                    {
                        client[i] = connfd;
                        break;
                    }
                }
                if(i == FD_SETSIZE)
                {
                    printf("too many connect.");
                    exit(-1);
                }
                
                FD_SET(connfd, &allset);
                
                maxfd = maxfd > connfd ? maxfd : connfd;
                
                maxi = i > maxi ? i : maxi;
                
                if(--nready <= 0)
                {
                    continue;
                }
            }
            
            //get client data
            for(i=0; i <= maxi; ++i)
            {
                if(client[i] < 0)
                {
                    continue;
                }
    
                if(FD_ISSET(client[i], &rset))
                {
                    int byteN;
                    char buf[MAXLINE];
                    if( (byteN = read(client[i], buf, MAXLINE)) == 0)
                    {
                        close(client[i]);
                        FD_CLR(client[i], &allset);
                        client[i] = -1;
                    }
                    else
                    {
                        write(client[i], buf, byteN);
                        if(--nready <= 0)
                        {
                            break;
                        }
                    }
                }
            }
        }
    
        return 0;
    }

    8. pselect函数

    #include <sys/select.h>
    int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, 
                const struct timespec *timeout, const sigset_t *sigmask);
    此函数比select函数多了timespec和sigmask-不细说了

    9. poll函数

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

    struct pollfd {
        int fd; /* file descriptor */
        short events; /* requested events */  //入参
        short revents; /* returned events */  //出参
    };

    说明:与select提供的功能累世,处理流设备时能提供额外的信息。不限定最大描述符数量。

  • 相关阅读:
    day17---无参装饰器
    day17---作业
    python面向过程的编程思想
    算法之二分法
    python递归函数
    pyth作业3/25
    三元表达式、生成式、生成器表达式
    python 3/24作业
    python生成器
    python 迭代器
  • 原文地址:https://www.cnblogs.com/4tian/p/2624353.html
Copyright © 2011-2022 走看看