高级IO部分

如图:
在这里插入图片描述

五种IO模型

我们先讲一个例子:我们去食堂吃饭,点餐后进行等待,现在有5种情况:

(1)A同学点餐之后一动不动就在窗口等着叫号,即阻塞IO;
(2)B同学点餐之后就开始玩手机,时不时看一下餐好了没有即非阻塞IO(轮询);
(3)C同学点餐之后告诉自己旁边的同学餐好了叫一下他,然后开始玩手机,即信号驱动IO;
(4)D同学发现有好几个窗口都可以排到这份餐于是他在这些窗口都排了号,等待任意一个窗口即可,即IO多路转接;
(5)E同学则是让自己的同学去帮他点餐,在拿到餐之后通知他来吃即可,即异步IO;

这5种情况相当于5种IO模型,同样的还有钓鱼的例子;
IO操作的流程=等待IP条件具备+数据拷贝
“等”的意思是等条件就绪,例如input等输入条件就绪,output是等输出条件就绪;
那么高效IO=减少等的比重

  • 阻塞IO
    在内核将数据准备好之前,系统调用会一直等待,所有的套接字默认都是阻塞方式,直到条件具备,完成IO操作后调用返回(A同学的情况);如图:
    在这里插入图片描述
    在这里插入图片描述
  • 非阻塞IO
    为了完成IO操作发起调用,若当前不具备IO操作条件,则立即报错返回,可以干点其他事情,循环过来进行判断(B同学的例子);
    非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用.
    如图:
    在这里插入图片描述
    在这里插入图片描述
  • 信号驱动IO
    提前对IO信号自定义处理方式,当IO条件具备时,操作系统通过信号通知进程,这时候IO条件已经具备,直接发起调用进行数据拷贝(C同学的例子);如图:
    在这里插入图片描述
    在这里插入图片描述
  • IO多路复用/多路转接
    虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符select负责等,并且一次等多个文件描述符)的就绪状态(D同学的例子);如图:
    在这里插入图片描述
  • 异步IO
    IO操作条件的等待与拷贝都由操作系统来进行等待与操作,等到IO操作完成之后,通过信号通知进程,进程直接对数据进行操作(E同学的例子);如图:
    在这里插入图片描述
    IO的几种操作中,IO操作效率越来越高,但是流程和控制却越来越复杂;
I/O多路转接/多路复用

多路转接/多路复用IO是对大量的文件描述符进行就绪事件监控(需要对描述符进行监控的场景都可以使用多路转接模型),就绪事件就是可读/可写/异常事件;
监控的好处:让进程可以只针对就绪了指定事件的描述符进行操作,提高效率性能,避免了因为对没有就绪的描述符操作导致的阻塞;

  • select
    select系统调用是用来让程序监视多个文件描述符的状态变化的,程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变;
    select一般用于socket网络编程中,在网络编程中,经常会遇到很多阻塞的函数,例如recv、recvfrom、connect,当函数不能成功执行时,程序会一直阻塞在这里,无法执行下面的代码,select就可以实现非阻塞编程;
    他是一个轮询函数,即当循环询问文件节点,可设置超时时间,超时时间到了就跳过代码继续执行;
    select的实现:
    1、进程定义指定事件的描述符集合,初始化清空集合,将需要监控的描述符根据需要监控的事件添加到指定的集合中;
    2、发起调用,将事件描述集合从用户态拷贝到内核中进行轮询遍历判断,若超时等待/有描述符就绪事件则调用返回,在调用返回的时候,会将事件描述符集合中没有就绪的事件描述符从集合中移除;
    3、进程在监控调用返回的时候,得到就绪的描述符集合,判断哪个描述符仍然还在哪个集合中,决定哪个描述符就绪了哪个事件,进而进行操作;
    select函数原型:
#include <sys/select.h>
/* According to earlier standards */
#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);

其中参数nfds表示需要监视的最大的文件描述符值+1;readfds、writefds、exceptfds这三个是文件描述符集,用位图表示,例如readfds,位图的bit位的位置代表文件描述符,bit位的内容代表在调用时用户命令内核要帮我关心哪些文件描述符的读事件,返回时代表内核告诉用户所关心的众多文件描述符当中哪些已经就绪,此时我们代表只有1号文件描述符就绪(读文件描述符),其他两个一样;timeout代表调用select的等待时间;
select的后4个参数都是输入输出参数,在每次调用时需要对参数进行重新设定;
select的返回值代表已经就绪的文件描述符个数,返回值为0代表timeout超时,返回值小于0代表出错;

struct timeval结构体用来设置超时时间,可以精确到秒和微秒;

struct timeval
{
	time_t tv_sec;
	suseconds_t tv-usec;
};

select相关函数

void FD_CLR(int fd, fd_set *set);//在指定位图中删掉指定bit位
int  FD_ISSET(int fd, fd_set *set);//判定一个文件描述符是否在该集合中
void FD_SET(int fd, fd_set *set);//用来设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set);//清除描述词组set的全部位

select机制的优势
例如recv操作,在默认的阻塞模式下的套接字中,recv会阻塞在那里,直到套接字连接是哪个有数据可读,把数据读到buf中后recv函数才能返回,不然会一直阻塞,在单线程的程序中这种情况会导致主线程被阻塞,整个程序锁死,若是永远没有数据过来,程序就会永远锁死,那么我们就要使用select模型,它是使用一种有序的方式,对多个套接字进行统一管理和调度;
select执行过程
用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回,当数据到达时,socket被激活,select函数返回,用户线程正式发起read请求,读取数据并继续执行;(连接建立事件可以被当成读事件处理)
从流程上来看,使用select函数进行IO请求和同步阻塞模型并没有多大的区别,甚至效率不高,但是使用select的优势是用户可以在一个线程内同时处理多个socket的IO请求,在同步阻塞模型中必须使用多线程;
select特点
我们知道,select模型的优点是效率相对较高,可以在一个线程内同时处理多个socket的IO请求,而且它遵循POSIX标准,跨平台移植性比较好;而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的;
但是也有缺点,即:
(1)select对描述符进行监控有最大数量限制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值