全文总结于李林锋老师的《Netty权威指南》,链接如下:
![](https://i-blog.csdnimg.cn/blog_migrate/6abe3e5a2d8e088bcec5755144a1b350.jpeg)
Linux系统I/O的实现
Linux阻塞
linux非阻塞
Linux I/O复用
Linux提供select/poll,进程通过将一个或多个fd(file descriptor,文件描述符,Linux内核为高效管理已被打开的“文件”所创建的索引)传递给select或poll系统调用,阻塞在select操作上,这样select/poll可以帮我们侦测多个fd是否处于就绪状态。select/poll是顺序扫描fd是否就绪,而且支持的fd数量有限,因此它的使用受到制约。Linux还提供了一个epoll系统调用,epoll使用基于时间驱动方式代替顺序扫描,因此性能更高。当有fd就绪时,立即回调函数rollback。
Linux信号驱动
首先开启套接口信号驱动I/O功能,并通过系统调用sigaction执行一个信号处理函数(此系统调用立即返回,进程继续工作,非阻塞)。当数据准备就绪时,就为该进程生成一个sigio信号,通过信号回调通知应用程序调用recvfrom来读取数据,并通知主循环函数处理数据。
异步I/O
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型区别是:信号驱动I/O由内核通知我们何时可以开始一个I/O操作:异步I/O模型由内核通知我们
I/O操作何时已经完成。
I/O多路复用技术通过把多个
I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。与传统的多线程模型相比,
I/O多路复用的最大优势是系统开销小,不需要创建新的额外线程,降低了系统维护工作量,节省了系统资源
。
应用场景:
1.服务器需要同时处理多个处于监听状态或者多个连接状态的套接字;
2.服务器需要同时处理多种网络协议的套接字;
目前支持有select、pselect、poll和epoll。
其中,epoll相比select的优点;
1.支持一个进程打开的socket描述符(fd)不受限制(仅受限于操作系统的最大文件句柄数),select受限于fd_setsize,默认1024;
2.I/O效率不会随着fd数目的增加而下降。使用select/poll时,当拥有一个很大的socket集合,由于网络延时和链路空闲,某时刻只有少部分socket是活跃的,而select/poll每次调用都会现行扫描全部的集合,效率低下。epoll是根据每个fd上的callback函数实现,只有活跃的socket才主动调用callback函数;
3.使用mmap加速内核与用户空间的消息传递;
4.epoll的API更简单。
Java网络编程模型
网络编程模型说白了Client/Server模型,两个进程之间通信。服务端提供IP地址和接口,客户端通过连接操作向服务端监听的接口发起请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(socket)进行通信。下面说说在JDK各版本网络编程中对IO的支持:
IO
即BIO(阻塞IO),在JDK1.4以前java都是采用这种方式网络通信的。同步阻塞I/O最大的问题:每当有一个新的客户端请求接入时,服务端必须创建一个新的线程处理新接入的客户端链路,一个线程只能处理一个客户端连接。
我们可以基于BIO自行实现伪异步I/O通信框架:采用线程池和任务队列。当有新的客户端接入时,将客户端的socket封装成一个task(实现Runnable接口)投递到后端你的线程池中进行处理,JDK线程池维护一个消息队列和N个活跃线程,对消息队列中的任务进行处理。
当这种实现方式没有从根本上解决同步I/O导致的通信线程阻塞问题,通信对方返回应答时间过长会引发级联故障。于是jdk后来提出了NIO。
NIO
即New IO,在JDK 1.4正式提出。NIO提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现。这两种新增的通道都支持阻塞和非阻塞两种模式。阻塞模式使用简单,但是性能和可靠性不好。非阻塞模型相反。一般来说,低负载、低并发的应用程序可使用同步阻塞I/O以降低编程复杂度。
NIO提供了高速的、面向块的I/O。通过定义包含数据的类,以及通过以块的形式处理这些数据,NIO不用使用本机代码就可以利用低级优化。重点关注如下概念:
缓冲区Buffer
包含要写入和读出的数据。在NIO库中,所有的数据都是用缓冲区处理的。缓冲区实质上是一个数组,通常是一个字节数组(ByteBuffer),也可以使用其他种类的数组。缓冲区还提供了对数据的结构化访问以及维护读写位置(limit)等信息。
基本所有java基本类型(除了boolean)都对应有个缓冲区。因为大多数标准I/O操作都使用ByteBuffer,所以它还提供了一些特有的操作,以方便网络读写。
![](https://img-blog.csdn.net/20180316143050222)
通道Channel
网络数据通过Channel读取和写入。和流不同之处在于通道是双向的,因为Channel是全双工(
Full Duplex,是通讯传输的一个术语。通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合
),所以它可以比流更好地映射底层操作系统的API。
![](https://img-blog.csdn.net/20180316143100495)
Channel大致可以分为两大类:用于网络读写的SelectableChannel和用于文件操作的FileChannel。
多路复用器Selector
提供选择已经就绪的任务的能力。Selector会不断地轮询注册在其上的Channel,如果某个Channel上面发生读或写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。
一个多路复用器Selector可以同时轮询多个Channel,JDK使用了epoll()代替了传统的select实现,所以没有最大连接句柄1024/2048的限制。这就是说,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。
![](https://img-blog.csdn.net/20180316143111977)
![](https://img-blog.csdn.net/20180316143122876)
AIO
异步通道提供两种方式获取操作结果:
1.通过java.util.concurrent.Future类来标识异步操作的结果;
2.在执行异步操作的时候传入一个java.nio.channels;
Completionhandler接口的实现类作为操作完成的回调。
它的异步套接字通道是真正的异步非阻塞I/O,对应于UNIX网络编程中的事件驱动
I/O
(AIO)。它不需要通过多路复用器Selector对注册的通道进行轮询操作即可实现异步读写。
异步服务端通道AsychronousServerSocketChannel,异步客户端通道AsychronousSocketChannel
各版本IO总结
JDK1.4推出的NIO,Selector是基于select/poll模型实现的,基于I/O复用技术实现的非阻塞I/O,不是异步I/O。在JDK1.5晚些版本优化了Selector实现使用了epoll替换了select/poll,上层的API没有变化。但是仍旧没有改变I/O模型。这里需要注意epoll一个致命BUG:可能导致Selector空轮询,最终CPU 100%。
JDK7提供的AIO提供了异步的套接字通道,是真正的异步I/O。在异步I/O操作的时候可以传递信号变量,当操作完成之后会回调相关方法。
NIO实现的关键就是多路复用I/O技术,多路复用的核心就是通过Selector来轮询注册在其上的Channel,当发现某个或多个Channel处于就绪状态,从阻塞状态返回就绪的Channel的选择键集合,进行I/O操作。
各版本IO的比较图:
![](https://img-blog.csdn.net/20180316143550249)