리눅스에서 select를 사용하는 이유

青灯夜游
풀어 주다: 2023-01-31 09:56:12
원래의
1163명이 탐색했습니다.

select를 사용하면 개발자가 동시에 여러 파일 버퍼를 기다릴 수 있으므로 IO 대기 시간을 줄이고 프로세스의 IO 효율성을 향상시킬 수 있습니다. select() 함수는 프로그램이 여러 파일 설명자를 모니터링하고 하나 이상의 모니터링된 파일 설명자가 소위 "준비" 상태가 될 때까지 기다릴 수 있도록 하는 IO 다중화 기능입니다. 설명자는 더 이상 차단되지 않으며 읽기, 쓰기 가능 및 예외를 포함한 특정 유형의 IO 작업에 사용될 수 있습니다.

리눅스에서 select를 사용하는 이유

이 튜토리얼의 운영 환경: linux7.3 시스템, Dell G3 컴퓨터.

select는 헤더 파일 #include 에 있는 컴퓨터 기능입니다. 이 함수는 읽기, 쓰기 또는 예외와 같은 파일 설명자 변경을 모니터링하는 데 사용됩니다.

1. 선택 기능 소개

선택 기능은 IO 다중화 기능으로, 파일 설명자에서 이벤트가 준비될 때까지 기다리는 것입니다. 시간 영역, IO 대기 시간을 줄이고 프로세스의 IO 효율성을 향상시킵니다.

select() 함수를 사용하면 프로그램이 여러 파일 설명자를 모니터링하고 모니터링되는 파일 설명자 중 하나 이상이 "준비"될 때까지 기다릴 수 있습니다. 소위 "준비" 상태는 파일 설명자가 더 이상 차단 상태가 아니며 읽기, 쓰기 가능 및 예외 발생을 포함한 특정 유형의 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, Exceptfds 유형은 모두 fd_set인데 fd_set 유형은 무엇인가요?

  • fd_set 유형은 본질적으로 비트맵 이며, bitmap 의 위치는 해당 파일 설명자를 나타내며, 내용은 파일 설명자가 유효한지 여부를 나타내며, 1은 해당 위치의 파일 설명자를 나타냅니다. 유효함, 0은 이 위치의 파일 설명자가 유효하지 않음을 의미합니다.
  • 비트맵에 파일 설명자 2와 3이 설정되어 있으면 비트맵은 1100을 나타냅니다.
  • fd_set의 상한은 1024개의 파일 설명자입니다.

readfds

  • readfds는 읽기 이벤트를 기다리는 파일 설명자 모음입니다. 읽기 이벤트에 관심이 없다면(버퍼에 데이터가 있음) NULL 값을 전달할 수 있습니다. .
  • 애플리케이션 프로세스와 커널 모두 readfds를 설정할 수 있습니다. readfds를 설정하는 애플리케이션 프로세스는 readfds에 있는 파일 설명자의 읽기 이벤트를 기다리도록 커널에 알리는 것입니다. readfds 설정은 응용 프로그램 프로세스의 어떤 읽기 이벤트가 적용되는지 알려주기 위한 것입니다

writefds리눅스에서 select를 사용하는 이유

readfds와 유사하게 writefds는 쓰기 이벤트를 기다리는 컬렉션입니다(공간이 있는지 여부) 버퍼에서) 쓰기 이벤트에 관심이 없으면 값을 NULL로 전달할 수 있습니다.

excessfds

커널이 해당 파일 설명자가 예외

를 수신할 때까지 기다리는 경우 는 실패한 파일 설명자를 Exceptfds 로 설정합니다. NULL 값을 전달합니다. timeout

커널에서 시간 선택 블록을 설정합니다. 비차단으로 설정하려면 NULL로 설정하세요. 5초 동안 차단하도록 선택하려면 struct timeval time={5,0}

;을 생성합니다. 여기서 struct timeval의 구조 유형은

           struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };
로그인 후 복사

반환 값

입니다. 파일이 없습니다. 설명자가 준비되면 0을 반환합니다.

호출이 실패하면 -1을 반환합니다.
  • 시간 초과 시 readfds에서 이벤트가 발생하면 남은 시간 초과 시간을 반환합니다.
  • 3.select의 워크플로

application process

kernel 둘 다 readfds 및 writefds에서 정보를 가져와야 합니다. 그중 커널은 readfds 및 writefds에서 기다려야 하는 파일 설명자를 알아야 합니다. writefds, 애플리케이션 프로세스는 readfds 및 writefds로부터 어떤 파일 설명자 이벤트가 준비된지 알아야 합니다.

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

리눅스에서 select를 사용하는 이유

4.Select服务器

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

一张图看懂select服务器:

리눅스에서 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();
}
로그인 후 복사

测试:

리눅스에서 select를 사용하는 이유

리눅스에서 select를 사용하는 이유

5.Select的缺陷

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

推荐学习:Linux视频教程

위 내용은 리눅스에서 select를 사용하는 이유의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!