NIO及Reactor模式
关于Nio
Java NIO即Java Non-blocking IO(Java非阻塞I/O),是Jdk1.4之后增加的一套操作I/O工具包,又被叫做Java New IO。
Nio要去解决的问题
Nio要解决的问题网上的解释一大堆,诸如银行取号、餐厅点餐等等。这些列子就不再具体地重复了,实际上就是为了使用现有的资源提供更高的生产效率。
这让我想起了以前学习政治的时候课本里的故事,资本家为了赚取更多的剩余价值往往会想方设法提高生产效率。如何提高呢?举个简单例子,一个汽车生产厂商有若干条生产线(一条生产线负责汽车制造的所有环节),每个生产线都有相同的工人数目,每个工人都负责一个生产环节,也就是说生产发动机和生产轮胎的工人数目是一样的,但是很明显生产发动机需要的时间肯定比轮胎要长很多,那么在每一条生产线上生产发动机的那个工人往往满负荷工作,而生产轮胎的工人却很闲,这样生产效率很低。因此厂家打破了这种一条生产线生产汽车所有环节的模式,改为一个汽车零部件一条生产线,那么在发动机生产线雇佣的工人数目一定多于轮胎生产线,这样每条生产线的工人都不会闲着,通过资源的合理分配最大化利用了工人的价值,提高了生产效率,赚取了剩余价值。
而如何通过资源合理分配来提高生产效率就是nio在计算机io领域要解决的问题。
同步/异步、阻塞/非阻塞
同步和异步是针对应用程序和内核的交互而言的:
同步指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪;异步是指用户进程触发IO操作以后便开始做自己的事情,而当IO操作已经完成的时候会得到IO完成的通知。
阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式:
阻塞方式下读取或者写入函数将一直等待;非阻塞方式下,读取或者写入函数会立即返回一个状态值。
Reactor(反应器)模式:
Reactor模式应用于同步IO场景,事件分离者等待某个事件的发生或者可操作状态的变化(比如文件描述符可读写,或者是socket可读写),事件分离者就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。

在Reactor模式中有如下几个角色:
1. Reactor
事件分离者,其核心是Selector,负责响应IO事件,一旦发生,广播发送给相应的Handler去处理。具体为一个Selector和一个ServerSocketChannel。ServerSocketChannel注册到Selector中,获取的SelectionKey绑定一个Acceptor(也可以理解为一个Handler)。
参考代码如下:
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set; /**
* 反应器模式 用于解决多用户访问并发问题
*/
public class Reactor implements Runnable {
public final Selector selector;
public final ServerSocketChannel serverSocketChannel; public Reactor(int port) throws IOException {
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(InetAddress.getLocalHost(), port);
serverSocketChannel.socket().bind(inetSocketAddress);
serverSocketChannel.configureBlocking(false); // 向selector注册该channel
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 利用selectionKey的attache功能绑定Acceptor 如果有事情,触发Acceptor
selectionKey.attach(new Acceptor(this));
} @Override
public void run() {
try {
while (!Thread.interrupted()) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
// Selector如果发现channel有OP_ACCEPT或READ事件发生,下列遍历就会进行。
while (it.hasNext()) {
// 来一个事件 第一次触发一个accepter线程,SocketReadHandler
SelectionKey selectionKey = it.next();
dispatch(selectionKey);
selectionKeys.clear();
}
}
} catch (IOException e) {
e.printStackTrace();
}
} /**
* 运行Acceptor或SocketReadHandler
*
* @param key
*/
void dispatch(SelectionKey key) {
Runnable r = (Runnable) (key.attachment());
if (r != null) {
r.run();
}
} }
2. Acceptor
也可以理解为一个Handler,这个Handler只负责创建具体处理IO请求的Handler,如果Reactor广播时SelectionKey创建一个Handler负责绑定相应的SocketChannel到Selector中。下次再次有IO事件时会调用对用的Handler去处理。
参考代码如下:
import java.io.IOException;
import java.nio.channels.SocketChannel; public class Acceptor implements Runnable {
private Reactor reactor; public Acceptor(Reactor reactor) {
this.reactor = reactor;
} @Override
public void run() {
try {
SocketChannel socketChannel = reactor.serverSocketChannel.accept();
if (socketChannel != null){
// 调用Handler来处理channel
new SocketReadHandler(reactor.selector, socketChannel);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. Handler
具体的事件处理者,例如ReadHandler、SendHandler,ReadHandler负责读取缓存中的数据,然后再调用一个工作处理线程去处理读取到的数据。具体为一个SocketChannel,Acceptor初始化该Handler时会将SocketChannel注册到Reactor的Selector中,同时将SelectionKey绑定该Handler,这样下次就会调用本Handler。
参考代码如下:
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel; public class SocketReadHandler implements Runnable {
private SocketChannel socketChannel; public SocketReadHandler(Selector selector, SocketChannel socketChannel) throws IOException {
this.socketChannel = socketChannel;
socketChannel.configureBlocking(false); SelectionKey selectionKey = socketChannel.register(selector, 0); // 将SelectionKey绑定为本Handler 下一步有事件触发时,将调用本类的run方法。
// 参看dispatch(SelectionKey key)
selectionKey.attach(this); // 同时将SelectionKey标记为可读,以便读取。
selectionKey.interestOps(SelectionKey.OP_READ);
selector.wakeup();
} /**
* 处理读取数据
*/
@Override
public void run() {
ByteBuffer inputBuffer = ByteBuffer.allocate(1024);
inputBuffer.clear();
try {
socketChannel.read(inputBuffer);
// 激活线程池 处理这些request
// requestHandle(new Request(socket,btt));
} catch (IOException e) {
e.printStackTrace();
}
}
}
综上所述,我们可以把上述角色的细化理解为将一份工作的工作量细化了,或者说重新分配了一下,专人做专事,这样最大化利用了每个角色的价值,提高工作效率。
NIO及Reactor模式的更多相关文章
- Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式
原创文章,同步发自作者个人博客,http://www.jasongj.com/java/nio_reactor/ Java I/O模型 同步 vs. 异步 同步I/O 每个请求必须逐个地被处理,一个请 ...
- nio的reactor模式
转自:http://blog.csdn.net/it_man/article/details/38417761 线程状态转换图 就是非阻塞IO 采用多路分发方式举个例子吧,你服务器做一个聊天室,按照以 ...
- Java进阶知识点5:服务端高并发的基石 - NIO与Reactor模式以及AIO与Proactor模式
一.背景 要提升服务器的并发处理能力,通常有两大方向的思路. 1.系统架构层面.比如负载均衡.多级缓存.单元化部署等等. 2.单节点优化层面.比如修复代码级别的性能Bug.JVM参数调优.IO优化等等 ...
- Java I/O模型从BIO到NIO和Reactor模式(转)
原创文章,转载请务必将下面这段话置于文章开头处(保留超链接).本文转发自技术世界,原文链接 http://www.jasongj.com/java/nio_reactor/ Java I/O模型 同步 ...
- 原生JDK网络编程- NIO之Reactor模式
“反应”器名字中”反应“的由来: “反应”即“倒置”,“控制逆转”,具体事件处理程序不调用反应器,而向反应器注册一个事件处理器,表示自己对某些事件感兴趣,有时间来了,具体事件处理程序通过事件处理器对某 ...
- 【死磕 NIO】— Reactor 模式就一定意味着高性能吗?
大家好,我是大明哥,我又来了. 为什么是 Reactor 一般所有的网络服务,一般分为如下几个步骤: 读请求(read request) 读解析(read decode) 处理程序(process s ...
- NIO使用Reactor模式遇到的问题
关于Reactor模式,不再多做介绍,推荐Doug Lea大神的教程:Java 可扩展的IO 本来在Reactor的构造方法中完成一系列操作是没有问题的: public class Reactor i ...
- 知识联结梳理 : I/O多路复用、EPOLL(SELECT/POLL)、NIO、Event-driven、Reactor模式
为了形成一个完整清晰的认识,将概念和关系梳理出来,把坑填平. I/O多路复用 I/O多路复用主要解决传统I/O单线程阻塞的问题.它通过单线程管理多个FD,当监听的FD有状态变化的时候的,调用回调函数, ...
- Reactor模式和NIO(转载二)
本文可看成是对Doug Lea Scalable IO in Java一文的翻译. 当前分布式计算 Web Services盛行天下,这些网络服务的底层都离不开对socket的操作.他们都有一个共同的 ...
随机推荐
- Gradle 下载
gradle各个版本的下载地址 :http://services.gradle.org/distributions 下载完后的gradle怎么使用呢? //下面是网上提供的方法,仅供学习参考 在 ht ...
- 多线程之任务: Task 基础, 多任务并行执行, 并行运算(Parallel)
Task - 基于线程池的任务(在 System.Threading.Tasks 命名空间下) 多 Task 的并行执行 Parallel - 并行计算(在 System.Threading.Task ...
- linux安装adb
本文只针对centOS6.8,其他版本未测试 1.下载adb包 下载android sdk for linux(http://tools.android-studio.org/index.php/sd ...
- BZOJ 后缀自动机四·重复旋律7
后缀自动机四·重复旋律7 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 神奇的 ...
- C# 扩展系统类方法
1.声明扩展方法的步骤:类必须是static,方法是static 2.第一个参数是被扩展的对象,前面标注this 3.使用扩展方法的时候必须保证扩展方法类已经在当前代码中using 例子:using ...
- JavaScript系列文章:自动类型转换-续
在上一篇文章中,我们详细讲解了JavaScript中的自动类型转换,由于篇幅限制,没能覆盖到所有的转换规则,这次准备详细讲解一下. 上次我们提到了对象类型参与运算时转换规则: 1). 在逻辑环境中执行 ...
- UICollectionViewCell 网格显示数据
using System; using System.Collections.Generic; using Foundation; using UIKit; namespace ddd { publi ...
- MySQL外键约束
mysql的五种约束 1.PRIMARY KEY 2.UNIQUE KEY 3.NOT NULL 4.DEFAULT 5.FOREIGN KEY 其中外键约束的使用用法 CREATE TABLE te ...
- STL的使用
Vector:不定长数组 Vector是C++里的不定长数组,相比传统数组vector主要更灵活,便于节省空间,邻接表的实现等.而且它在STL中时间效率也很高效:几乎与数组不相上下. #include ...
- iBatis.net 循环iterate,没有foreach
3.9.4. Iterate Element This tag will iterate over a collection and repeat the body content for each ...