企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[toc] ## 网络IO的模型分类 此IO模型出至Richard Stevens的<<UNIX® Network Programming Volume 1>> Stevens在文章中一共比较了五种IO Model: * blocking IO 阻塞IO * nonblocking IO 非阻塞IO * IO multiplexing IO多路复用 * signal driven IO 型号驱动IO * asynchronous IO 异步IO >由signal driven IO(信号驱动IO)在实际中并不常用,所以主要介绍其余四种IO Model。 对于一个network IO (以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,该操作会经历两个阶段: ~~~ 1)等待数据准备 (Waiting for the data to be ready) 2)将数据从内核拷贝到进程中(Copying the data from the kernel to the process) ~~~ 这两点很重要,因为这些IO模型的区别就是在两个阶段上各有不同的情况。 ## **阻塞IO(blocking IO)** 在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样: ![](https://images2017.cnblogs.com/blog/827651/201801/827651-20180121220439365-1845551743.png) * 准备数据阶段 当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达,这个时候kernel就要等待足够的数据到来。 而在用户进程这边,整个进程会被阻塞。 * 拷贝数据阶段 当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。 * 总结 **blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。** * 多线程能否解决 **对应所面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”或“连接池”或许可以缓解部分压力,但是不能解决所有问题。总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈** 可以用非阻塞接口来尝试解决这个问题。 ## **非阻塞IO(non-blocking IO)** Linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子: ![](http://book.luffycity.com/python-book/assets/chapter7/%E9%9D%9E%E9%98%BB%E5%A1%9EIO.png) * 数据准备阶段 当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。 从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情,或者直接再次发送read操作。 * 拷贝数据阶段 一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存(这一阶段仍然是阻塞的),然后返回。 ~~~ 也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞, 内核马上返回给进程,如果数据还没准备好,此时会返回一个error。 进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。 重复上面的过程,循环往复的进行recvform系统调用,这个过程通常被称之为轮询。 轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。 需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。 ~~~ * 总结 **在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。** **但是非阻塞IO模型绝不被推荐。** * 优点 能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。 * 缺点: 循环调用recv()将大幅度推高CPU占用率; 任务完成的响应延迟增大了,这会导致整体数据吞吐量的降低。 因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。 ## **多路复用IO(IO multiplexing)** IO multiplexing也可以说是select/epoll,也称这种IO方式为**事件驱动IO**(event driven IO)。 select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图: ![](https://images2017.cnblogs.com/blog/827651/201801/827651-20180121220750724-1438537565.png) * 数据准备阶段: 当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket * 拷贝数据阶段 当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。 * 结论 **结论: select的优势在于可以处理多个连接,不适用于单个连接** 用select的优势在于它可以同时处理多个connection。 **可以使用selectors模块,帮我们默认选择当前平台下最合适的IO多路复用模型** * **该模型的优点:** 相比其他模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。 如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。* * **该模型的缺点:** 首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。 * **几种多路复用IO的实现** select 轮询方式,windows只支持这种方式 ,linux也支持 poll 轮询方式,linux支持,poll能够监听的对象比select要多 **epoll 回调函数的方式,只有linux ,是一种很高效的方式** ## 异步IO(Asynchronous I/O) Linux下的asynchronous IO其实用得不多,从内核2.6版本才开始引入,。先看一下它的流程: ![](https://images2017.cnblogs.com/blog/827651/201801/827651-20180121220949521-1279971385.png) * 数据准备阶段 用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。 * 数据准备阶段 然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。 * 优缺点 异步IO应该是最好的IO模型,因为它在两个阶段都没有阻塞,但是python没有办法直接实现异步IO,但是可以使用开源的如`Tornado框架`等