select模型

IO复用

  • 什么是io复用?

    它是内核提供的一种同时监控多个文件描述符状态改变的一种能力;例如当进程需要操作多个IO相关描述符时(例如服务器程序要同时查看监听socket和大量业务socket是否有数据到来),需要内核能够监控这许多描述符,一旦这些描述符有就绪(或者状态改变了)就告诉主动告诉进程哪些描述符已经就绪,这样站在进程的角度,就不需要挨个的查看每个描述符是否就绪。

  • 比如

    如果用监控来自10根不同地方的水管(I/O端口)是否有水流到达(即是否可读),那么需要10个人(即10个线程或10处代码)来做这件事。如果利用某种技术(比如摄像头)把这10根水管的状态情况统一传达到某一点,那么就只需要1个人在那个点进行监控就行了。由于I/O多路复用是在单一进程的上下文中的,因此每个逻辑流程都能访问该进程的全部地址空间,所以开销比多进程低得多;缺点是编程复杂度高

    优点:开销低。

    缺点:编程复杂度高。

  • IO的五个模型(unix系统下面)

  • (1)阻塞IO模型

  •         
  • (2)非阻塞IO模型


  • (3)IO复用模型

  • (4)信号驱动IO模型

  • 前面四种都是同步IO复用

    (5)异步IO模型

  • 五种IO模型对比

  •   

    参考大神博客:https://blog.csdn.net/xiexievv/article/details/44976215


select模式

  • 函数接口

    • 监听描述符事件,如果描述符集合中没有就绪,等待;反之,函数返回,把描述符集合清空,并设置已经就绪的描述符(设置为1)。

    • 工作模式

      • select通过轮询来检测各个集合中的描述符(fd)的状态,如果描述符的状态发生改变,则会在该集合中设置相应的标记位;如果指定描述符的状态没有发生改变,则将该描述符从对应集合中移除。因此,select的调用复杂度是线性的,即O(n)。

      • select的限制:

        (1)前面提到FD_SETSIZE宏,这个宏是操作系统定义的。在linux下面通常是1024,也就是说select最多只能管理1024个描述符。如果大于1024的个描述,select将会产生不可预知的行为。那在没有poll或epoll的情况下,怎样使用select来处理连接数大于1024的情况呢?答案是使用多线程技术,每个线程单独使用一个select进行检测。这样的话,你的系统能够处理的并发连接数等于线程数*1024。早期的apache就是这种技术来支撑海量连接的。

        (2)需要修改传入的参数数组

        (3)不能指定某个有数据的socket

        (4)线程不安全

    • 函数原型

      
          /* 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);
      
    • 函数参数

      NO 参数 含义
      1 nfds 需要监视的最大的文件描述符值+1
      2 readfds 需要检测的可读文件描述符的集合
      3 writefds 需要检测的可写文件描述符的集合
      4 exceptfds 需要检测的异常文件描述符的集合
      5 timeout 超时时间
    • 函数返回值

      失败返回-1,获取到数据返回>0,超过时间返回0。

    • 宏定义

    • Select工作流程

      1:用FD_ZERO宏来初始化我们感兴趣的fd_set

      也就是select函数的第二三四个参数。

      2:用FD_SET宏来将套接字句柄分配给相应的fd_set

      如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中

      3:调用select函数。

      如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,

      4:用FD_ISSET对套接字句柄进行检查

      如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。
      No. 参数 含义
      1 FD_ZERO(fd_set *fdset) 清空文件描述符集
      2 FD_SET(int fd,fd_set *fdset) 设置监听的描述符(把监听的描述符设置为1)
      3 FD_CLR(int fd,fd_set *fdset) 清除监听的描述符(把监听的描述符设置为0)
      4 FD_ISSET(int fd,fd_set *fdset) 判断描述符是否设置(判断描述符是否设置为1)
      5 FD_SETSIZE 256
    • 结构体fd_set 描述符号集

      
      typedef struct fd_set {
              u_int   fd_count;               /* how many are SET? */
              SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
      } fd_set;

  • 编码流程

    定义描述符集

  •   
  • 清空描述符集

  •           

    设置指定的描述符并获取最大的描述符值+1

  •         

    等待描述符就绪

    判断已就绪的描述符,并做对应处理

  •     
  • 伪代码

    
    // 定义描述符集
    fd_set rset;
    // 清空描述符集
    FD_ZERO(&rset); 
    // 设置描述符 
    FD_SET(fd1,&rset);
    FD_SET(fd2,&rset); 
    // 获取最大描述符+1 
    int maxfdp1 = max(fd1,fd2) + 1; 
    // 等待描述符就绪 
    if(select(maxfdp1,&rset,NULL,NULL,NULL) > 0)
    { 
        // 判断已就绪的描述符 
        if(FD_ISSET(fd1,&rset)){ // do somthing }            
        if(FD_ISSET(fd2,&rset)){ // do somthing } 
    }

    sever.cpp文件

    
    #include <stdio.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <string.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <sys/select.h>
    #include <sys/sendfile.h>
    #include <stdlib.h>
    #include <sys/stat.h>
    
    #define max(a,b) ((a)>(b)?(a):(b))
    void show_info(int connfd){
        struct sockaddr_in local_addr;
        bzero(&local_addr,sizeof(local_addr));
        socklen_t local_addr_len = sizeof(local_addr);
        getsockname(connfd,(struct sockaddr*)&local_addr,&local_addr_len);
        printf("server local %s:%d\n",inet_ntoa(local_addr.sin_addr),ntohs(local_addr.sin_port));
        
        struct sockaddr_in peer_addr;
        bzero(&peer_addr,sizeof(peer_addr));
        socklen_t peer_addr_len = sizeof(peer_addr);
        getpeername(connfd,(struct sockaddr*)&peer_addr,&peer_addr_len);
        printf("server peer %s:%d\n",inet_ntoa(peer_addr.sin_addr),ntohs(peer_addr.sin_port));
    }
    int main(int argc,char* argv[]){
        if(3 != argc){
            printf("usage:%s <ip> <#port>\n",argv[0]);
            return 1;
        }
    
        int listenfd = socket(AF_INET,SOCK_STREAM,0);
        if(-1 == listenfd){
            perror("listenfd open err");
            return 1;
        }
        printf("socket create OK\n");
        
        int flag = 1;
        setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));    
    
        struct sockaddr_in local_addr;
        bzero(&local_addr,sizeof(local_addr));
        local_addr.sin_family = AF_INET;
        local_addr.sin_addr.s_addr = inet_addr(argv[1]);
        local_addr.sin_port = htons(atoi(argv[2]));
    
        if(-1 == bind(listenfd,(struct sockaddr*)&local_addr,sizeof(local_addr))){
            perror("bind err");
            return 1;
        }
        printf("bind OK\n");
    
        if(-1 == listen(listenfd,10)){
            perror("listen err");
            return 1;
        }
        printf("listen OK\n");
    
        fd_set rfds;
        FD_ZERO(&rfds);
        
        FD_SET(listenfd,&rfds);
        int maxfdp1 = listenfd + 1;
        int connfds[FD_SETSIZE-1];  
        size_t connfds_cnt = 0;
        for(;;){
            int i;
            FD_ZERO(&rfds);
            FD_SET(listenfd,&rfds);
            for(i=0;i<connfds_cnt;i++){
                FD_SET(connfds[i],&rfds);
                printf("FD_SET(%d)\n",connfds[i]);
            }
            printf("before select:%lu\n",rfds);
            if(-1 == select(maxfdp1,&rfds,NULL,NULL,NULL)){
                perror("select error");
                return 1;
            }
            printf("after select:%lu\n",rfds);
            if(FD_ISSET(listenfd,&rfds)){
                printf("listenfd ready\n");
                struct sockaddr_in remote_addr;
                bzero(&remote_addr,sizeof(remote_addr));
                socklen_t remote_addr_len = sizeof(remote_addr);
                int connfd = accept(listenfd,(struct sockaddr*)&remote_addr,&remote_addr_len);
                if(-1 == connfd){
                    perror("accept err");
                    //return 1;
                }
    
                if(connfds_cnt+1 == FD_SETSIZE-1){
                    fprintf(stderr,"connfd size over %d\n",FD_SETSIZE-1);
                    close(connfds[i]);
                }else{
                    connfds[connfds_cnt++] = connfd;                
                    maxfdp1 = max(connfd,maxfdp1-1)+1;
                    show_info(connfd);
                }
            }
            for(i=0;i<connfds_cnt;i++){
                if(-1 == connfds[i]){
                    continue;
                }
                if(FD_ISSET(connfds[i],&rfds)){
    
                    char buf[BUFSIZ];
                    bzero(buf,BUFSIZ);
                    ssize_t len;
                    if((len = read(connfds[i],buf,BUFSIZ-1)) == -1){
                        perror("read err");
                        //return 1;
                    }
                    if(0 == len){
                        printf("close %d\n",connfds[i]);
                        close(connfds[i]);
                        FD_CLR(connfds[i],&rfds);
                        memcpy(connfds+i,connfds+i+1,connfds_cnt-i-1);
                        connfds_cnt--;
                        i--;//数组发生变化,重新判断i的fd
                        continue;
                    }
                    printf("server recv:%s\n",buf);
                    
                    int fd = open(buf,O_RDONLY);
                    if(-1 == fd){
                        perror("open file err");
                    }
                    struct stat file_stat;
                    fstat(fd,&file_stat);
                    if(-1 == sendfile(connfds[i],fd,NULL,file_stat.st_size)){
                        perror("sendfile err");
                    }
                    printf("server send file %s ok\n",buf);
                    close(fd);
                }
            }
        }
        close(listenfd);
    
    }
    

    select模型图示:


  •                                       



   

             tips:

                      要定义一个数组来存放所有建立联系的套结字描述符,每次查寻的是这个。

猜你喜欢

转载自blog.csdn.net/qq_40477151/article/details/80314013