也谈Reactor模式
何谓Reactor模式?它是实现高性能IO的一种设计模式。网上资料有很多,有些写的也很好,但大多不知其所以然。这里博主按自己的思路简单介绍下,有不对的地方敬请指正。
BIO
Java1.4(2002年)以前,IO都是Blocking的,也就是常说的BIO,它在等待请求、读、写(返回)三个环节都是阻塞的。在等待请求阶段,系统无法知道请求何时到达,因此需要一个主线程一直守着,当有请求进来时,将请求分发给读写线程。如图:

代码如下:
ExecutorService executor = Excutors.newFixedThreadPollExecutor(100);//线程池
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(8088);
while(!Thread.currentThread.isInturrupted()){//主线程死循环等待新连接到来
Socket socket = serverSocket.accept();
executor.submit(new ConnectIOnHandler(socket));//为新的连接创建新的线程
}
class ConnectIOnHandler extends Thread{
private Socket socket;
public ConnectIOnHandler(Socket socket){ this.socket = socket; }
public void run(){
while(!Thread.currentThread.isInturrupted()&&!socket.isClosed()){//死循环处理读写事件
String someThing = socket.read()....//读取数据
if(someThing!=null){
......//处理数据
socket.write()....//写数据
}
}
}
需知,请求进来(accept),并不表示数据马上达到了,可能隔一段时间才会传进来,这个时候socket.read()也是一直阻塞的状态。socket.write()也同理,当向磁盘或其它socket写数据时,也要等对方准备好才能写入,在对方准备阶段,socket.write()也是阻塞的。这两个环节可能的无效阻塞导致读写线程的低效。
NIO
Java1.4开始,引入了NIO。NIO有三个概念:Selector、Buffer、Channel。与BIO的区别是,请求进来后,并不会马上分派IO线程,而是依靠操作系统底层的多路复用机制(select/poll/epoll等),在监听到socket读写就绪之后,再分配IO线程(实际可由当前线程[使用Buffer和Channel]直接读写,因为读写本身的效率很高),这就避免了线程等待。且与BIO多线程方式相比,使用I/O多路复用技术,系统不必创建和维护庞大的线程池,从而大大减小了开销。这部分工作是NIO的核心,由Selector负责,本质上是多路复用的Java封装。而Buffer和Channel又封装了一层socket的读写,应该为的是将IO与业务代码彻底分离。以下图示为本人理解:

如图示,与BIO中监听线程职责不同,Selector监听的不只是连接请求,还有读写就绪事件,当某个事件发生时,即通知注册了该事件的Channel,由Channel操作socket读写Buffer。虚线表示需要具体的NIO框架或业务代码自己处理,比如Channel如何注册以及注册何种事件,Channel处理IO的方式(如在当前线程处理还是新开线程,若新开线程,则可看作是AIO模式)等。NIO只是提供了一套机制,具体使用还是需要编程实现(Reactor模式就是OO的一种实现)。
示例代码(摘自Java NIO详解)
服务端:
package cn.blog.test.NioTest; import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set; public class MyNioServer {
private Selector selector; //创建一个选择器
private final static int port = 8686;
private final static int BUF_SIZE = 10240; private void initServer() throws IOException {
//创建通道管理器对象selector
this.selector=Selector.open(); //创建一个通道对象channel
ServerSocketChannel channel = ServerSocketChannel.open();
channel.configureBlocking(false); //将通道设置为非阻塞
channel.socket().bind(new InetSocketAddress(port)); //将通道绑定在8686端口 //将上述的通道管理器和通道绑定,并为该通道注册OP_ACCEPT事件
//注册事件后,当该事件到达时,selector.select()会返回(一个key),如果该事件没到达selector.select()会一直阻塞
SelectionKey selectionKey = channel.register(selector,SelectionKey.OP_ACCEPT); while (true){ //轮询
selector.select(); //这是一个阻塞方法,一直等待直到有数据可读,返回值是key的数量(可以有多个)
Set keys = selector.selectedKeys(); //如果channel有数据了,将生成的key访入keys集合中
Iterator iterator = keys.iterator(); //得到这个keys集合的迭代器
while (iterator.hasNext()){ //使用迭代器遍历集合
SelectionKey key = (SelectionKey) iterator.next(); //得到集合中的一个key实例
iterator.remove(); //拿到当前key实例之后记得在迭代器中将这个元素删除,非常重要,否则会出错
if (key.isAcceptable()){ //判断当前key所代表的channel是否在Acceptable状态,如果是就进行接收
doAccept(key);
}else if (key.isReadable()){
doRead(key);
}else if (key.isWritable() && key.isValid()){
doWrite(key);
}else if (key.isConnectable()){
System.out.println("连接成功!");
}
}
}
} public void doAccept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
System.out.println("ServerSocketChannel正在循环监听");
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(key.selector(),SelectionKey.OP_READ);
} public void doRead(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);
long bytesRead = clientChannel.read(byteBuffer);
while (bytesRead>0){
byteBuffer.flip();
byte[] data = byteBuffer.array();
String info = new String(data).trim();
System.out.println("从客户端发送过来的消息是:"+info);
byteBuffer.clear();
bytesRead = clientChannel.read(byteBuffer);
}
if (bytesRead==-1){
clientChannel.close();
}
} public void doWrite(SelectionKey key) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);
byteBuffer.flip();
SocketChannel clientChannel = (SocketChannel) key.channel();
while (byteBuffer.hasRemaining()){
clientChannel.write(byteBuffer);
}
byteBuffer.compact();
} public static void main(String[] args) throws IOException {
MyNioServer myNioServer = new MyNioServer();
myNioServer.initServer();
}
}
客户端:
package cn.blog.test.NioTest; import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator; public class MyNioClient {
private Selector selector; //创建一个选择器
private final static int port = 8686;
private final static int BUF_SIZE = 10240;
private static ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE); private void initClient() throws IOException {
this.selector = Selector.open();
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress(port));
clientChannel.register(selector, SelectionKey.OP_CONNECT);
while (true){
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
iterator.remove();
if (key.isConnectable()){
doConnect(key);
}else if (key.isReadable()){
doRead(key);
}
}
}
} public void doConnect(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
if (clientChannel.isConnectionPending()){
clientChannel.finishConnect();
}
clientChannel.configureBlocking(false);
String info = "服务端你好!!";
byteBuffer.clear();
byteBuffer.put(info.getBytes("UTF-8"));
byteBuffer.flip();
clientChannel.write(byteBuffer);
//clientChannel.register(key.selector(),SelectionKey.OP_READ);
clientChannel.close();
} public void doRead(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
clientChannel.read(byteBuffer);
byte[] data = byteBuffer.array();
String msg = new String(data).trim();
System.out.println("服务端发送消息:"+msg);
clientChannel.close();
key.selector().close();
} public static void main(String[] args) throws IOException {
MyNioClient myNioClient = new MyNioClient();
myNioClient.initClient();
}
}
在早期的JDK1.4和1.5 update10版本之前,Selector基于select/poll模型实现,是基于IO复用技术的非阻塞IO,不是异步IO。在JDK1.5 update10和linux core2.6以上版本,sun优化了Selctor的实现,底层使用epoll替换了select/poll。另据说Buffer指向的并非堆内内存,NIO使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native 堆中来回复制数据。
NIO的实现解析可参看:深入浅出NIO Socket实现机制
Reactor模式
NIO为实现Reactor模式提供了基础,上面的NIO图示其实就是Reactor模式的雏形,只是Reactor以OO的方式抽象出了几个概念,使得职责划分更加明确。
- Reactor:Reactor是IO事件的派发者,对应NIO的Selector;
- Acceptor:Acceptor接受client连接,建立对应client的Handler,并向Reactor注册此Handler,对应NIO中注册Channel和事件触发时的判断分支(上述NIO服务端示例代码的38-46行);
- Handler:IO处理类,对应NIO中Channel[使用socket]操作Buffer的过程。
基于上述三个角色画出Reactor模式图如下:

如此,Reactor模式便非常清晰地展现在我们眼前。那么业务线程如何与Reactor交互呢?由前文所知,数据存取于Buffer,具体操作由Handler负责。socket.read()将数据读入Buffer,需要一种机制将Buffer引用推送给业务线程;同样,业务线程返回的数据需要写入Buffer,按Reactor模式,写入后还需要注册write事件,socket可写后write()。如果直接调用的话,至少Handler和业务代码会耦合在一起,常见的解耦方式是定义接口,或使用消息中间件。
其它
话说回来,由于相对短暂的历史以及相对封闭的环境,.Net社区缺少很多概念的演化、探究和讨论,这也导致了.Neter们这些概念的缺失。虽然从语言层面上来说,C#和Java大同小异,前者甚至一定程度的有语法上的便利,然而只有认识到了其背后的思想和模式,才能真正用好这门语言,这就是.Neter需要了解Java及其历史的原因,毕竟.Net一开始就是参照着Java来的。
比如.Net里的堆栈概念,就算一些经典书籍都没有非常深入的说明,而Java方面的资料就很多了,参看深入理解JVM—JVM内存模型
其它参考资料:
转载请注明本文出处:https://www.cnblogs.com/newton/p/9776821.html
也谈Reactor模式的更多相关文章
- Redis与Reactor模式
Redis与Reactor模式 Jan 9, 2016 近期看了Redis的设计与实现,这本书写的还不错,看完后对Redis的理解有非常大的帮助. 另外,作者整理了一份Redis源代码凝视,大家能够c ...
- Reactor 模式的简单实现
Reactor 模式简单实现 在网上有部分文章在描述Netty时,会提到Reactor.这个Reactor到底是什么呢?为了搞清楚Reactor到底是什么鬼,我写了一个简单的Demo,来帮助大家理解他 ...
- NIO及Reactor模式
关于Nio Java NIO即Java Non-blocking IO(Java非阻塞I/O),是Jdk1.4之后增加的一套操作I/O工具包,又被叫做Java New IO. Nio要去解决的问题 N ...
- Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式
原创文章,同步发自作者个人博客,http://www.jasongj.com/java/nio_reactor/ Java I/O模型 同步 vs. 异步 同步I/O 每个请求必须逐个地被处理,一个请 ...
- 什么是Reactor模式,或者叫反应器模式
Reactor这个词译成汉语还真没有什么合适的,很多地方叫反应器模式,但更多好像就直接叫reactor模式了,其实我觉着叫应答者模式更好理解一些.通过了解,这个模式更像一个侍卫,一直在等待你的召唤,或 ...
- 知识联结梳理 : I/O多路复用、EPOLL(SELECT/POLL)、NIO、Event-driven、Reactor模式
为了形成一个完整清晰的认识,将概念和关系梳理出来,把坑填平. I/O多路复用 I/O多路复用主要解决传统I/O单线程阻塞的问题.它通过单线程管理多个FD,当监听的FD有状态变化的时候的,调用回调函数, ...
- Reactor模式通俗解释
Reactor这个词译成汉语还真没有什么合适的,很多地方叫反应器模式,但更多好像就直接叫reactor模式了,其实我觉着叫应答者模式更好理解一些.通过了解,这个模式更像一个侍卫,一直在等待你的召唤,或 ...
- ACE - Reactor模式源码剖析及具体实现(大量源码慎入)
原文出自http://www.cnblogs.com/binchen-china,禁止转载. 在之前的文章中提到过Reactor模式和Preactor模式,现在利用ACE的Reactor来实现一个基于 ...
- Reactor模式与Proactor模式
该文章总结了网上资源对这两种模式的描述 原文地址:http://www.cnblogs.com/dawen/archive/2011/05/18/2050358.html 1.标准定义 两种I/O多路 ...
随机推荐
- 关于css盒模型
在css中,width和height指的是内容区域的宽度和高度.增加内边距,边框和外边距不会影响内容区域的尺寸,但是会增加元素框的总尺寸.假设框的每个边上有10个像素的外边距和5像素的内边距,如果希望 ...
- 读《图解HTTP》有感-(了解web及网络基础)
写在前面 <图解HTTP>是由上野宣先生著,于均良先生译 闲暇之余!写写博文甚是高兴.如有不准确,望各位斧正.共同学习! 正文 HTTP协议是什么?能做什么? HTTP(超文本传输协议)实 ...
- sql中关于存在就不做操作的代码块
前言: 在开发中,经常会对数据库表进行新增修改操作,那么如果表中的属性信息已然存在啦!就没必要去做重复的操作了... 代码块 BEGIN SELECT "COUNT"(*) int ...
- Design5:SQL Server 文件和文件组
数据库是数据的仓库,用于存储数据,而存储数据需要媒介,现在的存储媒介,最常用的是硬盘,土豪一点的服务器使用固态硬盘(SSD),特殊用途的服务器使用内存.数据库最常用的存储文件是数据文件和日志文件,数据 ...
- Maven学习(七)-- 使用Maven构建多模块项目
摘自:http://www.cnblogs.com/xdp-gacl/p/4242221.html 在平时的Javaweb项目开发中为了便于后期的维护,我们一般会进行分层开发,最常见的就是分为doma ...
- Junit-4.1.2 @Test 使用
学习使用Junit-4.1.2 @Test来做单元测试 1.下载jar包 下载junit-4.12.jar 下载hamcrest-core-1.3.jar 2.在External Libraries中 ...
- 5.两分钟让你明白app后端有啥用
app后端,也称为app后台,称呼不一样,但指的是同一个东西. 我一直都以app后端有啥用这个问题不用解释.但在网络上,有准备进行app创业的网友(是从传统行业过来的)问过这个问题,我这里就以app后 ...
- 玩转spring mvc(四)---在spring MVC中整合JPA
关于在Spring MVC中整合JPA是在我的上一篇关于spring mvc基本配置基础上进行的,所以大家先参考一下我的上一篇文章:http://blog.csdn.net/u012116457/ar ...
- 某校高中生利用Python,巧妙获取考试成绩,看到成绩后无言以对!
Python是非常有吸引力的编程语言,学习Python的不是帅哥就是美女.为什么这么说呢?因为我和我的女朋友都是学习Python认识的,小编肯定是帅哥,不用去怀疑,而且我眼光特高. 给大伙讲一个故事, ...
- BZOJ_4269_再见Xor_线性基
BZOJ_4269_再见Xor_线性基 Description 给定N个数,你可以在这些数中任意选一些数出来,每个数可以选任意多次,试求出你能选出的数的异或和的最大值和严格次大值. Input 第一行 ...