• 技术文章 >运维 >linux运维

    linux为什么要用select

    青灯夜游青灯夜游2023-01-31 09:56:12原创56

    因为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类型是什么呢?

    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 */
               };

    返回值

    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);    
       }    
          
          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){
            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){
           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的缺陷

    推荐学习:Linux视频教程

    以上就是linux为什么要用select的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:linux
    上一篇:linux根目录是干什么的 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • linux挂载硬盘命令是mount命令吗• linux运行文件命令有哪些• linux怎么使用yum安装php• 详解如何增加Linux安全组端口(命令行方式)• Linux下systemctl、service与chkconfig命令的区别详解
    1/1

    PHP中文网