一、NIO介绍:

NIO模型:

1、Channel为连接通道,相当于一个客户端与服务器的一个连接,Selector为通道管理器,将Channel注册到Selector上,Selector管理着这些Channel,当管理的某个通道有事件到达时,Selector将会通知应用程序去处理;Selector工作的线程和处理网络事件的应用是在不同的线程中;

2、NIO 有一个主要的类Selector,这个类似一个观察者,只要我们把需要探知的socketchannel告诉Selector,我们接着做别的事情,当有事件发生时,他会通知我们,传回一组SelectionKey,我们读取这些Key,就会获得我们刚刚注册过的socketchannel,然后,我们从这个Channel中读取数据,放心,包准能够读到,接着我们可以处理这些数据。

3、Selector内部原理实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙,让我们通过这把钥匙来读取这个channel的内容。

其使用流程如下:

二、单线程BIO实现:

BIO也即Blocking IO,即阻塞的IO;

传统的BIO使用流程如下:

 public class IOServer {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(2345));
} catch (IOException ex) {
return;
}
try{
while(true) {
Socket socket = serverSocket.accept();
InputStream inputstream = socket.getInputStream();
IOUtils.closeQuietly(inputstream);
}
} catch(IOException ex) {
IOUtils.closeQuietly(serverSocket);
}
}
}

三、多线程BIO实现:

上例使用单线程逐个处理所有请求,同一时间只能处理一个请求,等待I/O的过程浪费大量CPU资源,同时无法充分使用多CPU的优势。下面是使用多线程对阻塞I/O模型的改进。一个连接建立成功后,创建一个单独的线程处理其I/O操作。

 public class IOServerMultiThread {
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(2345));
} catch (IOException ex) {
return;
}
try{
while(true) {
Socket socket = serverSocket.accept();
new Thread( () -> {
try{
InputStream inputstream = socket.getInputStream();
IOUtils.closeQuietly(inputstream);
} catch (IOException ex) {
}
}).start();
}
} catch(IOException ex) {
IOUtils.closeQuietly(serverSocket);
}
}
}

四、线程池处理BIO实现:

从上面的代码可以看到:当accept返回(也就是有数据网络数据到达时),创建一个线程,并在线程中处理网络读写以及逻辑处理;

为了防止连接请求过多,导致服务器创建的线程数过多,造成过多线程上下文切换的开销。可以通过线程池来限制创建的线程数:

 public class IOServerThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(2345));
} catch (IOException ex) {
return;
}
try{
while(true) {
Socket socket = serverSocket.accept();
executorService.submit(() -> {
try{
InputStream inputstream = socket.getInputStream();
} catch (IOException ex) {
}
});
}
} catch(IOException ex) {
try {
serverSocket.close();
} catch (IOException e) {
}
}
}
}

与上面的代码的区别之处在读写网络数据的线程是从线程池里面分配的,充分利用了线程,避免了大量创建线程的开销以及线程上下文切换的开销;

五、经典Reactor模式实现:

上面的模型也即NIO的标准模型,可以看到:多个Channel可以注册到同一个Selector对象上,实现了一个线程同时监控多个请求状态(Channel)。同时注册时需要指定它所关注的事件;

Acceptor处理客户端的连接请求,handlers(read,decode, compute, encode, send)执行非阻塞的读写,Reactor将IO事件派发给相应handlers来处理;Acceptor和handlers使用的是同一个管理器Selector,并且是在同一个线程中处理的;

 public class NIOServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(1234));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (selector.select() > 0) {
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
ServerSocketChannel acceptServerSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = acceptServerSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int count = socketChannel.read(buffer);
if (count <= 0) {
socketChannel.close();
key.cancel();
continue;
}
}
keys.remove(key);
}
}
}
}

selector.select()是阻塞的,当有至少一个通道可用时该方法返回可用通道个数。同时该方法只捕获Channel注册时指定的所关注的事件。

六、多工作线程的Reactor模式实现:

经典Reactor模式中,尽管一个线程可同时监控多个请求(Channel),但是所有handler以及acceptor的处理都在同一个线程中处理,无法充分利用多CPU的优势,同时读/写操作也会阻塞对新连接请求的处理。因此可以引入多线程,并行处理多个读/写操作,其模型如下图所示:

实现代码如下:

 public class NIOServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(1234));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while(iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
ServerSocketChannel acceptServerSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = acceptServerSocketChannel.accept();
socketChannel.configureBlocking(false);
SelectionKey readKey = socketChannel.register(selector, SelectionKey.OP_READ);
readKey.attach(new Processor());
} else if (key.isReadable()) {
Processor processor = (Processor) key.attachment();
processor.process(key);
}
}
}
}
} public class Processor {
private static final ExecutorService service = Executors.newFixedThreadPool(16);
public void process(SelectionKey selectionKey) {
service.submit(() -> {
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
int count = socketChannel.read(buffer);
if (count < 0) {
socketChannel.close();
selectionKey.cancel();
return null;
} else if(count == 0) {
return null;
}
return null;
});
}
}

可以看到他与经典Reactor模式的区别就是handler处理(网络读写以及encode,computer,decode处理)是在线程池中申请一个线程来专门处理的,因此acceptor连接请求处理和网络读写请求处理是在不同的线程中,网络读写不会影响acceptor连接的处理,可以提高服务器对于连接处理的效率和相应速度;

七、多Reactor实现:

Selector的实现原理是对其所管理的所有Channel进行轮询查询(在一个单独的线程中),而上面的多线程工作Reactor模式仍然是在一个Selector上进行轮询查询acceptor和网络读写,Selector在处理网络读写的同时是无法进行acceptor连接的处理的,因此在Selector压力较大时会有网络延迟;

多Reactor是在网络读写与acceptor实在不同的Selector上,也即acceptor是在主Reactor上,网络读写是在子Reactor上,一个主Reactor负责监控所有的连接请求,多个子Reactor负责监控并处理读/写请求,减轻了主Reactor的压力,降低了主Reactor压力太大而造成的延迟。

并且每个子Reactor分别属于一个独立的线程,每个成功连接后的Channel的所有操作由同一个线程处理。这样保证了同一请求的所有状态和上下文在同一个线程中,避免了不必要的上下文切换,同时也方便了监控请求响应状态。

Mina的线程模型正是采用了这种模型;

模型如下图所示:

子Reactor个数是当前机器可用核数的两倍。对于每个成功连接的SocketChannel,通过round robin的方式交给不同的子Reactor。

示意代码如下:

 public class NIOServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(1234));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
int coreNum = Runtime.getRuntime().availableProcessors();
Processor[] processors = new Processor[coreNum];
for (int i = 0; i < processors.length; i++) {
processors[i] = new Processor();
}
int index = 0;
while (selector.select() > 0) {
Set<SelectionKey> keys = selector.selectedKeys();
for (SelectionKey key : keys) {
keys.remove(key);
if (key.isAcceptable()) {
ServerSocketChannel acceptServerSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = acceptServerSocketChannel.accept();
socketChannel.configureBlocking(false);
Processor processor = processors[(int) ((index++) % coreNum)];
processor.addChannel(socketChannel);
processor.wakeup();
}
}
}
}
} public class Processor {
private static final ExecutorService service =
Executors.newFixedThreadPool(2 * Runtime.getRuntime().availableProcessors());
private Selector selector;
public Processor() throws IOException {
this.selector = SelectorProvider.provider().openSelector();
start();
}
public void addChannel(SocketChannel socketChannel) throws ClosedChannelException {
socketChannel.register(this.selector, SelectionKey.OP_READ);
}
public void wakeup() {
this.selector.wakeup();
}
public void start() {
service.submit(() -> {
while (true) {
if (selector.select(500) <= 0) {
continue;
}
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
SocketChannel socketChannel = (SocketChannel) key.channel();
int count = socketChannel.read(buffer);
if (count < 0) {
socketChannel.close();
key.cancel();
continue;
} else if (count == 0) {
continue;
} else {
System.out("{}\t Read message {}", socketChannel, new String(buffer.array()));
}
}
}
}
});
}
}

Java NIO Reactor模式的更多相关文章

  1. JAVA NIO non-blocking模式实现高并发服务器(转)

    原文链接:JAVA NIO non-blocking模式实现高并发服务器 Java自1.4以后,加入了新IO特性,NIO. 号称new IO. NIO带来了non-blocking特性. 这篇文章主要 ...

  2. JAVA NIO non-blocking模式实现高并发服务器

    JAVA NIO non-blocking模式实现高并发服务器 分类: JAVA NIO2014-04-14 11:12 1912人阅读 评论(0) 收藏 举报 目录(?)[+] Java自1.4以后 ...

  3. JAVA BIO,NIO,Reactor模式总结

    传统同步阻塞I/O(BIO) 在NIO之前编写服务器使用的是同步阻塞I/O(Blocking I/O).下面是一个典型的线程池客服端服务器示例代码,这段代码在连接数急剧上升的情况下,这个服务器代码就会 ...

  4. 高性能IO之Reactor模式(转载)

    讲到高性能IO绕不开Reactor模式,它是大多数IO相关组件如Netty.Redis在使用的IO模式,为什么需要这种模式,它是如何设计来解决高性能并发的呢? 最最原始的网络编程思路就是服务器用一个w ...

  5. Java NIO学习与记录(五): 操作系统的I/O模型

    操作系统的I/O模型 在开始介绍NIO Reactor模式之前,先来介绍下操作系统的五种I/O模型,了解了这些模型,对理解java nio会有不小的帮助. 先来看下一个服务端处理一次网络请求的流程图: ...

  6. Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式

    原创文章,同步发自作者个人博客,http://www.jasongj.com/java/nio_reactor/ Java I/O模型 同步 vs. 异步 同步I/O 每个请求必须逐个地被处理,一个请 ...

  7. java NIO的多路复用及reactor模式【转载】

    关于java的NIO,以下博客总结的比较详细,适合初学者学习(http://ifeve.com/java-nio-all/) 下面的文字转载自:http://www.blogjava.net/hell ...

  8. Java进阶知识点5:服务端高并发的基石 - NIO与Reactor模式以及AIO与Proactor模式

    一.背景 要提升服务器的并发处理能力,通常有两大方向的思路. 1.系统架构层面.比如负载均衡.多级缓存.单元化部署等等. 2.单节点优化层面.比如修复代码级别的性能Bug.JVM参数调优.IO优化等等 ...

  9. 同步异步阻塞非阻塞Reactor模式和Proactor模式 (目前JAVA的NIO就属于同步非阻塞IO)

    在高性能的I/O设计中,有两个比较著名的模式Reactor和Proactor模式,其中Reactor模式用于同步I/O,而Proactor运用于异步I/O操作. 在比较这两个模式之前,我们首先的搞明白 ...

随机推荐

  1. 查看电脑安装的JDK版本

    1.输入java -d32 -version,若出现如下界面则是32位 2.java -d64 -version,因为是32位的,所以结果如下

  2. powerdesigner导出sql时报错 Generation aborted due to errors detected during the verification of the model.

    powerdesigner导出sql时报错 Generation aborted due to errors detected during the verification of the model ...

  3. java.lang.String 使用介绍

    这里我们将总结字符串相关的知识,除了总结String的API用法,同时我们还会总结一些相关的知识点,包括字符串常量池.StringBuffer.StringBuilder,以及equals和==的用法 ...

  4. 实现简单的shell sed替换功能

    通过脚本传参数可以实现替换 # -*-coding:utf-8-*- # Author:sunhao import sys f = open('yesterday','r',encoding='utf ...

  5. uiautomator2 获取APP Toast内容

    前言:appium必须是1.6以上的版本 环境(安装和安装都可以运行成功,我也不确定要不要这个): 1.抓到toast需要安装uiautomator2,安装npm:npm install -g cnp ...

  6. python 创建flask项目方法

    Flask是一个基于Python的web框架,它的设计目的是提供Web开发所需的最小功能子集. Flask与别的框架(尤其是采用其他编程语言的框架)的不同之处在于:它没有绑定诸如数据库查询或者表单处理 ...

  7. Python 静态方法

    class Person: @staticmethod # 静态方法 def yue(): print("fsadf") # 静态方法可以使用对象访问. 也可以使用类名访问. 但是 ...

  8. 区分IE版本的js代码

    function IEVersion() { var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串 var isIE = userAgen ...

  9. 【Python】数字驱动

    #练习1:打开3个网址,每个等3秒钟 urls.txt: http://www.baidu.com http://www.sogou.com http://www.sohu.com main.py: ...

  10. JavaScript循环语句-6---for语句,while语句的应用逻辑

    JavaScript循环语句 学习目标 1.掌握for语句的语法结构 2.掌握for语句的应用逻辑 for语句 语法: For(语句1:语句2:语句3){ 被执行的代码块: } 语句1:在循环(代码块 ...