IO 模型
转载自:知乎:勤劳的小手 zhuanlan.zhihu.com/p/115912936
阻塞IO
描述:应用A调用recvfrom读取数据时,在数据被复制到应用缓冲区或发送错误时才返回,在此期间一直等待,进程从调用到返回这段时间内都被阻塞。
流程
- 应用进程向内核发起recfrom读取数据
- 准备数据报(应用进程阻塞)
- 将数据从内核复制到应用空间
- 复制完成后,返回成功提示。

优点:开发简单,等待数据过程中,用户线程挂起,不占用CPU资源。
缺点:阻塞全截断,不灵活,不支持高并发。
非阻塞IO
描述:在应用调用recvfrom读取数据时,如果没有数据,就会直接返回一个ewouldblock错误,不会阻塞在等待数据的第二阶段。同时,意味着应用要读取数据就需要不断的调用recvfrom请求,直到读取到数据为止。
流程
- 应用进程向内核发起recvfrom读取数据
- 没有数据包准备好,即刻返回EWOULDBLOCK错误码
- 应用进程向内核发起recvfrom读取数据...
- 若已有数据包,则执行5
- 将内核中的数据拷贝到用户空间
- 完成后,返回成功提示。

优:每次发起recvfrom调用,立即返回不会阻塞,实时性比较好。 缺:不断轮询内核,占用大量的CPU资源,效率低下。
IO复用模型
存在问题:并发环境下,若发送给B多个消息,就需要创建多个线程去调用 recvfrom读取数据。若为同步操作,若连接请求上百万,那么久需要上百万的线程去不断向内核发送recvfrom请求,读取数据,这样太浪费资源了。
衍生解决:linux系统,引入select/poll系统,用一个线程(函数)监控多个网络请求,linux称为fd文件扫描符(fd:标识一个网络请求),只需要一个或几个线程完成数据状态的询问操作,当数据准备就绪后再分配对应带的线程去读取数据。常:select/poll帮我看看,有数据的话通知我,我拿



select/poll/epoll对比
- select/poll默认单线程最多打开1024个fd。而epoll能配置
- select/poll采用不断去轮询就绪的fd,一旦fd太多,轮询效率高,导致IO效率降低,而epoll采用热点探测:实现一个伪AIO,让就绪的fd回调,只会对就绪的socket进行操作。
- epoll使用mmap来避免不必要的内存复制
- epoll的API更简单易用
信号驱动IO模型
问题:虽然复用IO模型解决了线程问题,但select采用轮询的方式监控fd,有点暴力,因为大多数轮询无效。为什么不能让我通知你数据好了呢?这就衍生了信号驱动IO模型。
不用轮询监控,而是在调用sigaction时候建立sigio的信号联系,当内核数据准备好之后再通经过SIGIO信号通知线程数据准备好的状态。线程收到信号后再向内核发起recvfrom读取请求。因为信号驱动IO模型下应用多线程在发出信号监控后即可返回,不会阻塞。

异步IO
问题:之前无论IO复用或信号驱动IO,都需要先select询问状态,再 recvfrom获取数据。能不能直接通知内核我要数据,剩下的交给你? 解决:应用告知内核启动某个操作,让内核在整个操作完成后,通知应用,这种模型与信号驱动模型的主要区别是,信号驱动IO是内核告知可开始下一个IO操作,而异步IO模型是由内核告知操作完成。


总结:异步IO的思路是解决发送询问和,发送接收数据请求的模式,现在只用发送一次请求就完成状态询问和拷贝数据操作。
优点:全阶段异步,用户进程只需要接收内核的操作完成事件或注册一个IO回调函数。
缺点:依赖OS底层实现,而目前linux下的AIO模型不完善,因此用到最多的还是多路复用IO模型
再谈IO模型里的同步和异步
阻塞:读取数据时,数据还没准备就绪的时候,如果是需要等待,则为阻塞。
非阻塞:上述情况,直接返回请求。
同步:发送请求到数据最后复制完成都需要参与的称为同步请求。
异步:发送完请求,不参与过程,只等最后通知,称为异步。
同步阻塞 同步非阻塞 异步非阻塞 为什么没有异步阻塞?因为异步模型下,发送请求后就即可返回了,所以不会阻塞。
谈谈你认识的NIO
- 上诉的NIO模型非阻塞IO
- BEW IO包:对多路复用模型再次封装后提供的代码层的API
组件:selector、channel、buffer