详解I/O多路转接之select

什么是多路转接IO

对大量的描述符进行I/O事件监控—可以告诉进程现在有哪些描述符就绪了,然后进行就可以只针对就绪了的描述符进行响应操作,避免对没有就绪的I/O操作所导致的效率降低和流程阻塞

  • IO事件:可读事件/可写事件/异常事件

I/O多路转接模型之select

select介绍

系统提供select函数来实现多路复用输入/输出模型.

  • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
  • 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变

操作流程:

1.程序员定义某个事件的描述符集合(可读事件的描述符集合/可写事件的描述符集合/异常事件的描述符集合),初始化清空集合

对哪个描述符关心什么事件,就把这个描述符添加到相应时间的描述符集合中

2.发起监控调用,将集合拷贝到内核中进行监控,监控的原理原理是轮询遍历判断

可读事件的就绪:接收缓冲区中数据的大小低于水位标记(量化标准–通常默认为1个字节)
可写事件的就绪:发送缓冲区中剩余空间的大小大于低水位标记(量化标准—通常默认为1个字节)
异常事件的就绪:描述符是否产生了某个异常

3.监控的调用返回,表示监控出错/有描述符就绪/监控等待超时了

并且调用返回的时候,将事件监控的描述符集合中的未就绪描述符从集合中移除了----(集合中仅仅保留就绪的描述符
因为返回的时候修改了集合,因此下次监控的时候,就需要重新向集合中添加描述符

4.程序员轮询判断那个描述符仍然在哪个集合中,就确定这个描述符是否就绪了某个事件,然后进行对应事件的操作即可

select并不会直接返回给用户就绪的描述符,而是返回了就绪的描述符集合,因此需要程序员进行判断

代码操作:

1.定义集合—struct fd_set,成员只有一个数组(当做二进制位图使用)添加描述符就是将描述符对应的比特位置1

因此select能够监控的描述符数量,取决于二进制比特位多少,而比特位多少取决于宏 ,FD_SETSIZE,默认等于1024

void FD_ZERO(fd_set* set);//初始化清空集合
void FD_SET(int fd,fd_set* set);//将fd描述符添加到set集合中
  1. select开始调用监控
int select(int nfds,fd_set* readfds, 
		fd_set* writefds, fd_set *exceptfds, 
		struct timeval* timeout);
  • nfds:当前监控的集合中最大的描述符+1,减少遍历次数
  • readfds/writefds/exceptfds:可读/可写/异常三种事件的描述符集合
  • timeout:struct timeval{tv_sec;tv_usec};时间结构体,通过这个时间决定select阻塞/非阻塞/限制超时的阻塞

若timeout为NULL,则表示阻塞监控,直到描述符就绪/出错才会返回
若timeout中的成员数据为0,则表示阻塞,监控的时候若没有描述符就绪,则立即超时返回
若timeout中成员数据不为0,则在指定的时间内,没有就绪则超时返回

返回值:>0表示就绪的描述符个数
==0表示没有描述符就绪,超时返回
<0表示是监控出错

3.调用返回,返回给程序员,就绪的描述符集合,程序员遍历判断哪个描述符还在哪个集合中,就是就绪了那个事件

int FD_ISSET(int fd,fd_set *set);
//判断fd描述符是否在集合中

注意:因为select返回时会修改集合,因此每次监控的时候都要重新添加描述符

4.若对描述符不想进行监控了,则从集合中移除描述符

void FD_CLR(int fd,fd_set* set);
		//从描述符中删除描述符fd

select就绪条件

读就绪
  • socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT. 此时可以无阻塞的读该文件 描述符, 并且返回值大于0;
  • socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
  • 监听的socket上有新的连接请求;
  • socket上有未处理的错误;
写就绪
  • socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记 SO_SNDLOWAT, 此时可以无阻塞的写, 并且返回值大于0;
  • socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE 信号;
  • socket使用非阻塞connect连接成功或失败之后;
  • socket上有未读取的错误;
异常就绪
  • socket上收到带外数据. 关于带外数据, 和TCP紧急模式相关(TCP协议头中, 有一个紧急指针的字段).

select优缺点分析

缺点
  1. select对描述符进行监控有最大数量上限,上限取决于宏-FD_SETSIZE,默认大小是1024
  2. 在内核中进行监控,是通过轮询遍历判断实现的,性能会随着描述符的增多而下降
  3. 只能返回就绪的集合,需要进程进行轮询遍历判断才能得知那个描述符就绪了哪个事件
  4. 每次监控都需要重新添加描述符到集合中,每次监控都需要将集合重新拷贝到内核中
优点:

遵循POSIX标准,跨平台移植性好

使用select简单检测标准输入

#include<sys/select.h>
#include<stdio.h>
#include<sys/types.h>
#include<sys/time.h>
#include<unistd.h>
#include<cstdio>
#include<time.h>
int main(int argc,char* argv[]){
    fd_set rfds;
    FD_ZERO(&rfds);//初始化描述符集合
    FD_SET(0,&rfds);//将描述符fd添加到描述符集合
    while(1){
        printf(">");
        fflush(stdout);
        int ret=select(1,&rfds,NULL,NULL,NULL);//监控
        if(ret<0){//监控错误
            perror("select error");
            continue;
        }
        if(FD_ISSET(0,&rfds)){//描述符已就绪
            char buf[1024];
            read(0,buf,sizeof(buf)-1);
            printf("buf-->%s\n",buf);
        }else{//描述符未就绪
            perror("error! invaild fd\n");
            continue;
        }
        FD_ZERO(&rfds);//将描述符置为0
        FD_SET(0,&rfds);//将标准输入描述符添加到集合中
    }
    return 0;
}

运行结果:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44826356/article/details/107460942