IO多路复用

Posted 狗蛋儿l

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IO多路复用相关的知识,希望对你有一定的参考价值。

1.I/O多路复用之select模型

1.1 select模型服务端的流程

在这里插入图片描述

1.2 bitmap位图

在这里插入图片描述
_FD_SETSIZE可以重新定义它的值的大小,但是实际应用开发不推荐这样使用,因为如果有更多socket的时候可以选择poll模型或者epoll模型。

1.3 select水平触发

select采用水平触发的方式,如果报告fd后事件没有被处理或者数据没有被完全读取,那么下次select时会再次报告该id,也就是说select不会丢失事件和数据。

1.4 select的缺点

select支持的文件描述符数量太小,默认是1024。虽然可以调整,但是描述符数量越大,效率就会更低,因此调整的意义不大。
每次调用select,都需要把fdset从用户态拷贝到内核态。
同时在线的大量客户端有事件发生的可能性太小,但是还是需要遍历fdset。因此随着监视的描述符数量增长,其效率也会线性降低。

2.I/O多路复用之poll模型

2.1 poll模型与select模型比较

poll和select在本质上没有差别,管理多个描述符也是进行轮询。根据描述符的状态进行处理,但是poll模型没有最大文件描述符数量的限制。
select的fdset采用bitmap,poll采用数组。
poll和select同样存在一个缺点,文件描述符的数组被整体复制与用户态和内核态的地址空间之间,而不论这些文件描述符是否有事件,它的开销随着文件描述符数量的增长而线性增大。还有poll返回后,也需要遍历整个描述符的数组才能得到有事件的描述符。

2.2 poll函数和参数

函数原型:*int poll(struct pollfd fds, int timeout);。
pollfd的结构体:

int fd;    //file descriptor;
short events;  //requested events;
short revents; //returned events;

3.I/O多路复用之epoll模型

3.1 epoll模型相对poll模型的改进

epoll模型解决了select和poll所有问题,包括fdset轮询和拷贝,采用更加合理的设计和实现方案。

3.2 水平触发

水平触发:如果报告fd后事件没有被处理或者数据没有被全部读取,那么epoll会立即再报告该fd。
优点:当进行socket通信的时候,保证数据的完整输出,进行IO操作的时候,如果还有数据,就会一直的通知。
缺点:由于只要还有数据,内核就会不停的在内核空间和用户空间切换,所以占用大量内核资源。试想一下当有大量数据到来的时候,每次读取一个字节,这样就会不停的进行切换,内核资源的浪费严重,效率来讲也是很低的。
应用场景:数据比较小。

3.3 边缘触发

边缘触发:如果报告fd后事件没有被处理或者数据没有被全部读取,那么epoll会下次再报告该fd。
优点:每次内核只会通知一次,大大减少内核资源的浪费,提高效率。
缺点:不能保证数据的完整。不能及时的取出所有的数据。
应用场景:数据比较大,包括nginx等等。

3.4 epoll函数和参数

创建epoll的句柄,本身就是一个fd:int epoll_create(int size);。
注册需要监视fd和事件:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);。
等待事件发生:int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout);。

4.IO五种模型

4.1 阻塞IO

最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除block状态。
典型的阻塞IO模型的例子为:data = socket.read();。

4.2 非阻塞IO

当用户线程发起一个read操作后,并不需要等待,而是马上就得到一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。
事实上在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。
典型的非阻塞IO模型一般如下:

while(true){
   data = socket.read();
   if(data!= error){
       /*处理数据*/
       break;
   } 
}

4.3 IO多路复用

详情参考IO多路复用。

4.4 信号驱动IO

在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册信号函数,然后用户线程会继续执行,当内核数据就绪时会发送信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。这个一般用于UDP中,对TCP套接口几乎是没用的,原因是该信号产生得过于频繁,并且该信号的出现并没有告诉我们发生什么事情。

4.5 异步IO

异步IO模型才是最理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它收到到一个asynchronous read之后会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要关心实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。
也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用iO函数进行实际的读写操作。

以上是关于IO多路复用的主要内容,如果未能解决你的问题,请参考以下文章

你管这破玩意叫 IO 多路复用?

多路转接(IO复用)接口介绍

多路复用io接口-epoll

IO多路复用 -- selectpollepoll实现TCP反射程序

IO多路复用

经典5种IO模型 | IO多路复用