如何快速理解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
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

世_生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值