Linux下实现的IO模型:
Linux的内核将所有外部设备都看做一个文件来操作,对一个文件的读写操作会调用内核提供的系统命令,返回一个file descriptor(fd,文件描述符)。对一个socket
的读写也会有相应的描述符,称为socket描述符。描述符就是一个数字,它执行内核中的一个结构体(文件路径,数据区等一些属性)。
因为程序运行在操作系统上,编程语言实现的IO操作API最终依赖于操作系统的IO实现。先理清阻塞、非阻塞、同步、异步这几个概念:
阻塞:调用方发起调用请求,在没有返回结果之前,调用方线程被挂起,出于一直等待状态
非阻塞:与阻塞相对,调用方发起调用请求,当前线程不会等待挂起,而会立即返回。后续可以通过轮询等手段来获取调用结果状态。
同步:所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回
异步:与同步相对,当一个异步过程调用发出后,调用者不会立刻得到结果,通过回调等措施来处理这个调用。
Linux实现了5中IO模型
1、阻塞IO模型
默认情况下,所有的文件操作都是阻塞的,在进程空间中调用recvform(recvform函数,用于从Socket套接口上接收数据),其系统调用直到数据包到达且被
复制到应用进程的缓冲区中或者发生错误才返回,在此期间会一直等待,进程在从调用recvform开始到它返回的整段时间内都是被阻塞的。
2、非阻塞IO模型
recvform从应用层到内核的时候,如果该缓冲区没有数据的话,就直接返回一个EWOULDBLOCK错误,一般都是对非阻塞IO模型进行轮询来检查这个状态,看
内核是不是有数据到来。
3、IO复用模型
Linux提供select/poll,进程通过将一个或多个fd传递给select或者poll系统调用,阻塞在select操作上,这样select/poll可以帮我们侦测多个fd是否处于就绪状态。
select/poll是顺序扫描fd是否就绪,而且支持的fd数量有限,因此它的使用受到了一些制约。Linux还提供了一个epoll系统调用,epoll使用基于事件驱动的方式
代替顺序扫描,因此性能更高。当有fd就绪时,立即回调函数rollback。
(Java核心类库Selector就是基于epoll的多路复用技术实现)
4、信号驱动IO模型
首先开启套接口信号驱动IO功能,并通过系统调用sigaction执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,
就为该进程生成一个SIGIO信号,通过信号回调通知应用程序调用recvform来读取数据,并通知主循环函数处理数据。(好了告诉我,我来处理,我先去忙)
5、异步IO
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。与信号驱动模型的主要区别是:信号驱动IO是由内
核通知我们何时可以开始一个IO操作;异步IO模型由内核通知我们何时已经完成。(等处理好了再告诉我)
IO多路复用技术:在IO编程过程中,当需要同时处理多个客户端接入请求时,可以利用多线程或者IO多路复用技术进行处理。IO多路复用技术通过把多个IO的阻塞到
同一个select的阻塞上,从而可以使系统在单线程的情况下同时处理多个客户端请求。而且与传统的多线程模型比,最大的优势是开销小,系统不需要创建新的额
外进程或这线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。
Java的IO演进
在Java1.4推出NIO之前,基于Java的所有Socket通信都采用了同步阻塞模式(BIO),一请求一应答,当并发访问量增大时响应时间延迟也增大,在性能和可靠性方面
存在巨大的瓶颈。因此推出了NIO(Non-Blocked IO)
但是它仍有很多不完善的地方,特别是对文件系统的处理能力仍显不足,主要问题如下
1)、没有统一的文件属性
2)、API能力比较弱,例如目录的级联创建和递归遍历,往往需要自己实现
3)、底层存储系统的一些高级API无法使用
4)、所有的文件操作都是同步阻塞调用,不支持异步文件读写操作
传统的BIO编程
网络编程的基本模型是CS模型,也就是两个进程之间相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起
连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket)进行通信。
在基于传统同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口,Socket负责发起连接操作。连接成功之后,双方通过输入和输出流进行同步阻塞式通信。
ServerSocket的accept方法在连接传入前会一直阻塞。不过BIO主要的问题在于每当有一个新的客户端请求接入时,服务端必须创建一个新的线程处理新接入的客户端链
路,一个线程只能处理一个客户端连接。处理完成之后,通过输出流返回应答给客户端,线程销毁。在高性能服务器应用领域,往往需要成千上万个客户端的并发连接,
这种模型显然无法满足高性能、高并发接入的场景。
NIO编程
AIO编程
NIO2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步通道提供以下两种方式获取操作结果:
1):通过java.util.concurrent.Future类来表示异步操作的结果
2):在执行异步操作的时候传入一个java.nio.channels
CompletionHandler接口的实现类作为操作完成的回调。
NIO2.0的异步套接字通道是真正的异步非阻塞IO,对应于UNIX网络编程中的事件驱动IO(AIO)。它不需要通过多路复用器对注册的通道进行轮询操作即可实现异步读
写,从而简化了NIO的编程模型。