文1转自:http://blog.csdn.net/jiyiqinlovexx/article/details/51204726
文2转自:http://www.php.cn/java-article-361228.html
侵删
文1
引言
BIO(Blocking-IO)和NIO(Non-Blocking-IO或New IO)是两种不同的网络通信模型,现如今NIO已经大量应用在Jetty、ZooKeeper、Netty等开源框架中。
一个面向流、一个面向缓冲区
一个是阻塞式的、一个非阻塞
一个没有io多路复用器、一个有
下面通过一个例子解释两者区别:
假设当前服务端程序需要同时从与多个客户端建立的连接读取数据。
使用BIO
如果采用阻塞式IO,单线程情况下,处理者线程可能阻塞在其中一个套接字的read上,导致另一个套接字即使准备好了数据也无法处理,这个时候解决的方法就是针对每一个套接字,都新建一个线程处理其数据读取。
所以说,在BIO工作模式下,服务端程序要想同时处理多个套接字的数据读取,在等待接收连接请求的主线程之外,还要为每一个建立好的连接分配一个新的线程进行处理。
使用NIO
轮询方式
如果将套接字读操作换成非阻塞的,那么只需要一个线程就可以同时处理套接字,每次检查一个套接字,有数据则读取,没有则检查下一个,因为是非阻塞的,所以执行read操作时若没有数据准备好则立即返回,不会发生阻塞。
I/O多路复用
这种轮询的方式缺点是浪费CPU资源,大部分时间可能都是无数据可读的,不必仍不间断的反复执行read操作,I/O多路复用(IOmultiplexing)是一种更好的方法,调用select函数时,其内部会维护一张监听的套接字的列表,其会一直阻塞直到其中某一个套接字有数据准备好才返回,并告诉是哪个套接字可读,这时再调用该套接字的read函数效率更高。
所以基本可以认为 “NIO = I/O多路复用 + 非阻塞式I/O”,大部分情况下是单线程,但也有超过一个线程实现NIO的情况
NIO三种模型
上面所讲到的只需要一个线程就可以同时处理多个套接字,这只是其中的一种单线程模型,是一种较为极端的情况,NIO主要包含三种线程模型:
Reactor单线程模型
Reactor多线程模型
3)主从Reactor多线程模型
Reactor单线程模型:
单个线程完成所有事情包括接收客户端的TCP连接请求,读取和写入套接字数据等。
对于一些小容量应用场景,可以使用单线程模型。但是对于高负载、大并发的应用却不合适,主要原因如下:
一个NIO线程同时处理成百上千的链路,性能上无法支撑,即便NIO线程的CPU负荷达到100%,也无法满足海量消息的编码、解码、读取和发送;
当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了NIO线程的负载,最终会导致大量消息积压和处理超时,NIO线程会成为系统的性能瓶颈;
可靠性问题:一旦NIO线程意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。
为了解决这些问题,演进出了Reactor多线程模型。
Reactor多线程模型:
Rector多线程模型与单线程模型最大的区别就是有一组NIO线程处理真实的IO操作。
Reactor多线程模型的特点:
有专门一个NIO线程-Acceptor线程用于监听服务端,接收客户端的TCP连接请求;
网络IO操作-读、写等由一个NIO线程池负责,线程池可以采用标准的JDK线程池实现,它包含一个任务队列和N个可用的线程,由这些NIO线程负责消息的读取、解码、编码和发送;
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方式实现。
BIO适用场景
适用于连接数目比较小,并且一次发送大量数据的场景,这种方式对服务器资源要求比较高,并发局限于应用中。
文2
NIO是为了弥补IO操作的不足而诞生的,NIO的一些新特性有:非阻塞I/O,选择器,缓冲以及管道。管道(Channel),缓冲(Buffer) ,选择器( Selector)是其主要特征。
概念解释:
Channel——管道实际上就像传统IO中的流,到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象。一个 Buffer 实质上是一个容器对象。
Selector——选择器用于监听多个管道的事件,使用传统的阻塞IO时我们可以方便的知道什么时候可以进行读写,而使用非阻塞通道,我们需要一些方法来知道什么时候通道准备好了,选择器正是为这个需要而诞生的。
NIO和传统的IO有什么区别呢?
1,IO是面向流的,NIO是面向块(缓冲区)的。
IO面向流的操作一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。,导致了数据的读取和写入效率不佳;
NIO面向块的操作在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多,同时数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。通俗来说,NIO采取了“预读”的方式,当你读取某一部分数据时,他就会猜测你下一步可能会读取的数据而预先缓冲下来。
2,IO是阻塞的,NIO是非阻塞的。
对于传统的IO,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
而对于NIO,使用一个线程发送读取数据请求,没有得到响应之前,线程是空闲的,此时线程可以去执行别的任务,而不是像IO中那样只能等待响应完成。
NIO和IO适用场景
NIO是为弥补传统IO的不足而诞生的,但是尺有所短寸有所长,NIO也有缺点,因为NIO是面向缓冲区的操作,每一次的数据处理都是对缓冲区进行的,那么就会有一个问题,在数据处理之前必须要判断缓冲区的数据是否完整或者已经读取完毕,如果没有,假设数据只读取了一部分,那么对不完整的数据处理没有任何意义。所以每次数据处理之前都要检测缓冲区数据。
那么NIO和IO各适用的场景是什么呢?
如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,这时候用NIO处理数据可能是个很好的选择。
而如果只有少量的连接,而这些连接每次要发送大量的数据,这时候传统的IO更合适。使用哪种处理数据,需要在数据的响应等待时间和检查缓冲区数据的时间上作比较来权衡选择。
通俗解释
最后,对于NIO和传统IO,有一个网友讲的生动的例子:
以前的流总是堵塞的,一个线程只要对它进行操作,其它操作就会被堵塞,也就相当于水管没有阀门,你伸手接水的时候,不管水到了没有,你就都只能耗在接水(流)上。
nio的Channel的加入,相当于增加了水龙头(有阀门),虽然一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各个水管里流出来的水,都可以得到妥
善接纳,这个关键之处就是增加了一个接水工,也就是Selector,他负责协调,也就是看哪根水管有水了的话,在当前水管的水接到一定程度的时候,就切换一下:临时关上当
前水龙头,试着打开另一个水龙头(看看有没有水)。
当其他人需要用水的时候,不是直接去接水,而是事前提了一个水桶给接水工,这个水桶就是Buffer。也就是,其他人虽然也可能要等,但不会在现场等,而是回家等,可以做
其它事去,水接满了,接水工会通知他们。
这其实也是非常接近当前社会分工细化的现实,也是统分利用现有资源达到并发效果的一种很经济的手段,而不是动不动就来个并行处理,虽然那样是最简单的,但也是最浪费
资源的方式。