Linux-select剖析

本文详细介绍了select函数在I/O多路复用中的作用,参数解释,工作流程,以及如何在服务器中使用select处理新连接和数据读写。特别提到了其在大量并发连接时的局限性及性能影响。

一、select函数

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

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

二、参数介绍

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

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();                                                                                                   
}        

五.Select的缺陷

  • 由于fd_set的上限是1024,所以select能等待的读事件的文件描述符和写事件的文件描述是有上限的,如果作为一个大型服务器,能够同时链接的客户端是远远不够的。
  • 每次应用进程调用一次select之前,都需要重新设定writefds和readfds,如果进行轮询调用select,这对影响cpu效率。
  • 内核每一次等待文件描述符 都会重新扫描所有readfds或者writefds中的所有文件描述符,如果有较多的文件描述符,则会影响效率。
<think> 我们参考引用[1]和[2]中的安装步骤,用户已经通过`pip3 install solc-select`安装了solc-select,但执行命令时提示未找到。这通常是因为安装路径没有添加到系统的PATH环境变量中。 根据引用[3]中pip show solc-select的结果,我们可以看到安装位置在`/home/wxy/.local/lib/python3.10/site-packages`,但实际可执行文件通常会在`~/.local/bin`目录下(对于Linux系统,当使用`pip3 install --user`时,默认安装到用户目录下的`.local/bin`)。 因此,解决步骤为: 1. 确认`~/.local/bin`是否在PATH环境变量中。 在终端执行:`echo $PATH`,查看输出是否包含类似`/home/你的用户名/.local/bin`的路径。 2. 如果没有,则需要将`~/.local/bin`添加到PATH中。 可以将以下行添加到`~/.bashrc`或`~/.zshrc`(取决于你使用的shell)中: ```bash export PATH="$HOME/.local/bin:$PATH" ``` 然后执行`source ~/.bashrc`(或`source ~/.zshrc`)使更改生效。 3. 如果已经存在,但仍然无法执行,可能是由于安装过程中出现了问题,可以尝试重新安装。 4. 如果重新安装后仍然不行,可以检查`~/.local/bin`中是否有名为`solc-select`的可执行文件。 5. 另外,注意在安装时是否使用了`sudo`。如果使用了`sudo`,则安装位置可能在系统目录(如`/usr/local/bin`)下,此时普通用户可能没有权限,或者需要确认该目录在PATH中。 根据引用[1],安装时使用了`sudo pip3 install solc-select`,这意味着安装在了系统目录(如`/usr/local/bin`)。因此,我们可以检查系统目录是否在PATH中,通常这些目录默认都在PATH中。 所以,可能的原因为: - 如果使用`sudo`安装,则命令应该可以直接使用,除非安装失败。 - 如果没有使用`sudo`,而是用`pip3 install solc-select`(用户模式安装),则需要确保`~/.local/bin`在PATH中。 根据用户描述,安装时使用了`sudo pip3 install solc-select`,那么应该检查系统目录。可以尝试: - 查看`/usr/local/bin`目录下是否有`solc-select`文件:`ls /usr/local/bin | grep solc-select` - 如果没有,可以尝试重新安装,并观察安装日志。 另外,也可以使用`which solc-select`来查看系统是否能找到该命令。 如果上述方法都不能解决,可以考虑使用绝对路径执行,例如: `/usr/local/bin/solc-select versions` 或者,如果使用用户安装模式,则可能是`~/.local/bin/solc-select`。 总结步骤: 1. 确定安装位置: - 用户模式下:`~/.local/bin/solc-select` - 系统模式下:`/usr/local/bin/solc-select` 2. 将安装位置添加到PATH(如果不在PATH中)并激活。 3. 如果安装位置在PATH中,但命令仍不可用,检查文件权限(是否可执行)和文件是否存在。 4. 重新安装:如果文件不存在,重新安装。 5. 如果重新安装后仍不存在,可能是安装过程中出现了错误,检查安装日志。 具体操作: 步骤1:检查安装位置 - 对于系统安装(使用sudo): ```bash sudo find / -name solc-select 2>/dev/null ``` - 对于用户安装(不使用sudo): ```bash find ~ -name solc-select 2>/dev/null ``` 步骤2:将找到的路径添加到PATH(如果不在PATH中),或者创建一个符号链接到已在PATH中的目录(如`/usr/local/bin`): ```bash sudo ln -s /path/to/solc-select /usr/local/bin/solc-select ``` 步骤3:如果文件存在,检查权限: ```bash ls -l /path/to/solc-select ``` 如果没有可执行权限,添加权限: ```bash chmod +x /path/to/solc-select ``` 步骤4:如果以上步骤无效,尝试重新安装: ```bash sudo pip3 uninstall solc-select sudo pip3 install solc-select ``` 注意:在重新安装后,再次查找solc-select的位置,并确保它在PATH中。 另外,引用[3]中显示安装的版本是1.0.4,我们可以通过`pip3 show solc-select`来查看安装位置(Location字段),然后进入该位置下的bin目录(如果有)或者查看是否有可执行文件。 因为不同系统或pip版本安装路径可能略有不同,所以需要灵活处理。 最后,如果用户使用的是非bash的shell(如zsh),请确保修改对应的配置文件(如.zshrc)。 希望这些步骤能帮助解决问题。 </think> 要解决 `pip3 install solc-select` 安装成功后执行命令提示未找到的问题,请按以下步骤操作: ### 原因分析 根据引用[3],`solc-select` 默认安装在用户目录下(如 `/home/wxy/.local/bin`),而该路径可能未加入系统 `PATH` 环境变量中[^3]。 ### 解决方案 #### 1. 确认安装位置 ```bash pip3 show solc-select | grep Location ``` 输出示例: ``` Location: /home/your_username/.local/lib/python3.10/site-packages ``` 记下路径中的 `.local/lib/python3.10/site-packages`,可执行文件实际在 **`.local/bin`** 目录下。 #### 2. 将安装路径加入 PATH ```bash # 临时生效 (当前终端有效) export PATH="$HOME/.local/bin:$PATH" # 永久生效 (添加到shell配置文件) echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc # Bash用户 source ~/.bashrc # 立即生效 # Zsh用户用此命令 echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc source ~/.zshrc ``` #### 3. 验证命令是否可用 ```bash # 检查命令位置 which solc-select # 查看版本 (确认安装成功) solc-select --version ``` #### 4. 如果仍不可用:手动创建符号链接 ```bash sudo ln -s $HOME/.local/bin/solc-select /usr/local/bin/solc-select ``` ### 安装测试 ```bash # 安装指定版本 solc-select install 0.8.11 # 查看已安装版本 solc-select versions ``` 预期输出: ``` Installed versions: 0.8.11 (current) ``` ### 注意事项 1. **权限问题**:如果安装时用了 `sudo pip3 install`,可执行文件可能在 `/usr/local/bin` 中,此时直接运行即可 2. **Shell重载**:修改 `PATH` 后务必执行 `source ~/.bashrc` 或重启终端 3. **Python版本**:确保 `pip3` 对应正确的 Python 版本(可用 `pip3 -V` 检查) > 💡 提示:根据引用[4],`solc-select` 的核心功能是管理多版本 Solidity 编译器[^4],正确配置后即可使用 `solc-select install` 和 `solc-select use` 管理编译器版本。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值