BIO和NIO是两种不一样的网络通讯模型,现现在NIO已经大量应用在Jetty、ZooKeeper、Netty等开源框架中。react
一个面向流、一个面向缓冲区后端
一个是阻塞式的、一个非阻塞安全
一个没有io多路复用器、一个有服务器
下面经过一个例子解释二者区别:网络
假设当前服务端程序须要同时从与多个客户端创建的链接读取数据。多线程
若是采用阻塞式IO,单线程状况下,处理者线程可能阻塞在其中一个套接字的read上,致使另外一个套接字即便准备好了数据也没法处理,这个时候解决的方法就是针对每个套接字,都新建一个线程处理其数据读取。并发
因此说,在BIO工做模式下,服务端程序要想同时处理多个套接字的数据读取,在等待接收链接请求的主线程以外,还要为每个创建好的链接分配一个新的线程进行处理。框架
轮询方式函数
若是将套接字读操做换成非阻塞的,那么只须要一个线程就能够同时处理套接字,每次检查一个套接字,有数据则读取,没有则检查下一个,由于是非阻塞的,因此执行read操做时若没有数据准备好则当即返回,不会发生阻塞。性能
I/O多路复用
这种轮询的方式缺点是浪费CPU资源,大部分时间可能都是无数据可读的, 没必要仍不间断的反复执行read操做,I/O多路复用(IOmultiplexing)是一种更好的方法,调用select函数时,其内部会维护一张监听 的套接字的列表,其会一直阻塞直到其中某一个套接字有数据准备好才返回,并告诉是哪一个套接字可读,这时再调用该套接字的read函数效率更高。
因此基本能够认为 “NIO = I/O多路复用 + 非阻塞式I/O”,大部分状况下是单线程,但也有超过一个线程实现NIO的状况
NIO三种模型
上面所讲到的只须要一个线程就能够同时处理多个套接字,这只是其中的一种单线程模型,是一种较为极端的状况,NIO主要包含三种线程模型:
1) Reactor单线程模型
2) Reactor多线程模型
3)主从Reactor多线程模型
Reactor单线程模型:
单个线程完成全部事情包括接收客户端的TCP链接请求,读取和写入套接字数据等。
对于一些小容量应用场景,可使用单线程模型。可是对于高负载、大并发的应用却不合适,主要缘由以下:
1) 一个NIO线程同时处理成百上千的链路,性能上没法支撑,即使NIO线程的CPU负荷达到100%,也没法知足海量消息的编码、解码、读取和发送;
2) 当NIO线程负载太重以后,处理速度将变慢,这会致使大量客户端链接超时,超时以后每每会进行重发,这更加剧了NIO线程的负载,最终会致使大量消息积压和处理超时,NIO线程会成为系统的性能瓶颈;
3) 可靠性问题:一旦NIO线程意外跑飞,或者进入死循环,会致使整个系统通讯模块不可用,不能接收和处理外部消息,形成节点故障。
为了解决这些问题,演进出了Reactor多线程模型。
Reactor多线程模型:
Rector多线程模型与单线程模型最大的区别就是有一组NIO线程处理真实的IO操做。
Reactor多线程模型的特色:
1) 有专门一个NIO线程-Acceptor线程用于监听服务端,接收客户端的TCP链接请求;
2) 网络IO操做-读、写等由一个NIO线程池负责,线程池能够采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送;
3) 1个NIO线程能够同时处理N条链路,可是1个链路只对应1个NIO线程,防止发生并发操做问题。
在绝大多数场景下,Reactor多线程模型均可以知足性能需求;可是,在极特殊应用场景中,一个NIO线程负责监听和处理全部的客户端链接可能会存在性能问题。例如百万客户端并发链接,或者服务端须要对客户端的握手消息进行安全认证,认证自己很是损耗性能。在这类场景下,单独一个Acceptor线程可能会存在性能不足问题,为了解决性能问题,产生了第三种Reactor线程模型-主从Reactor多线程模型。
即从单线程中由一个线程即监听链接事件、读写事件、由完成数据读写,拆分为由一个线程专门监听各类事件,再由专门的线程池负责处理真正的IO数据读写。
主从Reactor多线程模型
主从Reactor线程模型与Reactor多线程模型的最大区别就是有一组NIO线程处理链接、读写事件。
主 从Reactor线程模型的特色是:服务端用于接收客户端链接的再也不是个1个单独的NIO线程,而是一个独立的NIO线程池。Acceptor接收到客户 端TCP链接请求处理完成后(可能包含接入认证等),将新建立的SocketChannel注册到IO线程池(sub reactor线程池)的某个IO线程上,由它负责SocketChannel的读写和编解码工做。Acceptor线程池仅仅只用于客户端的登录、握手 和安全认证,一旦链路创建成功,就将链路注册到后端subReactor线程池的IO线程上,由IO线程负责后续的IO操做。
即从多线程模型中由一个线程来监听链接事件和数据读写事件,拆分为一个线程监听链接事件,线程池的多个线程监听已经创建链接的套接字的数据读写事件,另外和多线程模型同样有专门的线程池处理真正的IO操做。
NIO适用场景
服务器须要支持超大量的长时间链接。好比10000个链接以上,而且每一个客户端并不会频繁地发送太多数据。例如总公司的一个中心服务器须要收集全国便利店各个收银机的交易信息,只须要少许线程按需处理维护的大量长期链接。
Jetty、Mina、Netty、ZooKeeper等都是基于NIO方式实现。