一、我们先来看BIO的问题:
1 没有数据缓冲区,I/O性能存在问题;
2 没有C或C++中的Channel概念,只有输入和输出流;
3 通常会导致通信线程被长时间阻塞;
4 支持的字符集有限,硬件可移植性不好。
阻塞I/O模型:系统调用到数据包到达且被复制到应用进程的缓冲区中或者发生错误时才返回,在此期间一直会等待。
非阻塞I/O模型:轮询检查缓冲区有无数据的状态。
I/O复用模型:Linux提供select/poll,侦测多个fd是否处于就绪状态。同时还提供了一个epoll系统调用,一个基于事件驱动方式代替顺序扫描,因此性能更高。
信号驱动模型:通过信号回调通知应用程序调用recvfrom来读取数据
异步I/O:和信号驱动I/O区别在于异步I/O模型由内核通知我们I/O操作何时已经完成。
二、I/O多路复用
优势:系统开销小,不需要创建新的额外进程或者线程,不需要维护进程和线程的运行,降低了系统维护工作量,节省系统资源。
应用场景:server同时处理多个处于监听状态或者多个连接状态的套接字;
server需要同时处理多种网络协议的套接字。
三、传统BIO编程
下面先看下BIO通信模型图
首先有个Acceptor,负责监听多个客户端的连接,接收到连接请求后为每个客户端创建一个新的线程进行链路处理,处理完成后,通过输出流给客户端发送响应,这就是经典的请求-应答模型。
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,由于线程是Java虚拟机宝贵的系统资源,当线程数膨胀后,系统性能将急剧下降,随着并发量持续增大,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。
为了改进一线程一连接模型,后来又演进了一种通过线程池或者消息队列实现1个或者多个线程处理N个客户端的模型,由于它的底层通信机制依然使用同步阻塞I/O,所以被称为“伪异步”。
四、伪异步I/O编程
伪异步I/O通信模型图
伪异步I/O弊端分析
首先,当对Socket的输入流进行读取操作的时候,它会一直阻塞下去,直到发生如下三种事件:
有数据可读;
可用数据已经读取完毕;
发生空指针或者I/O异常。
这就意味着如果发送请求或者应答消息比较慢,或者网络传输较慢时,读取输入流一方通信线程就将长时间阻塞,在此期间,其他接入消息只能在消息队列中排队。
因此,伪异步I/O实际上仅仅是对之前I/O线程模型的一个简单优化,它无法从根本上解决同步I/O导致的通信线程阻塞问题。
OK,到这里,阻塞I/O的相关知识就讲解完毕,下节开始讲NIO编程的相关知识和原理。