8、反应器模式Reactor(多线程主从模型)

多线程主从模型角色

多线程主从模型主要的应用场景,就是netty
1、MainReactor 主反应器角色。该角色只处理OP_ACCEPT 的IO事件,只轮循主Selector。并将事件分发出去,由Acceptor角色执行
2、Acceptor接受者。Acceptor将分发过来的事件,创建SocketChannel,并将OP_WRITE事件注册到多个SubSelector中。
3、SubReactor 子反应器角色(多个子反应器对象)。每个子反应器对应一个Selector,只处理该Selctor中的OP_WRITE事件。
4、Handler 处理角色。ServerSocketChannel 接收到请求,创建出SocketChannel,并用SocketChannel与客户端通信

代码

主线程类

/**
 * 主从多线程模型优点:
 * 1、每一个selector注册器都有1个线程来监听
 *        主注册器只接收socket连接的请求
 *        子注册器处理IO的读写事件
 * 2、TchHandler 处理事件的时候,可以用线程池来实现。
 *
 *
 * <p>
 *     TcpReactor tcpReactor=new TcpReactor(9000);
 *     这时,已经有多个线程在监听多个selector注册器了,每个线程监听1个
 *     TcpReactor 中的run方法 while循环监听的是客户端请求事件
 *     TcpSubReactor中的run方法 while循环监听的是子注册器中的读写事件
 *
 * </p>
 *
 *
 * */
public class Main {


    public static void main(String[] args) {

        try{
            TcpReactor tcpReactor=new TcpReactor(9000);
            tcpReactor.run();
        }catch (Exception e){

        }


    }
}

1、当TcpReactor tcpReactor=new TcpReactor(9000);初始化的时候,就已经将所有的注册器创建好了
1个主注册器,N个子注册器
selectionKey.attach(new Acceptor(ssc)); 这个Acceptor 和单线程的区别是,只将ServerSocketChannel 当做入参创建Acceptor
2、TcpReactor 类只轮循主Selector,并将OP_ACCEPT事件分发出去。TcpReactor 中的run方法中的
Runnable runnable=(Runnable)selectionKey.attachment();永远都是Acceptor对象。不可能是TcpHandler对象

/**
 * 主反应器:只接收OP_ACCEPT事件的反应器
 *
 *
 *
 * */
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();

        //设置非阻塞,只有非阻塞的通道才能使用NIO的IO多路复用,才能注册到选择器中,否则报错,与BIO中的阻塞对立
        ssc.configureBlocking(false);
        //将通道注册到selector中,设置为感兴趣的IO事件模式,返回SelectionKey,相当于存在map中的key
        SelectionKey selectionKey= ssc.register(selector,SelectionKey.OP_ACCEPT);

        /**
         * 给selectionKey设置一个附加对象,当有OP_ACCEPT的IO事件发生的时候,它的处理对象就是Acceptor
         * 这个多线程主从模型,和单线程的入参不一样。这个Acceptor的构造参数就只有1个,ssc,没有selector注册器了
         * 执行到这里的时候,启动了2个子线程。2个子注册器Selector
         *
         * */
        selectionKey.attach(new Acceptor(ssc));
        InetSocketAddress address=new InetSocketAddress(port);
        //通道绑定端口
        ssc.socket().bind(address);
    }

    /**
     *
     * 该方法只接收socket客户端的请求,分发出去。
     * 并且在多线程模式下,只处理主注册器的(多个注册器,1个主注册器,N个子线程注册器)IO事件
     *
     * */
    @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事件,该IO事件是IO_ACCEPT 客户端请求的事件
     *
     *
     * */
    private void dispatch(SelectionKey selectionKey) {
        /**
         *
         * 这个runnable的对象,一定是Acceptor对象,不可能是TcpHandler对象
         *
         *
         * */
        Runnable runnable=(Runnable)selectionKey.attachment();
        if(runnable!=null){
            runnable.run();
        }

    }

}

3、Acceptor类中的run方法,会将接收过来的OP_ACCEPT事件,创建SocketChannel,并将OP_WRITE事件设置为感兴趣事件,
注册到子Selector中,并且选择键的附件是TcpHandler对象

public class Acceptor implements Runnable{

    private  final ServerSocketChannel ssc;


    //服务器CPU核数
    private final Integer cores=2;
    private final Selector[] selectors=new Selector[cores];
    private TcpSubReactor[] subReactors=new TcpSubReactor[cores];
    private Thread[] threads=new Thread[cores];
    private int index=0;

    public Acceptor(ServerSocketChannel ssc){

        //服务端的sokcet
        this.ssc=ssc;

        try{
            for(int i=0;i<cores;i++){
                selectors[i]=Selector.open();
                subReactors[i]=new TcpSubReactor(selectors[i],ssc,i);
                threads[i]=new Thread(subReactors[i]);
                //启动线程,执行run方法
                threads[i].start();
            }
        }catch (Exception e){

        }


    }




    @Override
    public synchronized 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(selectors[index], SelectionKey.OP_WRITE);
                //将TcpHandler处理对象设置为附件,当有IO事件是 读写事件的时候,会创建TcpHandler对象
                selectionKey.attach(new TcpHandler(selectionKey,socketChannel));
                index++;
            }

        } catch (Exception e) {
        }
    }
}

4、每一个子反应器对象都对应一个子注册器。Acceptor中的TcpSubReactor[]集合和Thread[]集合在主线程刚开始就已经创建好了,
并且Thread[]集合已经开始启动运行了。
TcpSubReactor 中的run()函数,while循环一直在监听着对应的子注册器。如果子注册器中有新注册的OP_WRITE IO事件,
这时就会调用分发方法dispatch(),
Runnable runnable=(Runnable)selectionKey.attachment(); 中的对象是TcpHandler对象。

/**
 * 子反应器:接收OP_WRITE事件的反应器。
 *
 *
 * */
public class TcpSubReactor implements Runnable{


    private  final ServerSocketChannel ssc;
    private final Selector selector;
    private int num;

    public TcpSubReactor(Selector selector,ServerSocketChannel ssc,int num){
        this.selector=selector;
        this.ssc=ssc;
        this.num=num;
    }

    /**
     * TcpReactor tcpReactor=new TcpReactor(9000);
     * 初始化TcpReactor的时候,该run方法已经开始跑了。
     * 但是它只接收子注册器中的IO事件。
     *
     * */
    @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();
            }
        }
    }

    private void dispatch(SelectionKey selectionKey) {
        /**
         *
         * 如果是接收客户端连接的话。
         * selectKey存储的是Acceptor对象,该对象是继承了Runnable接口的。
         * 所以,也可以执行run方法。在主线程中执行
         *
         *
         *
         *
         * */
        Runnable runnable=(Runnable)selectionKey.attachment();
        if(runnable!=null){
            runnable.run();
        }

    }

}

TcpHandler对象是服务端ServerSocketChannel创建的SocketChannel (服务端)与客户端进行通信。

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){
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 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、付费专栏及课程。

余额充值