可能这是关于BIO-NIO-AIO-Netty处理模型最好理解的文章了
无意中看到别人总结BIO-NIO-AIO-Netty的前世今生,也在往上翻阅了一些帖子,总感觉讲的太过于繁杂,往往一个简单的概念层层拓展,盖过了文章的主题,所以我想图文并茂的,层层递进,简单点、再简单点的讲出来。所以本文中不会出现代码。讲的不好的,或者错误的请指正!!
BIO(Blocking Input/Output)
处理步骤: 启动服务端,并循环监听客户端连接,每监听到一个请求,创建一个新线程处理数据。在未接收到连接时,会一直阻塞。每个客户端发送请求,处理完成后,再通过输出流返回客户端,然后销毁线程(bio的读写是半双工)。
缺陷:一个客户端请求,就对应一个线程,当请求过多的时候,线程越来越多,jvm内存被大量占用,线程是Java虚拟机宝贵资源,线程数膨胀后,系统性能下降,线程并发访问量继续增大,会导致进程宕机或僵死。
NIO(Non-blocking Input/Output)
这里需要阐述几个概念:
Buffer:当一个链接建立完成后,IO的数据未必会马上到达,为了当数据到达时能够正确完成IO操作,在BIO(阻塞IO)中,等待IO的线程必须被阻塞,以全天候地执行IO操作。为了解决这种IO方式低效的问题,引入了缓冲区的概念,当数据到达时,可以预先被写入缓冲区,再由缓冲区交给线程,因此线程无需阻塞地等待IO。
Channel: 是 I/O 传输发生时通过的入口,而缓冲区是这些数据传输的来源或目标。对于离开缓冲区的传输,您想传递出去的数据被置于一个缓冲区,被传送到通道。对于传回缓冲区的传输,一个通道将数据放置在您所提供的缓冲区中。
Selector: 通道和缓冲区的机制,使得线程无需阻塞地等待IO事件的就绪,但是总是要有人来监管这些IO事件。这个工作就交给了selector来完成,这就是所谓的同步。Selector允许单线程处理多个 Channel。要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪,这就是所说的轮询。一旦这个方法返回,线程就可以处理这些事件。
处理步骤: 看图最直观和BIO的区别就是多了一个Selector,就是说一个可以把多个客户端连接分配给一个Selector,他来负责与客户端的连接,那具体数据怎么处理呢,Selector会把具体处理的给到图中它的工人们(就是一线程池)。
缺点:Selector在不停的轮询,那有没有办法,让Selector就等着,有客户端连接的时候 通知我呗!这就是AIO。
AIO(Asynchronous Input/Output)
NIO 2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。
异步的套接字通道时真正的异步非阻塞I/O,对应于UNIX网络编程中的事件驱动I/O(AIO)。他不需要过多的Selector对注册的通道进行轮询即可实现异步读写,从而简化了NIO的编程模型。API比NIO的使用起来真的简单多了,主要就是监听、读、写等各种CompletionHandler。此处本应有一个WriteHandler的,确实,我们在ReadHandler中,以一个匿名内部类实现了它。
AIO流程其实是在NIO的基础上,是真正的异步非阻塞的,核心就是观察者模式的实现(可以理解为C语言的钩子,js的回调函数),从轮询变为了事件回调(比如:什么时候有客户端连上来了,你帮我调用某段代码,我该干嘛干嘛去)。
Netty
Netty封装了JDK的NIO,让你用得更爽,你不用再写一大堆复杂的代码了。Netty是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。
jdk自带的nio的api需要了解太多的概念,以及处理细节,学习成本太高了,而且各种异常处理,稍微不小心就手写了一个bug,而且代码的可读性很差!比如nio的粘包拆包,想想就恶心到不行。
那为什么netty封装的是nio而不是aio呢?
官网上是这么说的:大多数服务端都部署在linux系统上,但是linxu系统还没有AIO的实现,还是以epoll轮询来处理,所以封装AIO的话就显得多此一举了,但是windows系统是实现了aio的!
关于 nio和aio的概念的解释,请查看:https://blog.****.net/luzhensmart/article/details/82230076