Reactor 模式,通过一个或多个输入同时传递给服务处理器的模式 , 服务器端程序处理传入的多个请求,并将它们同步分派到相应的处理线程, 因此 Reactor 模式也叫 Dispatcher模式. Reactor 模式使用 IO 复用监听事件, 收到事件后,分发给某个线程(进程), 这点就是网络服务器高并发处理关键
主从 Reactor 多线程原理图
1. 主线程 MainReactor 对象通过 select 监听客户端连接事件,收到事件后,通过
Acceptor 处理客户端连接事件
2.当 Acceptor 处理完客户端连接事件之后(与客户端建立好 Socket 连接),MainReactor 将
连接分配给 SubReactor,SubReactor 将连接加入到自己的连接队列进行监听
3. 线程2 轮询是否有读写事件发生,有读事件发生,创建workhandler,进行读写
代码实现:
package dee.learn.server.newpackage;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Set;
/**
* @author Dee
* @version 1.0.0
* @ClassName MainReactor.java
* @Description 主Reactor
* @createTime 2022年04月04日 21:07:00
*/
public class MainReactor implements Runnable {
/**
* 用于接受连接的serverSocketChannel
*/
private ServerSocketChannel serverSocketChannel;
/**
* 组合子Rector
*/
private SubReactor subReactor;
/**
* 配置连接选择器
*/
private Selector selector;
public MainReactor (int port , SubReactor subReactor) {
try {
SelectorProvider provider = SelectorProvider.provider();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
this.subReactor = subReactor;
subReactor.setSelector(provider.openSelector());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){
try {
int select = selector.select();
if(select == 0){
System.out.println("===========>>>>>没有连接事件");
continue;
}
Set selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
//判断是否已有连接
if(selectionKey.isAcceptable()){
new Acceptor(serverSocketChannel,subReactor.getSelector()).run();
}
//移除,防止二次处理
iterator.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package dee.learn.server.newpackage;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
/**
* @author Dee
* @version 1.0.0
* @ClassName Acceptor.java
* @Description 接受请求
* @createTime 2022年04月04日 21:15:00
*/
public class Acceptor implements Runnable {
/**
* 用于接受请求的ServerSocketChannel
*/
private ServerSocketChannel serverSocketChannel;
/**
* 选择器,配置可读
*/
private Selector selector;
public Acceptor(ServerSocketChannel serverSocketChannel,Selector selector) {
this.serverSocketChannel = serverSocketChannel;
this.selector = selector;
}
@Override
public void run() {
try {
SocketChannel socketChannel = serverSocketChannel.accept();
//设置socketChannel 为非阻塞
socketChannel.configureBlocking(false);
//注册读事件
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("收到的连接" + socketChannel.getRemoteAddress());
} catch (IOException e) {
e.printStackTrace();
}
}
}
package dee.learn.server.newpackage;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Set;
/**
* @author Dee
* @version 1.0.0
* @ClassName SubReactor.java
* @Description 子Reactor
* @createTime 2022年04月04日 21:08:00
*/
public class SubReactor implements Runnable {
/**
* 配置可读选择器
*/
private Selector selector;
@Override
public void run() {
try {
while(true){
//这里使用selectNow 非阻塞,不能使用select() 因为这里阻塞了,会导致 注册可读事件时进入死循环
selector.selectNow();
Set selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
//判断是否可读
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
if(selectionKey.isReadable()){
new WorkHandler(selectionKey).run();
}
//移除,防止二次操作
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public Selector getSelector() {
return selector;
}
public void setSelector(Selector selector) {
this.selector = selector;
}
}
package dee.learn.server.newpackage;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
/**
* @author Dee
* @version 1.0.0
* @ClassName WorkHandler.java
* @Description 工作处理器,主要处理读写操作
* @createTime 2022年04月04日 21:49:00
*/
public class WorkHandler implements Runnable {
private SelectionKey selectionKey;
public WorkHandler(SelectionKey selectionKey) {
this.selectionKey = selectionKey;
}
@Override
public void run() {
SocketChannel socketChannel = null;
if(selectionKey.isReadable()){
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
socketChannel = (SocketChannel)selectionKey.channel();
int read = socketChannel.read(byteBuffer);
//如果read=-1 说明没有返回可读数据,渠道需要关闭
//不然会出现一个情况,客户端正常close ,服务端会接收到断开连接数据,也是可读isReadable,但是不会读到
if(read >= 0 ){
System.out.println("接收到的客户端信息是:"+ new String(byteBuffer.array(),0,read));
socketChannel.write(ByteBuffer.wrap("您好,我是服务端,已收到您的请求".getBytes(StandardCharsets.UTF_8)));
} else {
throw new IOException("读完成");
}
} catch (IOException e) {
e.printStackTrace();
selectionKey.cancel();
if(socketChannel != null){
try {
socketChannel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
}
package dee.learn.server.newpackage;
/**
* @author Dee
* @version 1.0.0
* @ClassName MainSubReactor.java
* @Description 主从单Reactor 模型
* @createTime 2022年04月04日 22:02:00
*/
public class MainSubReactorModel {
private MainReactor mainReactor;
private SubReactor subReactor;
public MainSubReactorModel(Integer port) {
this.subReactor = new SubReactor();
this.mainReactor = new MainReactor(port,this.subReactor);
}
/**
* 启动线程
*/
public void startThead(){
//主Reactor 线程
Thread mainReactorThread = new Thread(mainReactor);
mainReactorThread.start();
//从Reactor 线程
Thread subReactorThread = new Thread(subReactor);
subReactorThread.start();
}
/**
* 主方法入口
* @param args
*/
public static void main(String[] args) {
MainSubReactorModel mainSubReactorModel = new MainSubReactorModel(9999);
mainSubReactorModel.startThead();
}
}
package dee.learn.server.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
/**
* @author Dee
* @version 1.0.0
* @ClassName NIOClient.java
* @Description NIO客户端
* @createTime 2022年04月03日 09:34:00
*/
public class NIOClient {
public static void main(String[] args) throws IOException {
//1. 打开通道
SocketChannel socketChannel = SocketChannel.open();
//2. 设置连接IP和端口号
socketChannel.connect(new InetSocketAddress("127.0.0.1",9999));
//3. 写出数据
socketChannel.write(ByteBuffer.wrap("我是客户端数据".getBytes(StandardCharsets.UTF_8)));
//4. 读取服务器写回的数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int read = socketChannel.read(byteBuffer);
System.out.println("服务端返回数据:" + new String(byteBuffer.array(),0,read));
//5. 释放资源
socketChannel.close();
}
}
学习中遇到的问题:
1. 客户端发起socketChannel.close(),服务端还出现无限次的可读事件
客户端发起close 时,服务端会收到关闭信号(是可读数据),会使isReadable()为true,但是这个信号没法被读入byteBuffer,所以返回-1时,我们认为是读完毕,此处我们修改为报错,关闭通道
2. 接收到连接请求后,在acceptor中进行SocketChannel可读事件注册时一直阻塞
问题背景原因描述:开始是线程二是使用selector.select()方法,这时候会阻塞,同步锁publicKeys,而当接收到连接要进行注册可读事件时,也需要同步锁publicKeys ,这时候会陷入死循环
解决:线程二 run 方法,改用selector.selectNow()方法,该方法不会阻塞,没有keys 立马返回0