主从Reactor多线程原理学习

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<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> 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<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> 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

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
主从 Reactor线程是一种常见的网络编程模型,它通常用于高并发服务器的开发。该模型的核心思想是将网络 I/O 与业务逻辑分离,并通过多线程来实现并发处理。 在主从 Reactor线程模型中,主线程(也称为 Acceptor 线程)负责监听客户端连接请求,并将连接分配给工作线程(也称为 EventLoop 线程)进行处理。工作线程负责处理连接上的读写事件和业务逻辑,并将需要执行的任务交给线程池中的线程进行处理。 主从 Reactor线程模型主要包含以下组件: 1. Acceptor:负责监听客户端连接请求,并将连接分配给工作线程进行处理。 2. EventLoop:负责处理连接上的读写事件和业务逻辑,并将需要执行的任务交给线程池中的线程进行处理。 3. Thread Pool:用于执行异步任务,例如数据库查询和计算密集型任务等。 在实现主从 Reactor线程模型时,需要注意以下几点: 1. Acceptor 线程与工作线程之间应该使用线程安全的队列进行通信。 2. 每个工作线程应该拥有一个 EventLoop 对象,用于处理连接上的读写事件和业务逻辑。 3. 线程池中的线程应该使用异步方式执行任务,以避免阻塞工作线程。 总之,主从 Reactor线程模型是一种高效的网络编程模型,可以有效地提高服务器的并发处理能力。但是它的实现比较复杂,需要考虑线程同步、线程安全和性能等方面的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值