ホームページ  >  記事  >  運用・保守  >  Linux で select を使用する理由

Linux で select を使用する理由

青灯夜游
青灯夜游オリジナル
2023-01-30 19:04:541258ブラウズ

select を使用すると、開発者は複数のファイル バッファーを同時に待機できるため、IO 待機時間を短縮し、プロセスの IO 効率を向上させることができます。 select() 関数は、プログラムが複数のファイル記述子を監視し、監視されている 1 つ以上のファイル記述子が「準備完了」になるのを待機できるようにする IO 多重化関数です。いわゆる「準備完了」状態とは、ファイルを指します。記述子はブロックされなくなり、読み取り可能、書き込み可能、​​例外を含む特定の種類の IO 操作に使用できるようになりました。

Linux で select を使用する理由

#このチュートリアルの動作環境: linux7.3 システム、Dell G3 コンピューター。

select は、ヘッダー ファイル #include にあるコンピューター関数です。この関数は、ファイル記述子の変更 (読み取り、書き込み、または例外) を監視するために使用されます。

1. select 関数の概要

select 関数は IO 多重化関数であり、その主な機能はファイル記述子のイベントの準備が完了するのを待つことです。 , select を使用すると、複数のファイル バッファーを同時に待機できるようになり、IO 待機時間が短縮され、プロセスの IO 効率が向上します。

select() 関数を使用すると、プログラムは複数のファイル記述子を監視し、監視されている 1 つ以上のファイル記述子が「準備完了」になるまで待機できます。いわゆる「準備完了」状態とは、ファイル記述子がブロッキング状態ではなくなり、読み取り可能、書き込み可能、​​例外の発生など、特定のタイプの IO 操作に使用できることを意味します

#2. 関数パラメータの選択の概要
       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 は、fds タイプを除いてすべて fd_set です。 fd_set タイプですか?

  • fd_set タイプは本質的にビットマップ であり、 ビットマップ の位置は ## を表します。 # フェーズ 対応するファイル記述子 の内容は、ファイル記述子が有効かどうかを示します。1 は、この位置のファイル記述子が有効であることを意味し、0 は、この位置のファイル記述子が無効であることを意味します。 ファイル記述子 2 および 3 がビットマップに設定されている場合、ビットマップは 1100 を表します。
  • fd_set の上限は 1024 ファイル記述子です。
readfds

readfds は、読み取りイベントを待機している
  • ファイル記述子のセットです。 read events (バッファーにデータがある) を気にしない場合は、NULL 値を渡すことができます。 アプリケーション プロセスとカーネルの両方で readfds を設定できます。アプリケーション プロセスreadfds の設定は
  • カーネルにファイル記述子の読み取りイベントを待つように通知するためです。 readfds 内 .And Kernelreadfds の設定は #どの読み取りイベントが有効になるかをアプリケーション プロセスに指示するためです
writefds

Linux で select を使用する理由

readfds と同様に、writefds は書き込みイベントを待機しているコレクションです (

スペースはありますか) )気にしない場合は、イベントを書き込むときに、値 NULL を渡すことができます。

excelfds

カーネルが対応するファイル記述子を待っている間に

例外

が発生した場合、

will 失敗したファイル記述子は、Exceptfds に設定されます。エラー イベントを気にしない場合は、値 NULL を渡すことができます。 timeout

カーネル内の時間選択ブロックを設定します。非ブロッキングに設定したい場合は、NULL に設定します。 select を 5 秒間ブロックする場合は、

struct timeval time={5,0}

;

struct timeval の構造タイプは次のとおりです。

           struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };
戻り値

ファイル記述子の準備ができていない場合は 0 を返し、##呼び出しが失敗した場合は -1 を返します。

readfds でタイムアウト中のイベントが発生した場合、タイムアウトの残り時間を返します。
  • 3.ワークフローの選択

アプリケーション プロセス

カーネル 両方とも readfds と writefds から情報を取得する必要があります。その中で、カーネルはどのファイル記述子が readfds と writefds から待機する必要があるかを知る必要があり、アプリケーション プロセスはどのファイル記述子イベントが readfds と writefds から準備ができているかを知る必要があります。

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

Linux で select を使用する理由

4.Select服务器

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

一张图看懂select服务器:

Linux で 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);    
   }    
      
      static bool Bind(int sockfd,short int port){    
        struct sockaddr_in lock;    
        memset(&lock,&#39;\0&#39;,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){
        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]=&#39;\0&#39;;
                    cout<<str<<endl;
                    break;
                }
              }
            }
          }
        }

        void AddfdsArry(std::vector<int>& fds_arry,int fd){
       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();
}

测试:

Linux で select を使用する理由

Linux で select を使用する理由

5.Select的缺陷

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

推荐学习:Linux视频教程

以上がLinux で select を使用する理由の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。