Reactor反应器模式
什么是reactor反应器模式
反应器模式由Reactor反应器线程、Handler处理器两个角色组成:
- Reactor反应器线程:负责响应IO事件,并且分发到Handler处理器。
- Handler处理器:非阻塞的执行业务处理逻辑。
反应器模式从简单到复杂,有很多版本,根据上面的定义,仅仅是最简单的一个版本。如果要了解反应器模式,还要从OIO编程开始说起。
while(true)}{
socket = accept();//阻塞,接收链接
handle(socket);//读取数据,处理业务,返回结果
}
这种方式最大的问题是:前一个网络链接的业务处理还没完成,那么后面的连接请求没法被接受,于是后面的请求会被阻塞住,服务器的吞吐量就低了。
为了解决连接阻塞问题,出现了一个Connection Per Thread模式。一个线程处理一个连接。
class ConnectionPerThread implements Runnable{
public void run(){
try{
ServerSocket serverSocket = new ServerSocket(2222);
while(!Thread.interrupted){
Socket socket = serverSocket.accpet;
//接收一个连接后,为socket连接,新建一个线程
Handler handler = new Handler(socket);
new Thread(handler).start();
}
}
}
static class Handler implements Runable{
final Socket socket;
Handler(Socket s){
this.socket = s;
}
public void run(){
while(true){
try{
byte[] input = new byte[1024];
//读取数据
socket.getInputStream().read(input);
byte[] output = new byte[1024];
socket.getOutputStream().write(output);
}catcah(Exception e){
//异常处理
}
}
}
}
}
对于每一个新的网络链接都分配一个线程。每个线程都独自处理自己负责的输入和输出。服务器的监听线程也是独立,任务的socket连接的输入和输出处理,不会阻塞到后面新socket连接的监听和建立。
Connection Per Thread模式的优点:解决了新连接被严重阻塞的问题。
一个问题:一个线程同时负责处理多个socket连接的逻辑,可以吗?当然可以,但是传统的OIO编程中每个socket的IO读写操作都是阻塞的。在同一时刻,一个线程里只能处理一个socket,前一个socket被阻塞了,后面连接的IO操作是无法被并发执行的。
Connection Per Thread缺点:对应于大量的链接,需要耗费大量的线程资源,对线程资源要求太高。在系统中,线程是昂贵的系统资源。如果线程数太多,系统无法承受。而且,线程的反复创建、销毁、线程的切换也需要代价。
使用Reactor反应器模式可以解决上述问题。用反应器模式对线程的数量进行控制,做到一个线程处理大量的连接。
单线程的Reactor反应器模式
反应器模式的两个重要组件:
Reactor反应器:负责查询IO事件,当检测到一个IO事件,将其发送给相应的Handler处理器去处理。这里的IO事件,就是NIO中选择器监控的通道IO事件。
Handler处理器:与IO事件绑定,负责IO事件的处理。完成真正的连接建立、通道的读取、处理业务逻辑、负责将结果写出到通道等。
基于java NIO实现简单的单线程版本的反应器模式,的核心方法是attach(Object o)和attachment(),
两个方法分别是SelecttionKey选择键添加附件和取出附件,附件就是Handle处理器,当IO事件发生,选择键被select方法选到,就可以将附件取出,完成业务处理。
class Reactor implements Runable{
Selector selector;
ServerSocketChannel serverSocketChannel;
EchoServerReactor() throws IOException{
// 打开选择器、连接监听通道
。。。
//注册serverSocket的accpet事件
SelectionKey sk = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
//将新连接处理器作为附件,绑定到sk选择器
sk.attach(new AcceptorHandler());
}
public void run(){
try{
while(!Thread.interrupted()){
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while(it.hasNext()){
//反应器负责dispatch收到的事件
SelectionKey sk = it.next();
dispatch(sk);
}
selected.clear;
}
}catch(IOException ex){}
}
//反应器的分发方法
void dispatch(SelectionKey k){
Runable handler = (Runable) (k.attachment());
if(handler!=null){
handler.run();
}
}
//新连接处理器
class AcceptorHandler implements Runable{
public void run(){
//接收新连接
//需要为新连接,创建一个输入输出的handler处理器
}
}
}
上面代码中,设计了一个Handler处理器,叫做AcceptorHandler处理器。在注册serverSocket服务监听连接的接收事件之后,创建一个AcceptorHandler新连接处理器的实例,作为附件,被设置到了SelectionKey中。
当连接事件发生后,取出了之前attach到SelectionKey中的Handler业务处理器,进行socket的各种IO处理。
AcceptorHandler处理器的两大职责:一是接收新连接,二是在为新连接创建一个输入输出的Handler处理器,叫做IOHandler。
IOHandler就是负责socket的数据输入、业务处理,结果输出。
class IOHandler implements Runable{
final SocketChannel channel;
final SelectionKey sk;
IOHandler(Selector selector,SocketChannel c) throws IOException{
channel = c;
c.configureBlocking(false);
//取得选择键
sk = channel.register(selector,0);
//设置附件
sk.attach(this);
//注册读写就绪事件
sk.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
public void run(){
//处理输入和输出
}
}
在IOHandler的构造器中,有两点比较重要:
- 将新的SocketChannel传输通道,注册到了反应器Reactor类的同一个选择器中。
- Chanel传输通道注册完成后,将IOHandler自身作为附件,attach到了选择键中。这样,在Reactor类分发事件时,能执行到IOHandler的run方法。
代码示例
package com.io.test;
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.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* @ClassName EchoServerReactor
* @Description TODO
* @Author djl
* @Date 2020-02-08 21:04
* @Version 1.0
*/
public class EchoServerReactor implements Runnable {
Selector selector;
ServerSocketChannel serverSocketChannel;
EchoServerReactor() throws IOException {
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 2222));
serverSocketChannel.configureBlocking(false);
SelectionKey sk = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
sk.attach(new AcceptorHandler());
}
@Override
public void run() {
try {
while (!Thread.interrupted()) {
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while (it.hasNext()) {
SelectionKey sk = (SelectionKey) it.next();
dispatch(sk);
}
selected.clear();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void dispatch(SelectionKey sk) {
Runnable handle = (Runnable) sk.attachment();
if (handle != null) {
handle.run();
}
}
class AcceptorHandler implements Runnable {
@Override
public void run() {
try {
SocketChannel channel = serverSocketChannel.accept();
if (channel != null) {
new EchoHandler(selector, channel);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
new Thread(new EchoServerReactor()).start();
}
}
package com.io.test;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
/**
* @ClassName EchoHandler
* @Description TODO
* @Author djl
* @Date 2020-02-18 11:58
* @Version 1.0
*/
public class EchoHandler implements Runnable {
final SocketChannel channel;
final SelectionKey sk;
final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
final int RECIVING = 0, SENDING = 1;
int state = RECIVING;
public EchoHandler(Selector selector, SocketChannel channel) throws IOException {
this.channel = channel;
channel.configureBlocking(false);
sk = channel.register(selector, 0);
sk.attach(this);
sk.interestOps(SelectionKey.OP_READ);
selector.wakeup();
}
@Override
public void run() {
try {
if (state == SENDING) {
channel.write(byteBuffer);
byteBuffer.clear();
sk.interestOps(SelectionKey.OP_READ);
state = RECIVING;
} else if (state == RECIVING) {
int length = 0;
while ((length = channel.read(byteBuffer)) > 0) {
System.out.println(new String(byteBuffer.array(),0,length));
}
byteBuffer.flip();
sk.interestOps(SelectionKey.OP_WRITE);
state = SENDING;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端示例
package com.io.test;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* @ClassName NioDiscardClient
* @Description TODO
* @Author djl
* @Date 2020-02-09 11:15
* @Version 1.0
*/
public class NioDiscardClient {
public static void startClient() throws IOException {
InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(),2222);
SocketChannel socketChannel = SocketChannel.open(address);
socketChannel.configureBlocking(false);
while (!socketChannel.finishConnect()){
}
System.out.println("connect success");
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("hello world".getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
socketChannel.shutdownOutput();
socketChannel.close();
}
public static void main(String[] args) throws IOException {
startClient();
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。