浅谈5种IO模型 | 您所在的位置:网站首页 › 阻塞发送和非阻塞发送什么意思啊 › 浅谈5种IO模型 |
5种IO模型
5种IO模型1、背景2、IO模型2.1、阻塞式IO(blocking IO)2.2、非阻塞式IO(nonblocking IO)2.3、多路复用IO(IO multiplexing)2.4、信号驱动IO(signal driven IO)2.5、异步IO( asynchronous IO)
参考
5种IO模型
1、背景
从TCP发送数据的流程说起 要深入的理解各种IO模型,那么必须先了解下产生各种IO的原因是什么,要知道这其中的本质问题那么我们就必须要知一条消息是如何从过一个人发送到另外一个人的; 以两个应用程序通讯为例,我们来了解一下当“A”向"B" 发送一条消息,简单来说会经过如下流程: 第一步:应用A把消息发送到 TCP发送缓冲区。第二步: TCP发送缓冲区再把消息发送出去,经过网络传递后,消息会发送到B服务器得TCP接收缓冲区。第三步:B再从TCP接收缓冲区去读取属于自己的数据。![]() 如果理解了上面的消息发送流程,那么我们下面开始进入文章的主题; 2、IO模型我们把视角切换到上面图中的第三步, 也就是应用B从TCP缓冲区中读取数据。 思考一个问题: 因为应用之间发送消息是间断性的,也就是说在上图中TCP缓冲区还没有接收到属于应用B该读取的消息时,那么此时应用B向TCP缓冲区发起读取申请,TCP接收缓冲区是应该马上告诉应用B 现在没有你的数据,还是说让应用B在这里等着,直到有数据再把数据交给应用B。 把这个问题应用到第一个步骤也是一样,应用A在向TCP发送缓冲区发送数据时,如果TCP发送缓冲区已经满了,那么是告诉应用A现在没空间了,还是让应用A等待着,等TCP发送缓冲区有空间了再把应用A的数据访拷贝到发送缓冲区。 如果上面的问题你已经思考过了,那么其实你已经明白了什么是阻塞IO了,所谓阻塞IO就是当应用B发起读取数据申请时,在内核数据没有准备好之前,应用B会一直处于等待数据状态,直到内核把数据准备好了交给应用B才结束。 定义:在应用调用recvfrom读取数据时,其系统调用知道数据包到达切被复制到应用缓冲区中或者发送错误时才返回,在此期间一直会等待,进程从调用到返回这段时间内都是被阻塞的成为阻塞IO; 流程: 1、应用进程向内核发起recfrom读取数据。2、准备数据报(应用进程阻塞)。3、将数据从内核负责到应用空间。4、复制完成后,返回成功提示。按照上面的思路,所谓非阻塞IO就是当应用B发起读取数据申请时,如果内核数据没有准备好会即刻告诉应用B,不会让B在这里等待。 定义 非阻塞IO是在应用调用recvfrom读取数据时,如果该缓冲区没有数据的话,就会直接返回一个EWOULDBLOCK错误,不会让应用一直等待中。 在没有数据的时候会即刻返回错误标识,那也意味着如果应用要读取数据就需要不断的调用recvfrom请求,直到读取到它数据要的数据为止。 流程: 1、应用进程向内核发起recvfrom读取数据。2、没有数据报准备好,即刻返回EWOULDBLOCK错误码。3、应用进程向内核发起recvfrom读取数据。4、已有数据包准备好就进行一下 步骤,否则还是返回错误码。5、将数据从内核拷贝到用户空间。6、完成后,返回成功提示。![]() 思考一个问题: 我们还是把视角放到应用B从TCP缓冲区中读取数据这个环节来。如果在并发的环境下,可能会N个人向应用B发送消息,这种情况下我们的应用就必须创建多个线程去读取数据,每个线程都会自己调用recvfrom 去读取数据。那么此时情况可能如下图: 那么问题来了,这么多的线程不断调用recvfrom 请求数据,先不说服务器能不能扛得住这么多线程,就算扛得住那么很明显这种方式是不是太浪费资源了,线程是我们操作系统的宝贵资源,大量的线程用来去读取数据了,那么就意味着能做其它事情的线程就会少。 所以,有人就提出了一个思路,能不能提供一种方式,可以由一个线程监控多个网络请求(我们后面将称为fd文件描述符,linux系统把所有网络请求以一个fd来标识),这样就可以只需要一个或几个线程就可以完成数据状态询问的操作,当有数据准备就绪之后再分配对应的线程去读取数据,这么做就可以节省出大量的线程资源出来,这个就是IO复用模型的思路。 定义 进程通过将一个或多个fd传递给select(或者其他IO复用API),阻塞在select操作上,select帮我们侦测多个fd是否准备就绪,当有fd准备就绪时,select返回数据可读状态,应用程序再调用recvfrom读取数据。 复用IO模型解决了一个线程可以监控多个fd的问题,但是select是采用轮询的方式来监控多个fd的,通过不断的轮询fd的可读状态来知道是否就可读的数据,而无脑的轮询就显得有点暴力,因为大部分情况下的轮询都是无效的,所以有人就想,能不能不要我总是去问你是否数据准备就绪,能不能我发出请求后等你数据准备好了就通知我,所以就衍生了信号驱动IO模型。 于是信号驱动IO不是用循环请求询问的方式去监控数据就绪状态,具体如下: 1、调用sigaction时候建立一个SIGIO的信号联系,2、当内核数据准备好之后再通过SIGIO信号通知线程数据准备好后的可读状态,3、当线程收到可读状态的信号后,此时再向内核发起recvfrom读取数据的请求,因为信号驱动IO的模型下应用线程在发出信号监控后即可返回,不会阻塞4、所以这样的方式下,一个应用线程也可以同时监控多个fd。![]() ![]() 通过观察我们发现,不管是IO复用还是信号驱动,我们要读取一个数据总是要发起两阶段的请求,第一次发送select请求,询问数据状态是否准备好,第二次发送recevform请求读取数据。(这也就是为什么上面四种都是同步IO) 在IO模型里面如果请求方从发起请求到数据最后完成的这一段过程中都需要自己参与,那么这种我们称为同步;如果应用发送完指令后就不再参与过程了,只需要等待最终完成结果的通知,那么这就属于异步。思考一个问题: 也许你一开始就有一个疑问,为什么我们明明是想读取数据,什么非得要先发起一个select询问数据状态的请求,然后再发起真正的读取数据请求,能不能有一种一劳永逸的方式,我只要发送一个请求我告诉内核我要读取数据,然后我就什么都不管了,然后内核去帮我去完成剩下的所有事情? 有人设计了一种方案,应用只需要向内核发送一个read 请求,告诉内核它要读取数据后即刻返回;内核收到请求后会建立一个信号联系,当数据准备就绪,内核会主动把数据从内核复制到用户空间,等所有操作都完成之后,内核会发起一个通知告诉应用,我们称这种一劳永逸的模式为异步IO模型。 1、https://zhuanlan.zhihu.com/p/115912936 |
CopyRight 2018-2019 实验室设备网 版权所有 |