Linux中的网络I/O模型
网络的I/O一直是制约程序运行的关键,无论是C、C++还是Java都有自己I/O模型的实现,所有高级语言中的I/O模型,都脱离不了底层操作系统对于I/O的支持,在Windows中或者Linux中都有各自对于I/O的实现,在《Netty权威指南》中也有对Linux的I/O模型的简述,本文主要参考此书对Linux中I/O模型作简要分析描述。
一、同步、异步和堵塞、非堵塞
想要理解同步、异步、堵塞和非堵塞的区别首先要理解网络I/O的过程,一般针对网络I/O的操作,可以分成两个阶段,准备阶段和操作阶段。
- 准备阶段是判断是否能够操作(即等待数据准备就绪),在内核进程完成的。
- 操作阶段则执行实际的I/O调用,数据从内核缓冲区拷贝到用户进程缓冲区。
1.同步和异步
同步和异步描述的是用户进程和内核进程的交互方式,它描述的是I/O操作两个阶段的整体行为。
- 同步是指用户发起I/O请求以后堵塞当前进程,等待或者轮询内核进程的I/O完成后才执行后续操作
- 异步是指用户发起I/O请求以后可以继续执行后续操作,当内核进程完成I/O操作,并将数据拷贝到用户缓冲区以后通过回调函数通知用户进程。
2. 堵塞和非堵塞
堵塞和非堵塞描述的是用户进程调用内核 I/O 操作的方式,它描述的是网络I/O操作的第一阶段的行为。
- 阻塞是指 I/O操作的准备阶段需要一直等待数据准备就绪,当内核没有将数据准备好的时候,需要堵塞住当前进程(用户进程挂起)。
- 非堵塞是指I/O操作的准备阶段发起系统调用以后可以执行后续流程,不需要堵塞住等待数据完成(用户进程轮询或注册回调函数)。
二、堵塞型I/O模型
这是最为常用的I/O模型,在Java的1.4版本之前只支持该类型的I/O,当用户进程发起I/O操作时,如果DMA未将数据复制到内核空间(数据未准备好),那么用户进程将会挂起堵塞住,无法继续执行,直到数据准备完成,用户进程将数据从内核空间复制到用户空间,致词I/O操作最终完成。
三、非堵塞型I/O模型
此I/O模型在数据未准备好之前不会进行挂起等待,而是内核返回一个错误(EWOULDBLOCK),当用户进程收到错误后不断轮询内核进程,直到数据准备好用户进程开始将数据从内核空间复制到用户空间,完成I/O操作。用户进程持续轮询内核进程,以查看某个操作是否就绪。这么做往往需要消耗大量的CPU时间。所以在多数情况下,高级语言层面一般不会采用该模型实现I/O操作。
四、I/O多路复用模型
在此模型中,可以看到操作系统将I/O操作的两个阶段进行了更加明显的划分,在第一阶段操作系统提供了select/poll/epoll等系统调用,让多个用户进程堵塞在这些系统调用上,而并非堵塞在真正的I/O系统调用上。进程将一个或者多个fd(文件描述符)传递给select/poll/epoll等系统调用,当有fd就绪时,立刻回调函数rollback,通过调用真正的I/O系统调用(recvfrom)将数据从内核空间复制到用户空间。在链接数较少的情况下,该模型并不适用,因为我们此时多了一个系统调用,使用该模型的优势在于我们可以等待多个描述符就绪。
与I/O多路复用密切相关的另外一种I/O模型是在多线程中使用堵塞型I/O。这种模型与多路复用极为相似,但它没有使用select堵塞在多个文件描述符上,而是使用多个线程(每个文件描述符一个线程),这个每个线程都可以自由地调用诸如recvfrom之类的堵塞式I/O系统调用了。
摘自《UNIX网络编程卷1:套接字联网API(第3版)》
五、信号驱动型I/O模型
通过sigaction系统调用安装一个信号处理函数,当发起调用以后立刻返回,当有fd准备就绪时内核发送SIGIO信号通知用户进程,用户进程通过recvfrom进行数据复制,最后处理数据。这种模型的优势在于等待数据报到达期间进程不被堵塞,主循环可以继续执行,只要等待来自信号处理函数的通知即可。
六、异步I/O模型
此模型通过aio_read系统调用告知内核启动I/O操作,告知以后用户进程可以继续执行后续操作,内核完成I/O的整个操作(包括将数据从内核空间复制到用户空间)之后,通过信号告知用户进程I/O操作完成,用户进程执行对数据的处理。此模型下,整个I/O操作的两阶段都是由内核进程完成,用户进程只需要进行系统调用之后等待完成信号即可。
总结:
对于这几种I/O模型,生活中其实存在很多例子,我们以去售票窗口买车票为例,说明这几种I/O模型的区别。
- 堵塞型I/O:到窗口去买票,如果此时没有票,你一直等在窗口前(堵塞),直到该窗口有车票准备好,你从窗口取票离开。
- 非堵塞型I/O:如果窗口没有票,你就去做别的事情,你每隔一段时间来看一下有没有票(轮询),直到有票你取票离开。
- 多路复用型I/O:买票的时候由专人负责维护所有排队买票的人,并且收集每个人的买票需求,当窗口有票的时候,专人负责通知需要车票的人去窗口取票。
- 信号驱动型I/O:买票的时候如果窗口没票,你可以回家等着或者去干别的事情,当有票到达窗口的时候,售票员打电话通知你过来取票。
- 异步I/O模型:买票时你只需要告诉售票员你的需求,并且把你家的地址告知她,之后你可以去做其他事情,当窗口有票的时候,售票员负责将车票送到你家,你根本不需要关心车票什么时候有,你只需要在家等待即可。