nginx网络服务模型
网络IO的本质是socket的读取,socket在linux中被抽象为流,IO操作可以理解为对流的操作。为了操作系统的安全性等考虑,进程是无法直接操作I/O设备的,其必须通过系统调用请求内核来协助完成I/O动作,而内核会为每个I/O设备维护一个buffer。整个请求过程可以概括为:用户进程发起请求,内核接受到请求后,从I/O设备中获取数据到buffer中,再将buffer中的数据copy到用户进程的地址空间,该用户进程获取到数据后再响应客户端。如下图所示:
在整个请求过程中,数据从IO设备输入至kernel buffer需要时间,而从kernel buffer复制到用户进程也需要时间(从IO设备到kernel比从kernel到process需要花更多的时间)。因此根据在这两段时间内等待方式的不同,I/O动作可以分为以下五种模式:
阻塞I/O (Blocking I/O)
非阻塞I/O (Non-Blocking I/O)
I/O复用(I/O Multiplexing)
信号驱动的I/O (Signal Driven I/O)
异步I/O (Asynchrnous I/O)
同步和异步的概念
同步:就是在发出一个调用时,在没有得到结果之前,这个调用不返回结果,但是一但调用返回,就能得到返回值,也就是在调用者得到返回值前会一直处于等待状态,直到获得返回值。
异步:异步则与同步相反,调用发出之后,这个调用就直接返回了,所以没有返回结果,换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
如何理解同步与异步:
例子:
小五打电话问书店老板有没有《nginx优化》这本书,如果是同步机制,书店老板会说,你稍等,“我查一下”,然后开始查找,一直会等到查到为止(时间可能是几秒也可能是1天),然后在告诉你结果。
如果是异步机制,书店老板会直接告诉你我查找一下,查到了我打电话告诉你,然后通话结束,等到查找好了,主动打电话告诉你,也就是通过回调的方式告诉你结果。
阻塞I/O
当用户进程调用了recv()/recvfrom()这个系统调用,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。第二个阶段:当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,同步阻塞(blocking IO)的特点就是在IO执行的两个阶段都被block了。
同步非阻塞IO模型
非阻塞IO也会进行recvform系统调用,检查数据是否准备好。非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
所以,同步阻塞(nonblocking IO)的特点就是在IO执行的第1个阶段没有被block,第2个阶段被block了。
异步IO模型
首先用户进程告诉内核态需要什么数据(上图中通过aio_read),然后用户态进程就不管了,做别的事情,内核等待用户态需要的数据准备好,然后将数据复制到用户空间,此时才告诉用户态进程,”数据都已经准备好,请查收“,然后用户态进程直接处理用户空间的数据。
IO多路复用模型
I/O多路复用和阻塞I/O类似,不同的是这里使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。I/O多路复用用户进程阻塞的不是recvfrom,而是select/epoll。但是,用select的优势在于它可以同时处理多个connection。
所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
信号驱动
当需要等待数据的时候,首先用户态会向内核发送一个信号,告诉内核我要什么数据,然后用户态就不管了,做别的事情去了,而当内核态中的数据准备好之后,内核立马发给用户态一个信号,说”数据准备好了,快来查收“,用户态进程收到之后,立马调用recvfrom,等待数据从内核空间复制到用户空间,待完成之后recvfrom返回成功指示,用户态进程才处理别的事情。