linux高级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就绪信号处理方式,在处理方式中进行IO请求,进程可以一直干其他事情,等到收到IO就绪信号的时候,就会打断进程当前的操作去处理IO

  优先:相较于非阻塞IO,更加实时,资源利用更加充分

  缺点:流程更加复杂了,需要定义信号处理,既有主控流程又有信号处理流程,涉及到信号是否可靠的问题!

异步IO:IO顺序不确定,IO过程(等待+数据拷贝)由系统完成,不能自己进行

定义IO完成信号处理方式自定义,发起异步IO调用,告诉系统要完成什么功能,剩下的IO功能完全由系统完成,完成后通过信号通知进程

  优点:对资源利用最为充分,以最高效率进行任务处理

  缺点:资源消耗比较高,流程最为复杂!

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

概念理解:

阻塞:为了完成一个功能,发起系统调用,若不具备完成条件,则调用一直等待!

非阻塞:。。。。。。。。。。。。。。。。。。。。。。。。则报错返回

阻塞与非阻塞的区别:常用讨论调用函数是否阻塞,表示这个函数无法立即完成调用功能时是否立即返回!

同步:功能完成的流程还是顺序化的,并且功能是进程自身完成的。

异步:功能完成的流程不是顺序化的,是不确定的,并且功能不是由进程自身完成的,而是由系统完成。

异步的种类:异步阻塞----等着别人完成功能/异步非阻塞-----不等待别人完成功能

同步与异步的区别:通常用于讨论功能的完成方式,表示一个功能是否是顺序的且有自己来完成!

同步好还是异步好:同步IO流程简单,同一时间资源占用少。但是异步IO效率高,同一时间占用的资源多,处理效率高!

多路转接IO:主要用于进行大量的IO就绪事件监控,能够让我们的进程只针对就绪的IO进行IO操作

就绪事件:IO事件的就绪

  可读事件:一个描述符当前是否有可读数据

  可写事件:一个描述符当前是否可以写入数据

  异常事件:一个描述符是否发生了某些异常

只针对就绪的描述符进行IO操作有什么好处?---------避免阻塞,并且提高了效率。

答:在默认的socket中,例如一个tcp的一个服务端只能与一个客户端的socket通信一次,因为们不知道哪个为客户端新建的socket有数据到来或者监听socket有新连接,有可能就会对没有新连接到来的监听socket进行accept操作而阻塞或者对没有数据到来的普通socket进行recv阻塞。

  在tcp服务器中,将所有的socket设置为非阻塞,若没有数据到来,则立即报错返回,进行下一个描述符的操作,这种操作中,有一个不好的地方就是对没有就绪事件的描述符,降低了处理效率!

如何实现多路转接IO:select模型,poll模型,epoll模型!

接下来介绍select模型:

  slect模型使用流程以及接口介绍以及原理理解:

1.用户定义想要监控的事件的描述符集合(就绪事件有三种,但是并不是每一个描述符都要监控所有事件),例如,定义可读事件的描述符集合,将哪些描述符添加到这个集合中,就表示要对这个描述符监控可读事件,因此是想要监控什么事件就定义什么集合!

1.定义指定事件的集合/2.初始化集合/3.将需要监控这个事件的描述符添加到这个集合中去!

集合:fd_set结构体,结构体只有一个成员,就是一个数组-----作为位图使用。

向集合中添加一个描述符,描述符就是一个数字,添加描述符其实就是这个数字对应的比特位置为1。表示这个描述符被添加到了集合当中,这个数组中有多少个比特位或者说select最多能监视多少描述符,取决于宏  _FD_SETSIZE,默认是1024.

定义fid_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:可写事件集合;exceptfds异常事件集合----对不同集合中的描述符监控不同的事件

timeout:select默认是一个阻塞操作(struct timeval{tv_sec;tv_usec}),若timeout=NULL表示永久阻塞直至有描述符就绪再返回;

     若timeout中的数据为0,表示监控出错;返回值等于0,表示没有描述符就绪;返回值大于0,表示就绪的描述符个数!

  select会在返回之前干一件事,将所有集合中没有就绪的描述符都给从集合中移除出去(调用返回后,集合中保存的都是就绪的描述符)

3.程序员遍历判断那个描述符还在哪个集合中,确定哪个描述符就虚了哪些事件,进而进行相应的操作!

select流程总结:

  定义事件的集合int FD_set ->初始化集合清空集合FD_ZERO(fd_set *set);->将需要监控的描述符添加到集合当中FD_SET(fd_set *set)->发起调用int select(...),将监控的描述符集合拷贝到内核当中进行轮询遍历监控,当有描述符就绪或者等待的时候,监控调用返回(进行调用返回的时候,会清除掉集合当中那些未就绪的描述符,向程序返回就绪了的描述符集合)->程序在监控调用返回之后int FD_ISSET,遍历所有描述符是否还在集合当中,通过这个就可以判断程序那个描述符就绪了什么事件!

如果要解除哪个描述符的监控,就将描述符从集合中解除即可!void FD_CLR

优缺点分析:

1.select能监控的描述符最大数量,取决于一个宏FD_SETSIZE,默认是1024.

2.select监控原理是在内核中进行轮询遍历判断,性能会随着集合长度的增加而下降!

3.select在调用返回时,从监控集合中移除未就绪的描述符,因此每次监控都要重新向集合中添加描述符,并且拷贝到内核当中!

4.select并不会直接返回就绪的描述符进行操作,而是返回就绪集合,需要程序员自己遍历判断才能确定哪个描述符就绪。

5.select遵循POSIX标准,跨平台移植性比较好!

poll介绍:

int poll(struct pollfd *fds,nfds_t nfds,int timeout);

fds:要监控的描述符事件结构体,nfds:实际第一个参数描述符事件结构体数量;   timeout:超时等待时间-毫秒

struct pollfd{int fd:要监控的描述符;    short events:描述符想要监控的事件;  short revents:实际就绪的事件}

events:POLLIN-可读/POLLOUT-可写

revents:当poll接口返回调用的时候,这个描述符实际就绪的事件就会被写入到revents中,程序员就是通过这个成员进行判断的!

返回值:返回值大于0表示就绪的描述符个数,返回值等于0表示监控超时;返回值小于0表示监控出错

1.定义描述符事件结构体数组,将需要监控的描述符以及对应的事件信息填充入数组中

2.发起监控 调用poll,将数组中数据拷贝到内核中进行轮询遍历监控,有描述符就绪或者等待超时后返回,返回时将这个描述符实际就绪的事件填充到对应结构体的revents中

3.监控调用返回后,程序员在程序中遍历数组中没一个节点的revents,确定当前节点的描述符就绪了什么事件,进而进行相应的操作!

优缺点分析:

1.poll通过描述符事件结构体的方式简化了select的三种描述符集合的操作流程。

2.poll所能监控的描述符,没有了最大数量限制(要监控多少描述符定义多大数组)

3.poll每次监控不需要重新定义事件结构体

4.无法跨平台移植

5.每次监控调用返回也需要程序员在程序中进行遍历判断才知道哪个描述符就绪了那些事件

6.依然每次监控都需要将数组拷贝到内核中进行监控

7.监控的超时等待时间最精细到毫秒!

epoll linux下最好用的多路转接模型

接口介绍:

1.在内核中创建eventpoll结构体,返回一个描述符作为代码中的操作句柄!

int epoll_create(int size)

size是监控描述符的最大数量。但是在linux2.6.8版本之后,不做要求,只要大于零即可!

2.对需要监控的描述符组织事件结构体,将描述符以及对应事件结构添加到内核的eventpoll结构体中

int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)

epfd:epoll操作句柄 ;

op:EPOLL_CTL_ADD/EPOLL_CTL_DEL;

fd:要监控的描述符;

event:监控描述符对应的事件结构体信息!

struct epoll _event{

    unit32_t events;//表示要监控的事件,以及监控调用返回后实际 就绪的事件--------EPOLLIN/EPOLLOUT

    union epoll_data(int fd-描述符,void *ptr;)

}

每一个需要监控的描述符都会有一个对应的事件结构,当描述符就绪了监控的事件后,就会将这个事件结构体返回给程序员

3.开始监控,当有描述符就绪或者等待超时后监控调用返回

int epoll_wait(int epfd,sruct epoll_event *evs,int maxevents,int timeout);

epfd:epoll的操作句柄,通过这个句柄找到内核中指定的eventpoll结构体;

evs:epoll event描述符的事件结构体数组的首地址,用于获取就绪的描述符对应的事件结构体!

maxevents:evs数组的节点数量,主要是为了防止就绪太多,向evs中放置的时候出现越界!

timeout:超时等待时间--单位:毫秒!

返回值:返回值大于表示就绪的描述符个数;返回值等于0表示等待超时;返回值小于0表示监控出错!

红黑树特点:黑色节点可以连续,红色节点不能连续,保证了树的平衡性。根节点到叶子节点的每条路径 上黑色节点数量是相同的!查找的时间复杂度是logN。

红黑树的使用场景:查找效率高logN,数据时有序的(适合于少修改插入删除,但是查找比较多的场景)

1.eventpoll结构体在内核中创建,包含:rbr红黑树+rbllist双向链表

2.组织描述符事件结构,添加描述符监控

    struct epoll_event ev;

    events=EPOLLIN-可读

    data.fd=0;

    struct epoll_event ev;

    events=EPOLLIN-可读

    data.fd=3

调用epoll——wait开始监控

1.这个epoll_wait接口是一个异步阻塞操作----告诉操作系统哪些描述符需要监控,然后描述符的就绪监控过程由系统完成

2.操作系统为红黑树中每一个需要监控的描述符设置了事件回调,一旦描述符就绪了监控的事件,则自动通过回调函数,将就绪事件的描述符对应的epoll——event事件结构体添加到rdllist双向链表中!

我们的进程发起epoll_wait调用之后,并 没有立即返回,而是每隔一会儿去看双向链表是否为空,确定是否有描述符就绪

若rdllist不为空,表示有就绪,则将就绪描述符的结构信息,添加到epoll_wait传入evs数组中!

在epoll_wait接口返回值大于0的情况下,表示有就绪,而epoll_wait传输 的evs数组中前返回值个节点都是就绪描述符对应的事件结构体信息!

只需要对evs数组进行遍历,逐个判断events是神魔事件然后对fd描述符进行操作即可!

异步操作:完成流程不固定,实际功能并不由自身完成

epoll_wait异步阻塞操作:只是发起监控请求,实际监控过程由系统完成------异步操作

    而操作系统进行监控就是,当描述符就绪了指定事件,则将描述符对应事件信息放到双向链表中!

epoll_wait调发起后,调用并没有立即返回,而是等待我门 的双向链表不为空,或者超时进行返回------阻塞操作!

epoll不会随着描述符数据增多而性能下降!:链表内的都是已经就绪的,不会进行额外的遍历开销!

监控过程不是进程自己完成的,而是系统完成的。故进程也不知道什么时候有就绪!总得有一个反馈方式!

这里的反馈方式就是放到双向链表中!

使用c++封装一个Epoll类,每一个实例化的对象都是一个监控对象!

class Epoll{

public:

    Epoll();

    bool Add(TcpSocket &sock);

    bool Del(TcpSocket &sock);

    bool  Wait(std::vector<TcpSocket>*list);

private:

    int _epfd;

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值