如何快速理解Select原理

Posted 世_生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何快速理解Select原理相关的知识,希望对你有一定的参考价值。

目录


五种IO模型

  • 理解五种IO模型的基本概念

阻塞IO

  • 内核把数据准备好之前,系统调用一直等待(所以的套接字编程,默认都是阻塞方式)

非阻塞IO

  • 内核没有把数据准备好,系统调用依然直接返回,并返回并且返回EWOULDBLOCK错误码。

非阻塞IO往往需要程序员以循环的方式来读写文件描述符,这个过程称为轮询,对CPU来说是较大的浪费,一般只能在特定场合使用。

信号驱动IO

  • 内核将数据准备好了之后,向进程发送SIGIP信号通知进程进行IO操作

IO多路转接

  • 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件
    描述符的就绪状态

异步IO

  • 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据).

Select是什么

  • select是操作系统提供的一个系统接口
  • select可以监视多个文件描述符的状态变化
  • select监测到文件描述符有就绪事件时,进程会对就绪事件进行IO操作

读事件就绪:内核中有数据已经准备好了,等待进程调用系统接口(recv、read)将数据从内核拷贝到用户空间。
写事件就绪:内核中已经有空间了,等待进行调用系统接口(send、write)将数据写入到内核。

Select只是在完成这个过程,具有就绪事件通知机制

Select函数中参数的意义

   #include <sys/time.h>
   #include <sys/types.h>
   #include <unistd.h>
   
   函数原型
   int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

fd_set结构:位图(其中位图对应的位置表示要关心的文件描述符)
fd_set的结构是变量咋select的函数中是个输入输出型参数

输入:如果要监视文件描述符是否有数据进行读操作。fd_set readfds的结构对应的比特位上会置为1。

输出:如果有文件描述符要进行读操作。fd_set readfds的结构对应的比特位上会置为1。

nfds:监视的文件描述符的数量+1
readfds:监视读就绪的文件描述符
writefds:监视写就绪的文件描述符
exceptfds:监控异常发生达文件描述符集合

timeout:定时阻塞监控时间,3种情况

  1. NULL,永远等下去
  2. 设置timeval,等待固定时间
  3. 设置timeval里时间均为0,检查描述字后立即返回,轮询
    struct timeval
    long tv_sec; /* seconds /
    long tv_usec; /
    microseconds */
    ;

返回类型是int型,返回一个整形数.
成功:表示有多少个文件描述符有就绪事件发生。
错误:返回 -1
超时:返回 0

Select原理

原理:统一把文件描述符给select,让select帮我们来进行,有就绪事件发生时,用户再进行拷贝。(:交给内核的)

  • 将我们的文件描述符按照需要的等待事件(读或者写或者读写)分别添加到readfds、writefds的位图中。
  • select会将有事件发生的文件描述符写入readfds、writefds,返回给用户
  • 我们按照readfds、writefds的位图来对有事件的文件描述符进行读写操作

select缺点

每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便.
每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
select支持的文件描述符数量太小

Select的执行过程

写一段伪代码:

把需要监测的文件描述符都保存在一个数组中
for(;;)
	1、对readfds、writefds进行清空
	2、将数组中的文件描述符放入readfds、writefds中
	switch(select(监视的文件描述符总数+1,readfds ,wrtifds,nullptr,时间))
	case 0:
		break;
	case -1:
		break;
	default:
		有就绪事件发生
		1、有读事件发生,进行读操作
		2、有写事件发生,进行写操作
		循环查看数组中的文件描述符是否在readfds、writefds中,有则需要进行读写
		break;
	

维护位图的函数
void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位

代码实现

	void Start()
    
      InitAdd();/初始化记录文件描述符的数组,全为-1
      int max=-1;/文件描述符数量最大值
      sockadd[0]=lisent_sock;/将监听文件描述符放在数组的第一个位置
      for(;;)
        FD_ZERO(&readfds);/清空readfds的全部标识位
        for(auto i=0;i<NUM;i++)/添加文件描述符到readfds中
          if(sockadd[i]==-1)
            continue;
          
          FD_SET(sockadd[i],&readfds);/添加加文件描述符到readfds中
          if(max<sockadd[i])
            max=sockadd[i];/算出文件描述符的数量的最多数量
          
        
        struct timeval timeout=5,0;
        switch(select(max+1,&readfds,nullptr,nullptr,&timeout))
          case 0:/没有就绪事件发生
            break;
          case -1:/有异常退出
            break;
          default:/有就绪事件发生
            HandlerEvent();/执行相关函数
            break;
        
      
    
	void HandlerEvent()
    
      for(auto i=0;i<NUM;i++)
        if(sockadd[i]==-1)
          continue;
        
        /连接请求事件
        if(sockadd[i]==lisent_sock && FD_ISSET(sockadd[i],&readfds))
          struct sockaddr_in peer;
          socklen_t len=sizeof(peer);
          int sock=accept(sockadd[i],(struct sockaddr*)&peer,&len);    
          if(sock<0)
            continue;
          
          uint16_t peer_port = htons(peer.sin_port);
          std::string peer_ip = inet_ntoa(peer.sin_addr);
          std::cout << "get a new link: " << peer_ip << ":" << peer_port << std::endl;
          
          if(!addFd(sock))
            std::cout<<"assfd 失败"<<std::endl;
            close(sock);
          
          std::cout<<sock<<std::endl; 
        
        else
          /读写请求事件
          if(FD_ISSET(sockadd[i],&readfds))
            char buff[1024];
            ssize_t s=recv(sockadd[i],buff,sizeof(buff)-1,0);
            if(s==0)
                std::cout<<"没有数据读取";
                std::cout<<"client quit"<<std::endl;
                close(sockadd[i]);
                sockadd[i]=-1;
            
            else if(s>0)
              buff[s]=0;
            std::cout<<"echo# "<<buff<<std::endl;
            
            else
                std::cout<<"read errce"<<std::endl;
                close(sockadd[i]);
                sockadd[i]=-1;
            
          //fi
        //fi
      //rof
    //nur
	
	bool addFd(int sock)
    
      for(auto i=0;i<NUM;i++)
        if(sockadd[i]==-1)
          sockadd[i]=sock;
          return true;
        
      
      return false;
    

文件描述符的最大数量为1024个。
因为:

sizeof(fd_set)-128
128*8=1024

以上是关于如何快速理解Select原理的主要内容,如果未能解决你的问题,请参考以下文章

快速理解 VUEX 原理

深入理解Azure自动扩展集VMSS

求高中英语所有常用词组

solr facet 如何不分词

剖析搜索引擎分词的算法逻辑和语法思路

深入理解SQL原理:一条SQL查询语句是如何执行的?