(Linux) 高级IO

典型的五种IO模型:阻塞IO / 非阻塞IO / 信号驱动IO / 异步IO / 多路转接IO

IO完成的过程

  • 1、等待IO就绪(满足IO条件)
  • 2、进行数据拷贝

阻塞IO:发起IO调用,若IO条件不具备,则一直等待。

  • 优点:流程非常简单,代码操作简单,任务顺序操作;
  • 缺点:无法充分利用资源,任务处理效率比较低。

非阻塞IO:发起IO调用,若IO条件不具备,则立即报错返回,可以干点其他事情,完毕后循环回来重新发起IO请求。

  • 优点:相较于阻塞IO,任务处理效率高,利用IO等待时间干其他的事情;
  • 缺点:例程相较于阻塞IO更复杂,需要循环处理。

信号驱动IO:定义IO就绪信号处理方式,收到信号则认为IO就绪,发起IO调用;在处理方式中进行IO请求,进程可以一直干其他事情,等收到IO就绪信号的时候,会打断进程当前操作去处理进行IO;

  • 优点:相较于非阻塞IO,更加实时,资源利用更加充分;
  • 缺点:流程更加复杂,需要定义信号处理,既有主控流程又有信号处理流程,涉及到信号是否可靠的问题;

异步IO:IO顺序不确定,IO过程(等待+数据拷贝),由系统完成,不自己进行;
定义IO完成信号处理方式自定义,发起异步IO调用,告诉系统要完成什么功能,剩下的IO功能完全由系统完成,完成后通过信号通知进程。

  • 优点:对资源的利用最为充分,以最高效率进行任务处理;
  • 缺点:资源消耗比较高,流程最为复杂;

这四种IO,其实是处理效率的逐步增加,对资源的利用更加充分,流程越来越复杂的进化过程;

概念理解

  • 阻塞:为了完成一个功能,发起调用,若不具备完成的条件,则调用一直等待;
  • 非阻塞:为了完成一个功能,发起调用,若不具备完成功能的条件,则立即报错返回;
  • 阻塞和非阻塞的区别:常用于讨论调用函数是否阻塞,表示这个函数无法立即完成功能时是否立即返回;
  • 同步:功能完成的流程通常是顺序化的,并且功能是进程自身完成的;
  • 异步:功能完成的流程通常是不确定的,并且功能不是由进程自身完成的,由系统完成;
  • 异步的种类:1、异步阻塞- - -等着别人完成功能; / 2、异步非阻塞- - -不等待别人完成功能;
  • 同步与异步的区别:通常用于讨论功能的完成方式,表示一个功能是否顺序化且由自己来完成的;
  • 同步好还是异步好?:同步处理流程简单,同一时间占用资源少,但是异步处理效率高,同一时间占用资源多;

多路转接IO

主要用于进行大量的IO就绪事件监控,能够让我们的进程只针对就绪了指定事件的IO进行IO操作。
就绪事件:IO事件的就绪;
可读事件:一个描述符当前是否有数据可读;
可写事件:一个描述符当前是否可以写入数据;
异常事件:一个描述符是否发生了某些异常;

只对就绪的描述符进行IO操作有什么好处呢?- - -避免阻塞,并且提高效率
1、在默认的socket中,例如tcp一个服务端只能与一个客户端的socket通信一次,因为我们不知道哪个客户端新建的socket有数据到来或者监听socket有新连接,有可能就会对没有新连接到来的监听socket进行accept操作而阻塞或者对没有数据到来的普通socket进行recv阻塞;
2、在tcp服务端中,将所有的socket设置为非阻塞,若没有数据到来,则立即报错返回,进行下一个描述符的操作,这个操作中,有一个不好的地方,就是对没有就绪事件的描述符进行操作,降低了处理效率;

如何实现多路转接IO:操作系统提供了三种模型:select模型、poll模型、epoll模型;

select模型

使用流程以及接口介绍和原理理解

  • 1、用户定义想要监控的事件的描述集合(就绪事件有三种,但是并不是每一个描述符都要监控所有事件),例如,定义可读事件的描述符集合,将哪些描述符添加到这个集合中,就表示要对这个描述符监控可读事件,因此是想要监控什么事件就定义什么集合;
  • (1)定义指定事件的集合 / (2)初始化集合 / (3)将需要监控这个事件的描述符添加到这个集合中;
    集合:fd_set 结构体,结构体中只有一个成员,就是一个数组- - -作为位图进行使用,向集合中添加一个描述符,描述符就是一个数字,添加描述符其实就是这个数字对应的比特位置为1,表示这个描述符被添加到集合中;
    这个数组中有多少个比特位或者说select最多能监控多少描述符,取决于宏 _FD_SETSIZE,默认为1024;
    fd_set rfds;
    void FD_ZERO(fd_set *set); - - -清空指定的描述符集合;
    void FD_SET(int fd, fd_set *set); - - -将fd描述符添加到set集合中;
  • 2、发起调用,将集合中数据拷贝到内核中进行监控,监控采用轮询遍历判断方式进行;
    int select( int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    nfds:所有集合中最大的那个描述符的数值 + 1,为了减少内核中遍历次数;
    readfds:可读事件集合;
    writefds:可写事件集合;
    excptfds:异常事件集合- - -对不同集合中的描述符监控不同是事件;
    timeout:select默认是一个阻塞操作(struct timeval{tv_sec; tv_usec}),若timeout = NULL表示永久阻塞直到有描述符就绪再返回;若timeout中的数据为0,则表示非阻塞,没有就绪则立即返回;若timeout 有数据,若指定事件内没有描述符就绪则超时返回;
    返回值:返回值小于0,表示监控出错;返回值等于0,表示没有描述符就绪;返回值大于0,表示就绪是描述符个数;
    select会在返回之前,会将所有集合中没有就绪的描述符都给从集合中移除出去(调用返回后,集合中保存的就是就绪的描述符),返回给程序员一个就绪的描述符集合;
  • 3、select调用返回后,进程遍历哪些描述符还在哪些集合中,确定哪个描述符就绪哪个事件,进而进行相应操作。
  • 其他操作:
    void FD_CLR(int fd, fd_set *set)- - -从set集合中移除fd描述符;
    int FD_ISSET(int fd, fd_set *set) - - -判断fd描述符是否在set集合中;
    封装select,封装一个select类,每一个实例化的对象都是一个监控对象,向外提供简单的接口,可以监控大量的描述符,并且直接能够在外部获取到就绪的描述符(不需要在外部进行复杂的select操作);
class Select{
public:
	Select(){};  //初始化操作
	bool Add(TcpSocket &sock); //将sock中的描述符添加到保存集合中
	bool Del(TcpSocket &sock);  //从保存集合中移除这个描述符,不再监控这个描述符
	bool Wait(std::vector<TcpSocket> *list, int outtime);  //进行监控,并且直接向外提供就绪的TcpSocket
private:
	fd_set_rfds; //可读事件集合---保存要监控可读事件的描述符,每次监控使用的集合都是这个集合的复制版(selec会修改集合)
	int _maxfd;
};

select总结,优缺点分析
优点
1、select遵循posix标准,可以跨平台移植;
2、select的超时等待时间设置,可以精细到微秒;

缺点
1、select所能够监控的描述符数量有最大上限,取决于宏 _FD_SETSIZE,默认是1024;
2、select进行监控的原理,是内核中进行轮询遍历判断,性能会随着描述符的增多而下降;
3、select返回时移除集合中未就绪的描述符,每次监控都要重新添加描述符,重新拷贝到内核;
4、select只能返回就绪的描述符集合,无法直接返回就绪的描述符,需要用户进行遍历判断哪个描述符还在集合中才能确定是否就绪;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章