linux為什麼要用select

2023-01-31 06:02:07

因為select可以使開發者在同時等待多個檔案緩衝區,可減少IO等待的時間,能夠提高程序的IO效率。select()函數是IO多路複用的函數,允許程式監視多個檔案描述符,等待所監視的一個或者多個檔案描述符變為「準備好」的狀態;所謂的」準備好「狀態是指:檔案描述符不再是阻塞狀態,可以用於某類IO操作了,包括可讀,可寫,發生異常三種。

本教學操作環境:linux7.3系統、Dell G3電腦。

select是一個計算機函數,位於標頭檔案#include <sys/select.h> 。該函數用於監視檔案描述符的變化情況——讀寫或是異常。

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是為了告訴應用程序哪些讀事件生效

1.png

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中知道哪些檔案描述符的事件就緒.

2.png

如果我們要不斷輪詢等待檔案描述符,則應用程序需要不斷的重新設定readfds和writefds,因為每一次呼叫select,核心會修改readfds和writefds,所以我們需要在 應用程式設定一個陣列 來儲存程式需要等待的檔案描述符,保證呼叫 select 的時候readfds 和 writefds中的將如下:

3.png

4.Select伺服器

如果是一個select伺服器程序,則伺服器程序會不斷的接收有新連結每個連結對應一個檔案描述符,如果想要我們的伺服器能夠同時等待多個連結的資料的到來,我們監聽通訊端listen_sock讀取新連結的時候,我們需要將新連結的檔案描述符儲存到read_arrys陣列中,下次輪詢檢測的就會將新連結的檔案描述符設定進readfds中,如果有連結關閉,則將相對應的檔案描述符從read_arrys陣列中拿走

一張圖看懂select伺服器:

4.png

簡易版的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.png

6.png

5.Select的缺陷

  • 由於fd_set的上限是1024,所以select能等待的讀事件的檔案描述符和寫事件的檔案描述是有上限的,如果作為一個大型伺服器,能夠同時連結的使用者端是遠遠不夠的。
  • 每次應用程序呼叫一次select之前,都需要重新設定writefds和readfds,如果進行輪詢呼叫select,這對影響cpu效率。
  • 核心每一次等待檔案描述符 都會重新掃描所有readfds或者writefds中的所有檔案描述符,如果有較多的檔案描述符,則會影響效率。

推薦學習:Linux視訊教學

以上就是linux為什麼要用select的詳細內容,更多請關注TW511.COM其它相關文章!