非阻塞IO-多路复用select函数用法及其源代码解读

阻塞IO与非阻塞IO

IO模型

IO本质是基于操作系统接口来控制底层的硬件之间数据传输,并且在操作系统中实现了多种不同的IO方式

IO模型描述的是不同的IO方式,比较常用的几种:

  • 阻塞型IO模型
  • 非阻塞型IO模型
  • 多路复用IO模型
  • 信号驱动IO模型

阻塞IO模型

当程序发出IO请求后,阻塞进程(让进程进入睡眠状态),资源就绪后唤醒进程继续执行

一般默认的IO操作都是阻塞的,即程序发出IO请求后,如果IO操作没有完成,则进程会被阻塞,直到IO操作完成后才会返回结果

非阻塞IO模型

非阻塞IO模型的特点是当发出IO请求后,如果IO操作没有完成,则立即返回,告诉调用者IO操作还未完成,并不等待IO操作完成

在这里插入图片描述

  • 实现非阻塞IO模型,需要设置O_NONBLOCK标志,表示打开非阻塞模式
  • 设置方式有两种:
    • 使用fcntl函数设置
    • 使用open函数设置,如: fd = open(filename, O_NONBLOCK)

fcntl函数设置非阻塞模式:

#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

  • fd参数: 文件描述符
  • cmd参数:
    • F_SETFL: 设置文件状态的标志
    • F_GETFL: 获取文件状态的标志
    • F_SETFD: 设置文件描述符的附加标志
    • F_GETFD: 获取文件描述符的附加标志
  • arg参数:
    • O_NONBLOCK: 设置非阻塞模式
    • O_BLOCK: 设置阻塞模式
// todo 设置非阻塞的IO模型
//将标准输入改为非阻塞的IO模式
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(){
   

    int flags;//保存原来的标志位
    flags = fcntl(0, F_GETFL);//获取原来的标志位
    flags |= O_NONBLOCK;//追加一个标志位,设置非阻塞的IO模式
    fcntl(0, F_SETFL, flags);//设置新的标志位


    char c;
    while(1){
   
        fgets(&c, 1, stdin);
        printf("read %c\n", c);
    }

    return 0;
}

open函数设置非阻塞模式:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags, mode_t mode);

  • flags参数:

    • O_NONBLOCK: 设置非阻塞模式
    • O_BLOCK: 设置阻塞模式

多路复用IO模型

多路复用IO模型是利用select、poll、epoll等系统调用,可以同时监视多个文件描述符,一旦某个文件描述符就绪,则立即通知调用者

  • select: 监视的文件描述符数量有限制,一般1024个
  • poll: 监视的文件描述符数量没有限制
  • epoll: 监视的文件描述符数量没有限制,支持水平触发和边缘触发

本质上是通过复用一个进程来处理多个IO请求:
由内核来监控多个文件描述符是否可以进行IO操作,一旦可以进行IO操作,则立即通知调用者,否则继续等待

select 多路复用方案

select模型的基本思路是:

  • 通过单进程创建一个文件描述符集合,将需要监控的文件描述符添加到这个集合中
  • 由内核负责监控文件描述符是否可以进行读写,一旦可以读写,则通知相应的进程可以进行相应的 I/O 操作
  • 在这里插入图片描述

select模型的缺点:

  • 单个进程能够监视的文件描述符数量有限,因此在大量连接的情况下,效率较低
  • 同时监视的文件描述符数量受限,因此在大量并发的情况下,效率较低

函数实现

select 函数

监控一组文件描述符,阻塞当前进程,由内核检测相应的文件描述符是否就绪,一旦有文件描述
符就绪,将就绪的文件描述符拷贝给进程,唤醒进程处理
在这里插入图片描述

函数原型:

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

函数参数:

  • nfds: 集合中最大的文件描述符号+1
  • readfds: 可读文件描述符集合的指针
  • writefds: 可写文件描述符集合的指针
  • exceptfds: 其他文件描述符集合的指针
  • timeout: 超时时间结构体变量的指针

可用宏:

    • FD_SETSIZE: 文件描述符集合的最大容量
    • FD_SET(int fd, fd_set* set )//设置文件描述符集合
    • FD_CLR( int fd, fd_set* set )//清除文件描述符集合
    • FD_ISSET( int fd, fd_set* set )//判断文件描述符是否在文件描述符集合中
    • FD_ZERO( fd_set* set )//清空文件描述符集合

函数返回值:

  • 若超时,返回0
  • 若有文件描述符就绪,返回就绪的文件描述符个数

示例: 多路复用IO select 函数,使用select 函数监控标准输入,有输入打印相应的信息

// todo 多路复用IO  select 函数,,使用select 函数监控标准输入,有输入打印相应的信息
#include <stdio.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int main(){
   

    int ret;
    int maxfd=0;
    fd_set readfds;// 定义一个文件描述符集合
    FD_ZERO(&readfds);// 初始化文件描述符集合
    FD_SET(0,&readfds);// 将标准输入加入文件描述符集合

    struct timeval timeout; // 定义一个超时时间
    timeout.tv_sec=3; // 设置超时时间
    timeout.tv_usec=0;
    //? 3s后超时,如果文件描述符集合没有任何描述符就绪,就会超时返回
    //? timeout超时时间,会被select函数首次超时修改,循环时候需要重新赋值
    //? select函数还会将每次就绪的文件描述符直接拷贝到原集合中,相当于将未就绪的从文件中删除了
    //? 如果超时返回,相当于原集合直接被清空了.
    struct timeval temp;
    fd_set tempfds;
    while (1){
   
        temp=timeout; // 超时时间重新赋值
        tempfds=readfds; // 文件描述符集合重新赋值
        // 调用select函数,监控文件描述符集合中的文件描述符
        //@param maxfd: 最大的文件描述符
        //@param readfds: 监控的文件描述符集合
        //@param writefds: 监控可写的文件描述符集合
        //@param exceptfds: 监控异常的文件描述符集合
        //@param timeout: 超时时间
        //@return: 返回值是监控的文件描述符集合中就绪的文件描述符个数
        ret=select( maxfd+1, &tempfds, NULL, NULL, &temp); // 监控标准输入
        if (ret<0){
   
            perror("select error");
            return -1;
        }
        if (ret==0){
   
            //超时
            printf("timeout\n");
            continue;
        }

        if (ret>0){
   //有文件描述符就绪

            //! FD_ISSET函数功能:判断文件描述符fd是否在文件描述符集合fdset中
            //! @param fd: 文件描述符
            //! @param fdset: 文件描述符集合
            if (FD_ISSET(0,&readfds)){
   
                //标准输入就绪
                char buffer[1024]={
   0};
                fgets(buffer,1024,stdin);
                printf("input:%s",buffer);
            }
        }

        }
    return 0;
}

select 函数原理:

//select 函数原理
//函数原型:
#include <sys/select.h>
extern int select (int __nfds, fd_set *__restrict __readfds,
fd_set *__restrict __writefds,
fd_set *__restrict __exceptfds,
struct timeval 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

可能只会写BUG

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

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

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

打赏作者

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

抵扣说明:

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

余额充值