反应器模式角色
单线程的反应器模式,主要用在redis4.0以前,
1、Reactor 反应器角色。该角色主要是来监听Selector中感兴趣的IO事件的。找出所有事件,并将事件分发出去
2、Acceptor 接受者。该角色主要接收Reactor反应器中分发的 OP_ACCEPT接收事件,并创建SocketChannel通道,将处理类设置到选择键的附件中
3、Handler 处理角色。ServerSocketChannel 接收到请求,创建出SocketChannel,并用SocketChannel与客户端通信
代码
主线程类(只有1个线程)
tcpReactor.run() ,是在主线程中运行run方法。
.start(),才是开启一个新线程
public class Main {
public static void main(String[] args) {
try{
/**
* 将ssc通道注册到selector中,并且将selector的事件选择为接收的IO事件
* 当有客户端连接的时候,会创建Acceptor对象,并将该对象保存在selectionKey中
*
* */
TcpReactor tcpReactor=new TcpReactor(9090);
/**
* 这里是run方法,是在主线程中调用的。
* start方法是创建一个线程,异步执行的
* */
tcpReactor.run();
}catch (Exception e){
System.out.println("error");
}
}
}
1、TcpReactor 是反应器类,构造函数,已经初始化了注册器,并且设置感兴趣的事件是OP_ACCEPT事件,
并且将接收器对象(传入注册器和通道),放入选择键的附件里。
2、RUN 方法。一直在循环Selector注册器,此时注册器中是空的,selector.select()是阻塞的,
当有客户端的socket连接时,才会执行代码。
3、dispatch方法是分发IO事件的。这时,将选择键附件中的对象取出来。并且执行 Acceptor类中的run方法。
4、Acceptor中的run方法是将SocketChannel中的 OP_WRITE 读写事件注册到Selector中,
并将TcpHandler处理类的对象放入SelectionKey的附件中
5、这时TcpReactor 中的run方法会一直在循环,selectionKeys中selectionKey中存放的是TcpHandler
6、TcpReactor 中的dispatch方法Runnable runnable=(Runnable)selectionKey.attachment();
这个runnable对象是TcpHandler,执行TcpHandler中的run方法,跟客户端的socket通信
public class TcpReactor implements Runnable{
private final ServerSocketChannel ssc; //服务端socket通道
private final Selector selector; //选择器,通道注册的地方
public TcpReactor(int port)throws Exception{
//创建选择器对象
selector=Selector.open();
//打开服务端通道
ssc=ServerSocketChannel.open();
InetSocketAddress address=new InetSocketAddress(port);
//通道绑定端口
ssc.bind(address);
//设置非阻塞,只有非阻塞的通道才能使用NIO的IO多路复用,才能注册到选择器中,否则报错,与BIO中的阻塞对立
ssc.configureBlocking(false);
//将通道注册到selector中,设置为感兴趣的IO事件模式,返回SelectionKey,相当于存在map中的key
SelectionKey selectionKey= ssc.register(selector,SelectionKey.OP_ACCEPT);
//给selectionKey设置一个附加对象,当有OP_ACCEPT的IO事件发生的时候,它的处理对象就是Acceptor
selectionKey.attach(new Acceptor(selector,ssc));
}
@Override
public void run() {
//在线程中断前持续执行
while (!Thread.interrupted()){
System.out.println("接收连接。。。。。。");
try{
//查看注册器中有没有事件发生,如果没有事件发生,该方法阻塞到这里。
//该方法会将查询出来的IO事件放到Set<SelectionKey> selectionKeys集合中。Selector类中就有该集合。
selector.select();
}catch (Exception e){
}
System.out.println("发生了事件,开始执行所有注册在selector中的IO事件");
Set<SelectionKey> selectionKeys=selector.selectedKeys();
//遍历所有发生的事件
Iterator<SelectionKey> it=selectionKeys.iterator();
while (it.hasNext()){
//从迭代器中读取事件
SelectionKey selectionKey=it.next();
//该处相当于一个路由器,分发处理
dispatch(selectionKey);
it.remove();
}
}
}
/**
* 单线程模式下:
* 该分发的方法,所有的IO事件都处理。
* 因为把所有的事件都注册到一个selector中去了
*
*
* */
private void dispatch(SelectionKey selectionKey) {
/**
*
* 如果是接收客户端连接的话。
* selectKey存储的是Acceptor对象,该对象是继承了Runnable接口的。
* 所以,也可以执行run方法。在主线程中执行
*
* 如果是接收IO事件中的读写事件的时候
* selectKey存储的是TcpHandler对象,该对象继承了Runnable接口的。
* 所以,也可以执行run方法。在主线程中执行
*
* */
Runnable runnable=(Runnable)selectionKey.attachment();
if(runnable!=null){
runnable.run();
}
}
}
此类是Acceptor类
public class Acceptor implements Runnable{
private ServerSocketChannel ssc;
private Selector selector;
public Acceptor(Selector selector, ServerSocketChannel ssc){
//选择器
this.selector=selector;
//服务端的sokcet
this.ssc=ssc;
}
@Override
public void run() {
try {
//serverSocket接收客户端的请求,创建一个socket 和一个socketChannel和客户端的socket通信
SocketChannel socketChannel=ssc.accept();
if(socketChannel!=null){
//设置socketChannel是非阻塞的,与BIO中的阻塞对立
socketChannel.configureBlocking(false);
//注册到selector中,并设置感兴趣的IO事件是读写。 OP_READ 是<=1 OP_WRITE 是<=2
SelectionKey selectionKey= socketChannel.register(selector, SelectionKey.OP_WRITE);
//将TcpHandler处理对象设置为附件,当有IO事件是 读写事件的时候,会创建TcpHandler对象
selectionKey.attach(new TcpHandler(selectionKey,socketChannel));
}
} catch (Exception e) {
}
}
}
TcpHandler类
public class TcpHandler implements Runnable{
private SelectionKey selectionKey;
private SocketChannel socketChannel;
public TcpHandler(SelectionKey selectionKey,SocketChannel socketChannel){
//selectionKey选择键
this.selectionKey=selectionKey;
//服务端创建的和客户端保持通信的 socketChannel 通道
this.socketChannel=socketChannel;
}
@Override
public void run() {
try {
read();
} catch (Exception e) {
e.printStackTrace();
}
}
private void read() throws Exception {
byte[] bytes=new byte[1024];
ByteBuffer byteBuffer=ByteBuffer.wrap(bytes);
int numBytes=socketChannel.read(byteBuffer);//读取字符串
if(numBytes == -1){
closeChannel();
return;
}
String str=new String(bytes);
if(StringUtils.isNotBlank(str)){
Thread.sleep(2000);
System.out.println("正在处理业务逻辑。。。。。。。。。。。。。。。。。。");
System.out.println( socketChannel.getRemoteAddress().toString()+ ">>>>接收到的客户端的信息是:"+ str);
send(str);
}
selectionKey.selector().wakeup();//使一个阻塞的selector立即返回
}
private void send(String str) throws IOException {
String returnStr="根据读取的数据返回响应:"+socketChannel.getLocalAddress().toString()+"\r\n";
ByteBuffer byteBuffer=ByteBuffer.wrap(returnStr.getBytes());
while (byteBuffer.hasRemaining()){
socketChannel.write(byteBuffer);//回传给client的回应字符串,发送buffer的postion位置,到limit位置
}
}
private void closeChannel(){
try{
selectionKey.cancel();
socketChannel.close();
}catch (Exception e){
}
}
}