要想理解这几个基本的网络模型需要认清楚以下几点:
1、不要被 “阻塞”、“非阻塞” 的字面意思所误导;
2、需要清楚 ”内核态“、”用户态“ 的基本概览;
3、Java nio 包中的网络模型,并不是Linux层面所讲的”同步非阻塞IO模型(NIO)“;
4、传统的BIO并不是说一定不可用,某些场景下反而更适合;
如下6种
1、同步阻塞IO(BIO):
- 应用程序【用户态】需要为每个连接提供一个线程来处理网络io事件;
- 这里的”阻塞“更大意义上是偏向【可能导致大量的线程资源处于阻塞的状态】;
- 线程资源宝贵,无法大量创建;
- 线程过多切换,效率损耗;
- 这些线程会在io过程中进行等待,干不了其他事情;
- 编码简单,【并发低&连接少】场景下的首选(几个-几十个);
- 应用程序端可以使用线程池;
2、同步非阻塞IO(NIO):
- 并不是等同于Java中的nio包下的编程模型;
- 该种模型实际运用非常少见;
- 个人理解,它甚至不如BIO;
- 如果不加以优化改造,还是需要大量的线程,每个连接需要分配一个;
- 只不过这些线程不是阻塞等待io结果响应,而生一直忙轮询;
3、多路复用:
- 目前互联网上使用最多的io模型;
- Java nio 包其实是对这种模型的实现;
- select/poll/epoll 分别对应着三种多路复用管理的手段,随着linux内核版本的升级而逐步升级;
- 重点在epoll的特性及优势上面;
- 应用程序只需要使用一个线程就可以感知到多个连接的io事件,存在就绪的io事件以后,应用程序可选择主动处理(使用线程池);
- 不再需要开辟大量的线程资源,阻塞等待;
- 不再需要浪费大量的线程空轮询耗费cpu;
4、信号驱动:
信号驱动模式利用linux信号机制,通过sigaction函数将sigio读写信号以及handler回调函数注册到内核队列中,注册后应用进程不堵塞,可以去干别的工作。当网络IO状态发生变化时触发SIGIO中断,通过调用应用程序的handler通知应用程序网络IO就绪了。信号驱动的前半部分操作是异步行为,后面的网络数据操作仍然属于同步阻塞行为。
- 内核io事件就绪会通知用户态;
- 用户态再进行数据读取(内核态->用户态);
5、异步IO(AIO):
异步IO通过一些列异步API实现,是五种IO模式中唯一个真正的异步模式,目前Java的AIO使用的就是本模式。异步模式的读操作通过调用内核的aio_read函数来实现。应用线程调用aio_read,递交给内核一个用户空间下的缓冲区。内核收到请求后立刻返回,不阻塞应用线程。当网络设备的数据到来后,内核会自动把数据从内核空间拷贝到aio_read函数递交的用户态缓存。拷贝完成后以信号的方式通知用户线程,用户线程拿到数据后就可以执行后续操作。
- 应用程序在【内核】io准备(用户态不可访问io结果)的阶段,不需要任何一个线程轮询或者阻塞等待;
- 类似【内核】主动带着应用程序可访问的数据来回调应用程序;
参考文章: