目录
5、基于NIO 服务端创建Reactor设计模式的服务demo
一、reactor设计模式简介
reactor设计模式,是一种基于事件驱动的设计模式。将一个或多个客户的服务请求分离(demultiplex)和调度(dispatch)给应用程序。当请求抵达后,服务处理程序使用I/O多路复用策略,然后同步地派发这些请求至相关的请求处理程序。
1、在Reactor模式中,有5个关键的参与者
1)、描述符(handle)
由操作系统提供,用于识别每一个事件,如Socket描述符、文件描述符等。在Linux中,它用一个整数来表示。事件可以来自外部,如来自客户端的连接请求、数据等。事件也可以来自内部,如定时器事件。
2)、同步事件分离器(demultiplexer)
是一个函数,用来等待一个或多个事件的发生。调用者会被阻塞,直到分离器分离的描述符集上有事件发生。Linux的select函数是一个经常被使用的分离器。
3)、事件处理器接口(event handler)
是由一个或多个模板函数组成的接口。这些模板函数描述了和应用程序相关的对某个事件的操作。
4)、具体的事件处理器
是事件处理器接口的实现。它实现了应用程序提供的某个服务。每个具体的事件处理器总和一个描述符相关。它使用描述符来识别事件、识别应用程序提供的服务。
5)、Reactor 管理器(reactor)
定义了一些接口,用于应用程序控制事件调度,以及应用程序注册、删除事件处理器和相关的描述符。它是事件处理器的调度核心。 Reactor管理器使用同步事件分离器来等待事件的发生。一旦事件发生,Reactor管理器先是分离每个事件,然后调度事件处理器,最后调用相关的模 板函数来处理这个事件。
Reactor负责等待事件、分离事件和调度事件,实际上,Reactor管理器并没有被具体的 事件处理器调用,而是管理器调度具体的事件处理器, 图中的handle对应的是操作系统提供的句柄,例如I/O句柄,Event_Handler类持有这些句柄,‘’
2、运行流程
初始化dispatcher,注册具体事件处理器到分发器(即指定什么事件触发什么事件处理器),注册完毕后,分发器调用handle_events方法启动事件循环,并启动Synchronous Event Demultiplexer等待事件发生(阻塞等待),当有事件发生,即某个Handle变为ready状态(如TCP socket变为等待读状态),Synchronous Event Demultiplexer就会通知Initiation Dispatcher
Initiation Dispatcher根据发生的事件,将被事件源激活的Handle作为『key』来寻找并分发恰当的事件处理器回调方法。
3、总结
reactor负责监听所有的io事件,当检测到一个新的io事件时,reactor会唤醒这个事件对应的模块处理;
acceptor则负责响应socket连接请求事件,acceptor会接收请求建立连接,之后构造handler对象;
handler对象负责向reactor注册io读事件,然后从网络上读取请求并执行对应的业务逻辑,最后发回响应。
4、具体模型分类
单线程模型(I/O、非I/O业务操作都在一个线程上处理,可能会大大延迟I/O请求的响应)
工作站线程池模型(非I/O操作从Reactor线程中移出转交给工作者线程池执行)
多线程模型(mainReactor线程主要负责接收客户端的连接请求,然后将接收到的SocketChannel传递给subReactor,由subReactor来完成和客户端的通信),但是注意subReactor线程只负责完成I/O的read()或者write()操作,在读取到数据后业务逻辑的处理仍然放入到工作者线程池中完成,可避免因为read()数据量太大而导致后面的客户端连接请求得不到即时处理的情况
5、基于NIO 服务端创建Reactor设计模式的服务demo
public class Reactor implements Runnable {
public final Selector selector;
public final ServerSocketChannel server;
/**
* 创建了 ServerSocketChannel 对象,并调用 configureBlocking() 方法,配置为非阻塞模式
* 把通道绑定到制定端口,向 Selector 注册事件,并指定参数 OP_ACCEPT,即监听 accept 事件
*/
public Reactor(int port) throws IOException {
// 创建Selector对象
selector = Selector.open();
// 创建可选择通道,并配置为非阻塞模式
server = ServerSocketChannel.open();
server.configureBlocking(false);
// 绑定通道到指定端口
ServerSocket socket = server.socket();
InetSocketAddress address = new InetSocketAddress(port);
socket.bind(address);
/**
* 为了将Channel和Selector配合使用,必须将channel注册到selector上。
* 通过SelectableChannel.register()方法来实现
*/
// 向 Selector 注册该 channel
SelectionKey selectionKey = server.register(selector, Selection.OP_ACCEPT);
/**
* selectionKey.attach(theObject); 可以将一个对象或更多信息附着到 SelectionKey上,
* Object attachedObj = selectionKey.attachment(); 可以从 SelectionKey 获取附着的信息。
*/
// 利用 selectionKey 的 attach 功能绑定 Acceptor,如果有事件,触发 Acceptor
selectionKey.attach(new Acceptor(this));
}
/**
* Selector 开始监听 ,进入内部循环。在非阻塞 IO 中,内部循环模式都是遵循这种方式。
* 首先调用 select() 方法,该方法会阻塞,直到至少有一个事件发生,
* 然后使用 selectedKeys() 方法获取发生事件的 SelectionKey,然后使用迭代器进行循环
*/
@Override
public void run() {
try {
while (!Thread.interrupted()) {
// 该调用会阻塞,直到至少有一个事件发生
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
dispatch(key);
}
selected.clear();
}
} catch (IOException ex) {
/* ... */
}
}
/**
* 运行 Acceptor
*/
void dispatch(SelectionKey key) {
Acceptor acceptor = (Acceptor) key.attachment();
Runnable r = (Runnable)(acceptor );
if (r != null) {
r.run();
}
}
}
public class Acceptor implements Runnable {
private Reactor reactor;
public Acceptor(Reactor reactor) {
this.reactor=reactor;
}
/**
* 接收请求
*/
@Override
public void run() {
try {
ServerSocketChannel server = reactor.server;
SocketChannel channel = server.accept();
if(channel != null) {
// 调用 Handler 来处理 channel
new SocketReadHandler(reactor.selector, channel);
}
} catch (IOException e) {
/* ... */
}
}
}
public class SocketReadHandler implements Runnable {
private Selector selector;
private SocketChannel channel;
public SocketReadHandler(Selector selector, SocketChannel channel) throws IOException {
this.selector = selector;
this.channel = channel;
channel.configureBlocking(false);
/**
* 将新接入的客户端连接注册到 Reactor 线程的多路复用器上
* 监听读操作位,用来读取客户端发送的网络消息
*/
SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_READ);
// 将 SelectionKey 绑定为本 Handler 有事件触发时,将调用本类的 run 方法。
selectionKey.attach(this);
}
/**
* 处理读取客户端发来的信息的事件
*/
@Override
public void run() {
// 创建读取的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
int count = channel.read(buffer);
if (count > 0) {
buffer.flip():
CharBuffer charBuffer = decoder.decode(buffer);
String msg = charBuffer.toString();
// ...
SelectionKey selectionKey = channel.register(selector, SelectionKey.OP_WRITE);
selectionKey.attach(name);
}
} catch (IOException e) {
/* ... */
}
buffer.clear();
}
}
二、Reactor与Proactor设计模式对比
1、Reactor与Proactor的概念
两种I/O多路复用模式:Reactor和Proactor
一般地,I/O多路复用机制都依赖于一个事件多路分离器(Event Demultiplexer)。分离器对象可将来自事件源的I/O事件分离出来,并分发到对应的read/write事件处理器(Event Handler)。开发人员预先注册需要处理的事件及其事件处理器(或回调函数);事件分离器负责将请求事件传递给事件处理器。两个与事件分离器有关的模式是Reactor和Proactor。Reactor模式采用同步IO,而Proactor采用异步IO。
在Reactor中,事件分离器负责等待文件描述符或socket为读写操作准备就绪,然后将就绪事件传递给对应的处理器,最后由处理器负责完成实际的读写工作。
而在Proactor模式中,处理器--或者兼任处理器的事件分离器,只负责发起异步读写操作。IO操作本身由操作系统来完成。传递给操作系统的参数需要包括用户定义的数据缓冲区地址和数据大小,操作系统才能从中得到写出操作所需数据,或写入从socket读到的数据。事件分离器捕获IO操作完成事件,然后将事件传递给对应处理器。比如,在windows上,处理器发起一个异步IO操作,再由事件分离器等待IOCompletion事件。典型的异步模式实现,都建立在操作系统支持异步API的基础之上,我们将这种实现称为“系统级”异步或“真”异步,因为应用程序完全依赖操作系统执行真正的IO工作。
举个例子,将有助于理解Reactor与Proactor二者的差异,以读操作为例(类操作类似)。
在Reactor中实现读:
- 注册读就绪事件和相应的事件处理器
- 事件分离器等待事件
- 事件到来,激活分离器,分离器调用事件对应的处理器。
- 事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。
在Proactor中实现读:
- 处理器发起异步读操作(注意:操作系统必须支持异步IO)。在这种情况下,处理器无视IO就绪事件,它关注的是完成事件。
- 事件分离器等待操作完成事件
- 在分离器等待过程中,操作系统利用并行的内核线程执行实际的读操作,并将结果数据存入用户自定义缓冲区,最后通知事件分离器读操作完成。
- 事件分离器呼唤处理器。
- 事件处理器处理用户自定义缓冲区中的数据,然后启动一个新的异步操作,并将控制权返回事件分离器。
可以看出,两个模式的相同点,都是对某个IO事件的事件通知(即告诉某个模块,这个IO操作可以进行或已经完成)。在结构上,两者也有相同点:demultiplexor负责提交IO操作(异步)、查询设备是否可操作(同步),然后当条件满足时,就回调handler;不同点在于,异步情况下(Proactor),当回调handler时,表示IO操作已经完成;同步情况下(Reactor),回调handler时,表示IO设备可以进行某个操作(can read or can write)。
2、简单对比
使用Proactor框架和Reactor框架都可以极大的简化网络应用的开发,但它们的重点却不同。
Reactor框架中用户定义的操作是在实际操作之前调用的。比如你定义了操作是要向一个SOCKET写数据,那么当该SOCKET可以接收数据的时候,你的操作就会被调用;而Proactor框架中用户定义的操作是在实际操作之后调用的。比如你定义了一个操作要显示从SOCKET中读入的数据,那么当读操作完成以后,你的操作才会被调用。
Proactor和Reactor都是并发编程中的设计模式。在我看来,他们都是用于派发/分离IO操作事件的。这里所谓的IO事件也就是诸如read/write的IO操作。"派发/分离"就是将单独的IO事件通知到上层模块。两个模式不同的地方在于,Proactor用于异步IO,而Reactor用于同步IO。
参考:
https://segmentfault.com/a/1190000002715832
http://jenny-run.iteye.com/blog/2243781
https://www.cnblogs.com/joqk/p/3972402.html
https://blog.csdn.net/u010168160/article/details/53019039
https://www.jianshu.com/p/af202026ffc5
https://blog.csdn.net/linxcool/article/details/7771952
http://www.cnblogs.com/dawen/archive/2011/05/18/2050358.html
http://www.linkedkeeper.com/12.html