7、反应器模式Reactor(单线程版)

反应器模式角色

单线程的反应器模式,主要用在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 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){
        }
    }
}

你可能感兴趣的:(netty)