Reactor学习

Reactor

我们需要先了解下NIO的基础和概念。
NIO相对于BIO是非阻塞的API。NIO有三个核心组件分别是:
			Buffer缓冲区
			channel通道
			selector选择器

Buffer缓冲区
缓冲区的本质是一个可以写入数据的内存模块,可以再次读取。
使用Buffer进行数据写入和读取,有四个步骤:
1、将数据写入缓冲区,
2、调用buffer.filp(),转为读取模式
3、缓冲区读取数据
4、调用buffer.clear()或者buffer.compact()清除缓存区
channel通道
bio的管道是outStream和inputStream直接对对接。Nio是channel和channel对接。如下图:
在这里插入图片描述
主要是socketchannel和serversocketcannel.
socketchannel用户tcp的网络连接。对应客户端而言是客户端主动发起和客户端的链接。对应服务端是服务的获取新的链接。
SocketChannel socketChannel =SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress(“127.0.0.1”,9030));

//创建网络请求
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);//设置非阻塞模式
serverSocketChannel.socket().bind(new InetSocketAddress(9030));//绑定端口
System.out.println(“启动成功”);

selector选择器
实现了单个线程可以管理多个通道,从而管理多个网络连接。
一个线程selector监听多个channel的不同事件:
1、connect连接
2、accept准备就绪
3、read读取
4、write写入

Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。
通道触发了一个事件意思是该事件已经就绪。比如某个Channel成功连接到另一个服务器称为“ 连接就绪 ”。一个Server Socket Channel准备好接收新进入的连接称为“ 接收就绪 ”。一个有数据可读的通道可以说是“ 读就绪 ”。等待写数据的通道可以说是“ 写就绪 ”。

单线程模型Reactor
所有的IO操作都在同一个NIO线程上完成的。单线程NIO处理监听请求、接受读请求、处理业务逻辑、写请求等。
作为服务端的Reactor:
1、接受客户端的tcp请求
2、读取通信端的请求和应答信息
3、向通信端发送请求。
单reacotor的模型如下:

在这里插入图片描述
Reactor 多线程模型
Reactor多线程模型与单线程模型最大的区别在于,IO处理线程不再是一个线程,而是一组NIO处理线程。原理如下图所示
在这里插入图片描述

其特点:
1)有一个acceptor线程监听客户端 的tcp请求
2)读写IO线程,由专门的线程池负责读写。这些io读写的线程池负责读取、发送数据
3)一个acceptor线程可以处理N个链路,但是一个链路只有一个accptor线程

主从Reactor 多线程模型
主从Reactor线程模型的特点是:服务端用于接收客户端连接的不再是一个单独的NIO线程,而是一个独立的NIO的线程池。Acceptor接收到客户端TCP连接请求并处理完成后(可能包含接入认证),再将新创建的SocketChannel注册到IO线程池(sub reactor)的某个IO处理线程上并处理编解码和读写工作。Acceptor线程池仅负责客户端的连接与认证,一旦链路连接成功,就将链路注册到后端的sub Reactor的IO线程池中。 线程模型图如下
在这里插入图片描述


package com.wm.test.net.mynio;


import com.study.hc.net.nio.NIOServerV3;
import com.sun.org.apache.bcel.internal.generic.Select;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 多路复用选择器 是selector
 *
 * reactor线程模型,一般是2种
 * 一种是单reactor模式
 *    reactor负责处理accept,和读、写就绪
 *    handle负责处理读数据和写数据, workspace工作线程负责处理业务
 *
 * 二种是多线程reator模式
 *   mainreactor是负责处理accept,读写就绪
 *   subreactor线程负责处理读、写数据
 *   workspace工作线程负责处理业务
 *
 *
 */

public class NIOServierV3{

    /** 处理业务操作的线程 */
    private static ExecutorService workPool = Executors.newCachedThreadPool();

    /**只有1个serverchannel 和多个客户端client 连接**/
    private ServerSocketChannel serverSocketChannel;

    /**  1、创建多个线程 - accept处理reactor线程 (accept线程)**/
    private ReactorThread[] mainReactorThread = new ReactorThread[1];
    private ReactorThread[] subReactorThread= new ReactorThread[8];



    /** ReactorThread 线程,封装着,selector,accept和reader 以及 serverSocketChannel 和 SocketChannel
     * 注册监听事件中**/

    abstract  class ReactorThread extends Thread{
        Selector selector;//多路复用选择器
        LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();//单独注册channle用的

        //seletor 监听到事件后,调用这个方法
        public abstract void handle(SelectableChannel selectableChannel)throws Exception;

        //初始化选择器
        public ReactorThread() throws IOException {
            this.selector=Selector.open();
        }

        volatile  boolean running =false;

        private void doStart(){
            if(!running){
                running=true;
                super.start();//启动线程
            }
        }

        private SelectionKey regist(SelectableChannel selectableChannel) throws Exception {


           // selectableChannel.register(selector,0,selectableChannel);//频道注册到选择器上,模式是0

            // 因为线程在执行channel注册到selector的过程中,会和调用selector.select()方法的线程争用同一把锁

            // 代码里select()方法实在eventLoop中通过while循环调用的,争抢的可能性很高,为了让register能更快的执行,就放到同一个线程来处理
            //必须等任务都注册完成后,才能调用select()

            //上面的方法改为由线程任务提交模式

            FutureTask<SelectionKey> task = new FutureTask<SelectionKey>(()-> selectableChannel.register(selector, 0, selectableChannel));
            taskQueue.add(task);
            return task.get();
        }




        @Override
        public void run() {
            while(running){

                Runnable runnable;

                while((runnable=taskQueue.poll())!=null){
                    runnable.run();
                }
                try {
                    selector.select(1000);
                    //取出所有的事件集合

                    Set<SelectionKey> selected  =   selector.selectedKeys();
                    Iterator<SelectionKey> iterator= selected.iterator();

                    while(iterator.hasNext()){
                        SelectionKey key = iterator.next();
                        //key对应的时间不同的注册事件不一样
                        iterator.remove();
                        int readyOps = key.readyOps();

                        //public final boolean isReadable()
                        //测试此键的通道是否已准备好进行读取。
                        //调用此方法的形式为 k.isReadable() ,该调用与以下调用的作用完全相同:
                       /* if((readyOps & (SelectionKey.OP_ACCEPT | SelectionKey.OP_READ))!=0 || readyOps==0){



                        }*/
                        //上面的用户相同
                       if(key.isReadable() || key.isAcceptable() || readyOps==0){

                           SelectableChannel selectableChannel= (SelectableChannel) key.attachment();
                           selectableChannel.configureBlocking(false);

                           try {
                               handle(selectableChannel);
                               //如果是serverchannel则是需要accept获取socketchannel和 注册读事件
                                    //还有将serverchannel分配给subReactor去处理
                               //如果是socketchannel则需要直接读取数据,做业务逻辑处理

                               if(!selectableChannel.isOpen()){
                                   key.cancel(); // 如果关闭了,就取消这个KEY的订阅
                               }

                           } catch (Exception e) {
                               e.printStackTrace();
                               key.cancel(); // 如果有异常,就取消这个KEY的订阅
                           }
                       }


                    }
                    //while的外面 与 selector.select并列
                    selector.selectNow();
                } catch (IOException e) {
                    e.printStackTrace();
                }


            }

        }
    }


    /**
     * 绑定端口
     *
     * @throws IOException IO异常
     */
    private void bind() throws IOException {

        //  1、 正式绑定端口,对外服务
        serverSocketChannel.bind(new InetSocketAddress(8080));
        System.out.println("启动完成,端口8080");


    }


    /**
     * 初始化线程组
     */
    private void newMain和subReactorGroup() throws IOException {

        // 创建mainReactor线程, 只负责处理serverSocketChannel
        for (int i = 0; i < mainReactorThread.length; i++) {
            AtomicInteger incr = new AtomicInteger(0);
            mainReactorThread[i] = new ReactorThread() {
                @Override
                public void handle(SelectableChannel selectableChannel) throws Exception {
                    // 只做请求分发,不做具体的数据读取
                    ServerSocketChannel ch = (ServerSocketChannel) selectableChannel;
                    SocketChannel socketChannel= ch.accept();
                    socketChannel.configureBlocking(false);

                    // 收到连接建立的通知之后,分发给I/O线程继续去读取数据
                    int index = incr.getAndIncrement() % subReactorThread.length;
                    //ReactorThread workEventLoop = subReactorThread[index];

                    ReactorThread ioReactor=subReactorThread[index];
                    ioReactor.doStart();//io线程跑起来 和 io线程注册 是分开的,其实里面一个select()和rigitst里面都是用到锁的
                    //在regist用的是线程注册,futuretask是阻塞的,等dostart线程里面的taskqune任务run之后可以获取
                    SelectionKey selectionKey = ioReactor.regist(socketChannel);//是注册频道和channel、selector关联
                    selectionKey.interestOps(SelectionKey.OP_READ);
                    System.out.println(Thread.currentThread().getName() + "收到新连接 : " + socketChannel.getRemoteAddress());

                }
            };


        }
        // 创建IO线程,负责处理客户端连接以后socketChannel的IO读写
        for (int i = 0; i < subReactorThread.length; i++) {
            subReactorThread[i]=new  ReactorThread(){


                @Override
                public void handle(SelectableChannel selectableChannel) throws Exception {
                    // work线程只负责处理IO处理,不处理accept事件
                    SocketChannel ch = (SocketChannel) selectableChannel;
                    ByteBuffer requestBuffer = ByteBuffer.allocate(1024);
                    while (ch.isOpen() && ch.read(requestBuffer) != -1) {
                        // 长连接情况下,需要手动判断数据有没有读取结束 (此处做一个简单的判断: 超过0字节就认为请求结束了)
                        if (requestBuffer.position() > 0) break;
                    }
                    if (requestBuffer.position() == 0) return; // 如果没数据了, 则不继续后面的处理
                    requestBuffer.flip();
                    byte[] content = new byte[requestBuffer.limit()];
                    requestBuffer.get(content);
                    System.out.println(new String(content));
                    System.out.println(Thread.currentThread().getName() + "收到数据,来自:" + ch.getRemoteAddress());

                    // TODO 业务操作 数据库、接口...
                    workPool.submit(() -> {
                    });

                    // 响应结果 200
                    String response = "HTTP/1.1 200 OK\r\n" +
                            "Content-Length: 11\r\n\r\n" +
                            "Hello World";
                    ByteBuffer buffer = ByteBuffer.wrap(response.getBytes());
                    while (buffer.hasRemaining()) {
                        ch.write(buffer);
                    }
                }
            };

        }

    }

    /**
     * 初始化channel,并且启动一个eventLoop线程,
     * 服务端需要启动serversockert
     *
     * @throws IOException IO异常
     */
    private void initAndRegister() throws Exception {

        // 1、 创建ServerSocketChannel
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        // 2、 将serverSocketChannel注册到selector
        int index = new Random().nextInt(mainReactorThread.length);
        mainReactorThread[index].doStart();
        SelectionKey selectionKey = mainReactorThread[index].regist(serverSocketChannel);
        selectionKey.interestOps(SelectionKey.OP_ACCEPT);
    }






}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值