Linux——select详解

Posted 努力学习的少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux——select详解相关的知识,希望对你有一定的参考价值。

  • 💂 个人主页:努力学习的少年
  • 🤟 版权: 本文由【努力学习的少年】原创、在CSDN首发、需要转载请联系博主
  • 💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦

目录

1. select函数介绍

2. select函数参数的介绍

3.select的工作流程

4.Select服务器

5.Select的缺陷


1. select函数介绍

select函数是IO多路复用的函数,它主要的功能是用来等文件描述符中的事件是否就绪,select可以使我们在同时等待多个文件缓冲区 ,减少IO等待的时间,能够提高进程的IO效率。

select()函数允许程序监视多个文件描述符,等待所监视的一个或者多个文件描述符变为“准备好”的状态。所谓的”准备好“状态是指:文件描述符不再是阻塞状态,可以用于某类IO操作了,包括可读,可写,发生异常三种

2. select函数参数的介绍

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

ndfs

等待的文件描述符的最大值+1,例如:应用进程想要去等待文件描述符3,5,8的事件,则

nfds=max(3,5,8)+1;

fd_set类型

readfds和writefds,exceptfds的类型都是fd_set,那么fd_set类型是什么呢?

  • fd_set类型本质是一个位图位图的位置 表示 相对应的文件描述符,内容表示该文件描述符是否有效,1代表该位置的文件描述符有效,0则表示该位置的文件描述符无效。
  • 如果将文件描述符2,3设置位图当中,则位图表示的是为1100。
  • fd_set的上限是1024个文件描述符。

readfds

  • readfds是 等待读事件的文件描述符集合,.如果不关心读事件(缓冲区有数据),则可以传NULL值。
  • 应用进程和内核都可以设置readfds,应用进程设置readfds是为了通知内核去等待readfds中的文件描述符的读事件.而 内核设置readfds是为了告诉应用进程哪些读事件生效

writefds

与readfds类似,writefds是等待写事件(缓冲区中是否有空间)的集合,如果不关心写事件,则可以传值NULL。

exceptfds

如果内核等待相应的文件描述符发生异常,则将失败的文件描述符设置进exceptfds中,如果不关心错误事件,可以传值NULL。

timeout

设置select在内核中阻塞的时间,如果想要设置为非阻塞,则设置为NULL。如果想让select阻塞5秒,则将创建一个struct timeval time=5,0;

其中struct timeval的结构体类型是:

           struct timeval 
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           ;

返回值

  • 如果没有文件描述符就绪就返回0;
  • 如果调用失败返回-1;
  • 如果timeout中中readfds中有事件发生,则返回timeout剩下的时间。

3.select的工作流程

应用进程内核都需要从readfds和writefds获取信息,其中,内核需要从readfds和writefds知道哪些文件描述符需要等待,应用进程需要从readfds和writefds中知道哪些文件描述符的事件就绪.

如果我们要不断轮询等待文件描述符,则应用进程需要不断的重新设置readfds和writefds,因为每一次调用select,内核会修改readfds和writefds,所以我们需要在 应用程序设置一个数组 来保存程序需要等待的文件描述符,保证调用 select 的时候readfds 和 writefds中的将如下:

4.Select服务器

 如果是一个select服务器进程,则服务器进程会不断的接收有新链接每个链接对应一个文件描述符,如果想要我们的服务器能够同时等待多个链接的数据的到来,我们监听套接字listen_sock读取新链接的时候,我们需要将新链接的文件描述符保存到read_arrys数组中,下次轮询检测的就会将新链接的文件描述符设置进readfds中,如果有链接关闭,则将相对应的文件描述符从read_arrys数组中拿走

一张图看懂select服务器:

简易版的select服务器:

server.hpp文件:

#pragma once                                                                                                           
  #include<iostream>    
  #include<sys/socket.h>    
  #include<sys/types.h>    
  #include<netinet/in.h>    
  #include<string.h>    
  using std::cout;    
  using std::endl;    
  #define BACKLOG 5    
      
  namespace sjp    
    class server    
      public:    
      static int Socket()    
        int sock=socket(AF_INET,SOCK_STREAM,0);    
        if(sock>0)    
        return sock;    
        if(sock<0)    
          exit(-1);    
W>        
      
      static bool Bind(int sockfd,short int port)    
        struct sockaddr_in lock;    
        memset(&lock,'\\0',sizeof(lock));    
        lock.sin_family=AF_INET;    
        lock.sin_port=htons(port);    
        lock.sin_addr.s_addr=INADDR_ANY;    
        if(bind(sockfd,(struct sockaddr*)&lock,(socklen_t)sizeof(lock))<0)    
                  exit(-2);    
            
        return true;    
          

     static bool Listen(int sockfd)
        if(listen(sockfd,BACKLOG)<0)
          exit(-3);
        
        return true;
      
    ;
  

 select_server.hpp文件

#pragma once                                                                                                           
  #include<vector>
  #include"server.hpp"
  #include<unistd.h>
  #include<time.h>
  
  namespace Select
    class select_server
      private:
        int listen_sock;//监听套接字    
        int port;    
          
      public:    
        select_server(int _port):port(_port)    
      
        //初始化select_server服务器    
        void InitServer()    
          listen_sock=sjp::server::Socket();    
          sjp::server::Bind(listen_sock,port);    
          sjp::server::Listen(listen_sock);    
            
      
      
        void Run()    
          std::vector<int> readfds_arry(1024,-1);//readfds_arry保存读事件的文件描述符    
          readfds_arry[0]=listen_sock;//将监听套接字保存进readfds_arry数组中    
          fd_set readfds;    
          while(1)    
          FD_ZERO(&readfds);    
          int nfds=0;    
          //将read_arry数组中的文件描述符设置进程readfds_arry位图中    
          for(int i=0;i<1024;i++)    
              
            if(readfds_arry[i]!=-1)    
            FD_SET(readfds_arry[i],&readfds);    
           if(nfds<readfds_arry[i])
              nfds=readfds_arry[i];
            
            
          
  
          //调用select对readfds中的文件描述符进行等待数据
          switch(select(nfds+1,&readfds,NULL,NULL,NULL))
            case 0:
              //没有一个文件描述符的读事件就绪
              cout<<"select timeout"<<endl;
              break;
            case -1:
              //select失败
              cout<<"select error"<<endl;
            default:
              
              //有读事件发生
                Soluation(readfds_arry,readfds);
                break;
              
                     
          
        
                                                                                                                         
        void Soluation(std::vector<int>& readfds_arry,fd_set readfds)
W>        for(int i=0;i<readfds_arry.size();i++)
            if(FD_ISSET(readfds_arry[i],&readfds))
            
              if(readfds_arry[i]==listen_sock)
                //有新链接到来
                struct sockaddr peer;
                socklen_t len;                                                                                           
                int newfd=accept(listen_sock,&peer,&len);
                cout<<newfd<<endl;
                //将新链接设置进readfds_arry数组中
                AddfdsArry(readfds_arry,newfd);
              
              else
                //其他事件就绪
                char str[1024];
                int sz=recv(readfds_arry[i],&str,sizeof(str),MSG_DONTWAIT);
                switch(sz)
                  case -1:
                    //读取失败
                    cout<<readfds_arry[i]<<": recv error"<<endl;
                    break;
                  case 0:
                    //对端关闭
                    readfds_arry[i]=-1;
                    cout<<"peer close"<<endl;
                    break;
                  default:
                    str[sz]='\\0';
                    cout<<str<<endl;
                    break;
                
              
            
          
        

        void AddfdsArry(std::vector<int>& fds_arry,int fd)
W>        for(int i=0;i<fds_arry.size();i++)
            if(fds_arry[i]==-1)
              fds_arry[i]=fd;
              break;
            
          
        
    ;
         

 select_server.cc文件

#include"select_server.hpp"    
    
int main(int argv,char* argc[])    
  if(argv!=2)    
    cout<<"./selectserver port"<<endl;    
    exit(-4);    
      
                                                                                                                         
  int port=atoi(argc[1]);//端口号
  Select::select_server* sl=new Select::select_server(port);    
  sl->InitServer();                                             
  sl->Run();                                                                                                   
        

测试:

 

5.Select的缺陷

  • 由于fd_set的上限是1024,所以select能等待的读事件的文件描述符和写事件的文件描述是有上限的,如果作为一个大型服务器,能够同时链接的客户端是远远不够的。
  • 每次应用进程调用一次select之前,都需要重新设定writefds和readfds,如果进行轮询调用select,这对影响cpu效率。
  • 内核每一次等待文件描述符 都会重新扫描所有readfds或者writefds中的所有文件描述符,如果有较多的文件描述符,则会影响效率。

以上是关于Linux——select详解的主要内容,如果未能解决你的问题,请参考以下文章

select函数详解及应用

linux too many files

Linux——select详解

Linux——select详解

linux select函数详解

EPOLL使用详解