第五篇:基于NIO的Reactor三种模式

一、前言

二、NIO引入

2.1 客户端代码

public class NioClient {
    public static void main(String[] args) throws Exception {
        //得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();

        //设置成非阻塞
        socketChannel.configureBlocking(false);

        //提供服务器Ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress(9091);

        //连接服务器端
        if (!socketChannel.connect(inetSocketAddress)) { //如果连接不上
            while (!socketChannel.finishConnect()){
                System.out.println("Nio 非阻塞");
            }
        }

        new Thread(new MyRunnable(socketChannel)).start();
        while (true) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int read = socketChannel.read(buffer);
            byte[] array = new byte[buffer.position()];
            if (array.length >= 0) System.arraycopy(buffer.array(), 0, array, 0, array.length);
            if (read > 0) {
                System.out.println(new String(array));
            }
        }
    }

    static class MyRunnable implements Runnable{

        SocketChannel socketChannel;

        public MyRunnable(SocketChannel socketChannel) {
            this.socketChannel = socketChannel;
        }

        @Override
        public void run() {
            while (true) {
                //创建一个Buffer对象并存入数据
                Scanner scanner = new Scanner(System.in);
                String message = scanner.nextLine();
                ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
                //发送数据
                try {
                    socketChannel.write(buffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2.2 服务端代码

public class NioServer {
    public static void main(String[] args) throws Exception {

        //得到serverSocketChannel对象
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //得到Selector对象
        Selector selector = Selector.open();

        //绑定端口
        serverSocketChannel.bind(new InetSocketAddress(9091));

        //设置为非阻塞式
        serverSocketChannel.configureBlocking(false);

        //把ServerSocketChannel注册给Selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//监听连接

        while (true) {
            if (selector.select(2000) == 0) {   // selector轮询
                //System.out.println("2秒内没有客户端来连接我");
                continue;
            }
            //得到SelectionKey对象,判断事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            for (SelectionKey selectionKey : selectionKeys) {
                if (selectionKey.isAcceptable()) { //连接事件
                    System.out.println("有人来连接");
                    //获取网络通道
                    SocketChannel clientSocket = serverSocketChannel.accept();
                    //设置非阻塞式
                    clientSocket.configureBlocking(false);
                    //连接上了 注册读取事件
                    clientSocket.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (selectionKey.isReadable()) {//读取客户端数据事件
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
                    socketChannel.read(byteBuffer);   // 读入


                    // 逻辑 关于array开始
                    byte[] array = new byte[byteBuffer.position()];  // 新建array
                    if (array.length >= 0)
                        System.arraycopy(byteBuffer.array(), 0, array, 0, array.length);  // 复制元素放到array中
                    System.out.println(new String(array));   // 打印array
                    // 逻辑 关于array结束


                    byteBuffer.put("你好".getBytes());  // put放入
                    byteBuffer.flip(); // 翻转,写出或打印的前奏
                    socketChannel.write(byteBuffer);  // 写出
                    //写完过后要记得clear一下,不然position的值和limit的是一样,下次就会报异常。
                    byteBuffer.clear();  // 这个buffer同时用于读写,每次循环要清空
                }
                //手动从当前集合将本次运行完的对象删除
                selectionKeys.remove(selectionKey);  // iterator或者foreach需要remove
            }
        }
    }
}

2.3 金手指

金手指:重点看NIOServer代码,NIOClient和IOClient是一样的,甚至可以不需要IOClient,直接用命令行模拟,对于NIOServer,后面的单线程Reactor就是对其封装并分类。

三、基于NIO的Reactor单线程模式

3.1 手写Reactor单线程模式

3.1.1 项目结构

项目结构为 Main+Reactor分发器+Acceptor处理连接请求+Handler处理读写请求+Client客户端
在这里插入图片描述

3.1.2 项目详细

Main类:仅包含一个main()方法,为程序入口。

package com.reactor1;

public class Main {

    // Reactor就是对NIO封装,按照该功能分为不同类,对于开发者来说,只要按照Main类中的,
    // 新建TcpReactor对象,传入ip:port,调用run()方法就好了,底层就是一个nio  good
    // Reactor  用于客户端连接和分发(acceptor和read/write)
    // Acceptor accept接收连接请求
    // Handler  read接收请求 write写回给客户端

    // 三种模型都是这样
    // Reactor:把IO事件分配给对应的handler处理
    // Acceptor:处理客户端连接事件
    // Handler:处理非阻塞的任务

    public static void main(String[] args) {
        Reactor reactor = null;
        try {
            reactor = new Reactor(1333);
            reactor.run();  // 为什么设置里使用run  不是start   不需要实现Runnable接口,造成误解,

            // run()方法:
            // 是在主线程中执行方法,和调用普通方法一样;(按顺序执行,同步执行)
            // start()方法:
            // 是创建了新的线程,在新的线程中执行;(异步执行)

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

TcpReactor类:是一个实现Runnable接口的具有多线程功能的类,用于接收accept连接请求,包括构造方法和run()方法和dispatch()分发方法,子线程处理逻辑在run()方法中,dispatch()方法为调度方法,根据事件绑定的对象开新线程。

package com.reactor1;

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.util.Iterator;
import java.util.Set;

public class Reactor {

    private final ServerSocketChannel serverSocketChannel;   // 类变量
    // 三要素都要是类变量   selector--channnel--buffer(读写才要buffer,连接不需要buffer)
    private final Selector selector;  // 类变量

    public Reactor(int port) throws Exception {  // Reactor和之前的nio一样,就是对其封装一次

        // 初始五句话:初始化selector  初始化服务端channel 服务端channel绑定端口  服务端channel设置为非阻塞  服务端channel注册到selector上面,返回在selector中表示这个服务端channel的key
        // 最后一句:服务端这个channel去绑定一个服务端acceptor对象,用来接收请求的,参数为服务端channel和服务端selector

        selector = Selector.open();  // good   这一步不能少  初始化selector   直接静态方法新建
        // 只有一个serverSocketChannel
        serverSocketChannel = ServerSocketChannel.open();  // 初始化服务端channel   直接静态方法新建
        //在ServerSocketChannel绑定监听端口
        serverSocketChannel.socket().bind(new InetSocketAddress(port));  // 服务端channel绑定端口
        //设置ServerSocketChannel为非阻塞
        serverSocketChannel.configureBlocking(false);   // 服务端channel设置为非阻塞
        // ServerSocketChannel向selector注册一个OP_ACCEPT事件,然后返回该通道的key     SelectionKey.OP_ACCEPT  16
        SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  // 服务端channel注册到selector上面,返回在selector中表示这个服务端channel的key
        //给定key一个附件的Acceptor对象
        selectionKey.attach(new Acceptor(serverSocketChannel, selector));  // 服务端这个channel去绑定一个服务端acceptor对象,用来接收请求的,参数为服务端channel和服务端selector
    }


    public void run() {
        //在线程被中断前持续运行
        while (!Thread.interrupted()) {  // 当服务端线程没有被打断的时候
            System.out.println("Waiting for new event on port:" + serverSocketChannel.socket().getLocalPort() + "...");  // 在服务端端口号等待客户端连接
            int len=-1;
           //这一句打印表示服务端已经启动,或已经处理完成一次请求,服务端目前处于空闲状态
            try {
                //若没有事件就绪则不往下执行,NIO的底层是linux非阻塞io,轮询
                if ((len =  selector.select()) == 0) {   // 这里启动的时候阻塞,连接客户端才通过,没有客户端连接,这里就是0,也可以通过唤醒来处理
                    continue;    // 既然方法还没执行完就阻塞,就完成不会返回0,那么还需要continue;干什么
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
           // System.out.println(len);   // 这里我想打印一下
            //取得所有已就绪事件的key集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();   // 当前类变量selector中的key集合  selectedKey 被选择的指定的key Accept read write
            Iterator<SelectionKey> it = selectionKeys.iterator();  // 迭代iterator() while
            while (it.hasNext()) {

                dispatch(it.next());     //根据事件的key进行分发调度
                it.remove();  // 每次删除一个,和Reactor之前的nio一样,就是对其封装一次
            }
        }
    }

    //调度方法,根据事件绑定的对象开新线程
    private void dispatch(SelectionKey key) {
        //根据事件之key绑定的对象开启线程
      Runnable r = (Runnable) key.attachment();   // 三个绑定  服务端启动的使用绑定Accept 客户端连接的时候使用
        // 客户端连接的时候绑定READ   客户端输入的时候使用
        // 客户端输入的时候绑定WRITE  输出给客户端的时候调用
        // 输出给客户端的时候绑定READ   下一次客户端输入的时候使用
        // 得到传递多来的selector中的key,所attach绑定的Runnable对象,就是Acceptor implements Runnable
        if (r != null) {
            r.run();  // 调用Acceptor的run()方法   调用  主线程   两个都是主线程
        }
    }
}

Acceptor类:是一个实现Runnable接口的具有多线程功能的类,用于接收accept连接请求,包括构造方法和run()方法,子线程处理逻辑在run()方法中。

package com.reactor1;

import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

//接受连接请求线程
public class Acceptor implements Runnable {

    private final ServerSocketChannel serverSocketChannel;   // 两个类变量,都是被TcpReactor的类变量赋值(TcpReactor类的构造函数)
    private final Selector selector;   // 两个类变量,都是被TcpReactor的类变量赋值(TcpReactor类的构造函数)

    public Acceptor(ServerSocketChannel serverSocketChannel, Selector selector) {
        this.serverSocketChannel = serverSocketChannel;   // 仅仅设置而已,acceptor的run什么时候调用
        this.selector = selector;
    }


    public void run() {
        try {
            //接受client连接请求
            SocketChannel socketChannel = serverSocketChannel.accept();   // 既然调用就接收请求,
            System.out.println(socketChannel.getRemoteAddress().toString() + " is connected.");  // 这一句作用是表示启动服务端之后,

            if (socketChannel != null) {   // 不为空
                //设置成非阻塞   good
                socketChannel.configureBlocking(false);
                //SocketChannel向selector注册一个OP_READ事件,然后返回该通道的key good

                //还是注册到同一个selector上面  等待输入
                SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);   //注册读  因为服务端是先读后写
                //使一个阻塞住的selector操作立即返回
                //   selector.wakeup();   //wakeup 唤醒selector中的其中一个,为什么要唤醒,在哪里阻塞,
                // selector.wakeup主要是为了唤醒阻塞在selector.select上的线程,在selector.select()后线程会阻塞
                //给定可以一个附加的TCPHandler对象
                selectionKey.attach(new Handler(selectionKey, socketChannel));  // 这个key attach一个TcpHandler
                // 传入两个参数  selectionKey是客户端channel注册到selector的时候获取到,客户端channel是连接得到的
                // Acceptor建立连接,TcpHandler就要处理读写请求了
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

TcpHandler类:也是一个多线程类,用来处理客户端发过来的读写请求,包含构造方法、run()方法、send()方法、read()方法、closeChannel()方法和process()方法。构造方法用户初始化selectionKey和客户端连接socketchannel,run()方法是子线程具体逻辑,通过判断当前state的值决定服务端是读还是写,从客户端读就是调用read()方法,写出到客户端就调用send()方法,实际上,底层都是调用socketChannel.read()方法和socketChannel.write()方法,所以说,是对NIO的封装,是基于NIO实现的。剩下两个,closeChannel()方法,仅仅封装了selectionKey.cancel(); socketChannel.close();表示因服务大异常引起的连接关闭操作;process()方法,仅调用了线程休眠,模拟服务端处理耗时。

package com.reactor1;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

public class Handler implements Runnable {  // 不是多线程类  不是答应

    private final SelectionKey selectionKey;   // 两个类变量,是在Accetpor的run方法中设置的,
    // 传入两个参数  selectionKey是客户端channel注册到selector的时候获取到,客户端channel是连接得到的
    private final SocketChannel socketChannel;
    int state;
    
    public Handler(SelectionKey selectionKey, SocketChannel socketChannel) {
        this.selectionKey = selectionKey;
        this.socketChannel = socketChannel;
        //初始的状态设定为Reading
        state = 0;   // state表示read还是send run用来判断   先读取客户端的数据,
        // 然后三步一体:将标志位修改为state=1,注册写事件,唤醒一个selector,执行run()方法,将数据写到客户端
    }
    
    public void run() {   // 这个run()方法什么时候调用  知道了   逻辑就是读写
        try {
            if (state == 0) {
                //读取网络数据
                read();   //read send 向对应
            } else {
                //发送网络数据
                send();  // read send 相对应
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("[Warning!] A client has been closed");
            closeChannel();   // 发生错误,关闭连接
        }
    }

    private void closeChannel() {
        try {
            selectionKey.cancel();    //
            socketChannel.close();   //  selectionKey是客户端channel注册到selector的时候获取到,客户端channel是连接得到的
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void send() throws Exception {
        String str = "Your message has sent to" + socketChannel.socket().getLocalSocketAddress().toString() + "\r\n";   //这是服务端要发送给客户端的响应
        //wrap自动把buf的position设为0,所以不需要在flip()  good  wrap和flip 就是要打印或者write
        ByteBuffer buf = ByteBuffer.wrap(str.getBytes());
        while (buf.hasRemaining()) {
            //回传给client回应字符串,发送buf的position位置到limit位置为止之间的内容
            socketChannel.write(buf);   // 服务端使用channell来写,客户端使用两个stream读写,因为stream是单向的,
            // 服务端使用一个channel读写,因为channel是双向的,api层面既有channel.read,也有channel.write
            // 而且每次都是channel.write(buffer) channel.read(buffer)   所以  channel+buffer==stream   selector是类变量,服务端全局选择
            // 服务端的channel和stream如何联系起来  不用联系,直接静态方法新建channel
            // 为什么分为服务端channel和客户端channel
            // 服务端channel  TcpReactor构造函数中,serverSocketChannel = ServerSocketChannel.open();
            // 客户端channel   Acceptor类中的run()方法,SocketChannel socketChannel = serverSocketChannel.accept();   // 启动服务端后阻塞在这里
            // 服务端channel是打开来给客户端连接的,客户端channel表示服务端获取到的一个客户端连接,表示服务端获取到的一个客户端连接  理解了  很重要   good
            // 所以,服务端channel是open  客户端channel是accept() 源码命名优美
        }
        // 写完成数据到客户端后,然后三步一体:将标志位修改为state=0,注册读事件,唤醒一个selector,执行run()方法,读取客户端的数据,就是一个循环
        //改变状态
        state = 0;  // 改变状态为0,继续读取客户端的数据
        //通过key改变通道注册的事件   SelectionKey.OP_READ  1
        selectionKey.interestOps(SelectionKey.OP_READ);   //  类变量selectionKey的感兴趣的操作设置为read  下一次使用   TCPHandlers selectedkey就是read
        //使一个阻塞住的selector操作立即返回
        //  selectionKey.selector().wakeup();  // 类变量selectionkey 得到这个key关联的selector,只有一个类变量selector,然后唤醒这个selector
        // 调用wakeup()的地方,read send
    }

    private void read() throws Exception {
        //non-blocking下不可用Readers,因为Readers不支持non-blocking
        byte[] arr = new byte[1024];
        ByteBuffer buffer = ByteBuffer.wrap(arr);
        //读取字符串
        int numBytes = socketChannel.read(buffer);  // 服务端使用channel来读取,
        if (numBytes == -1) {
            System.out.println("[Warning!] A client has been closed.");
            closeChannel();  // 如果读完了,关闭连接,注意不是客户端发送-1,而是客户端发送exit
            // 因为客户端发送exit会导致出现client.close,这个时候服务端可以感应到
            return;
        }
        //将读取到的byte内容转为字符串类型
        String str = new String(arr);
        if ((str != null) && !str.equals(" ")) {
            //模拟逻辑处理
            process();  // 模拟逻辑处理  good
            System.out.println(socketChannel.socket().getRemoteSocketAddress().toString() + ">" + str);  // 打印客户端ip:port 及其 数据
            //  然后三步一体:将标志位修改为state=1,注册写事件,唤醒一个selector,执行run()方法,将数据写到客户端
            //改变状态
            state = 1;  // 已经读取完毕,就要修改状态,将将会结果发送给客户端
            //通过key改变通道注册的事件   SelectionKey.OP_WRITE  4
            selectionKey.interestOps(SelectionKey.OP_WRITE);   // 类变量selectionKey的感兴趣的操作设置为write  下一次使用 TCPHandlers selectedkey就是write
            //使一个阻塞住的selector操作立即返回
            //   selectionKey.selector().wakeup();    // 类变量selectionkey 得到这个key关联的selector,只有一个类变量selector,然后唤醒这个selector
        }
    }

    void process() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Client类

package com.reactor1;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Client {   // 客户端没什么好看的

    public static void main(String[] args) {
        String hostname = "127.0.0.1";
        int port = 1333;  // 服务端ip:port
        try {
            //连接至目的地
            Socket client = new Socket(hostname, port); // 建立socket连接
            System.out.println("连接至目的地:" + hostname);   // 第一,启动之后这里打印一句
            PrintWriter out = new PrintWriter(client.getOutputStream());   // 源头时client.getOutputStream() 就是本地socket需要输出到服务端的东西
            BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));  // 源头是client.getInputStream() 就是本地socket从服务端读取到的东西
            BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in)); // 源头时System.in  键盘输入
            String input;

            while ((input = stdIn.readLine()) != null) { //读取输入  输入不为空,循环不解释
                out.println(input); //发送输入的字符串  输入的东西发送给服务端
                out.flush();//强制将缓冲区的数据输出
                if (input.equals("exit")) {
                    break;   // 唯一退出条件
                }
                System.out.println("server:" + in.readLine());   // 打印从服务端读到的东西
            }
            client.close();   // 关闭socket连接  因为客户端发送exit会导致出现client.close,这个时候服务端可以感应到
            System.out.println("client stop.");  // 打印一下关闭
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.2 解释Reacter单线程模式

现在对手写Reactor单线程模式解释,Reactor单线程程序执行:Main --> Reactor–> Acceptor --> Handler。

对于Main类
开发者只需要传入端口就可以初始化服务端Reactor类,然后调用Reactor类的run()方法在main线程中启动。
值得注意的是,这里的Reactor Acceptor Handler都不是多线程,都是调用run()不是调用start(),不要被骗了。
run()方法:在主线程中执行方法,和调用普通方法一样;(按顺序执行,同步执行)
start()方法:创建了新的线程,在新的线程中执行;(异步执行)

Reactor就是对NIO封装,按照该功能封装为不同类,对于开发者来说,只要按照Main类中的,新建Reactor对象,传入ip:port,调用run()方法就好了,底层就是一个NIO。
Reactor类:用于客户端连接和分发(acceptor和read/write)
Acceptor类:accept接收连接请求
Handler类:read接收请求 write写回给客户端

对于Reactor类
变量:selector ServerSocketChannel
三要素都要是类变量 selector–channnel–buffer(读写才要buffer,连接不需要buffer)
方法1:Reactor类的构造函数
初始五句:初始化selector,初始化服务端channel,服务端channel绑定端口,服务端channel设置为非阻塞,服务端channel注册到selector上面,返回在selector中表示这个服务端channel的key;
最后一句:服务端这个channel去绑定一个服务端acceptor对象,用来接收请求的,参数为服务端channel和服务端selector。
方法2:Reactor类的run()方法
(1)当服务端线程没有被打断的时候while (!Thread.interrupted()),进入循环,selector.select() 表示若没有事件就绪则不往下执行,NIO的底层是linux非阻塞IO;
(2)在while迭代循环中,轮询取得所有已就绪事件的key集合,即当前类变量selector中的key集合,selectedKey被选择的指定的key,包括accept、read、write;
(3)根据事件的key进行分发调度,每次删除一个,和Reactor之前的nio一样,就是对其封装一次。
方法3:Reactor类的dispatch()方法
dispatch()方法中,对于传递到selector中的key,attach绑定为Runnable对象,涉及到多个绑定:
第一,Reactor类中,服务端启动的使用绑定Accept,客户端连接的时候使用;
第二,Accept类中,客户端连接的时候绑定READ,客户端输入的时候使用;
第三,Handler类中,客户端输入的时候切换为WRITE,输出给客户端的时候调用;
第四,Handler类中,输出给客户端的时候切换为READ,下一次客户端输入的时候使用。

对于Acceptor类:
变量:两个类变量,都是被TcpReactor的类变量赋值(TcpReactor类的构造函数)

对于Handler类:
方法1:TcpHandler类的send()方法
服务端使用一个channel读写,因为channel是双向的,api层面既有channel.read也有channel.write,而且每次都是channel.write(buffer) channel.read(buffer) 所以 channel+buffer==stream selector是类变量,服务端全局选择。
问题1:服务端的channel和stream如何联系起来?
回答1:不用联系,直接静态方法新建channel。
问题2:为什么分为服务端channel和客户端channel?
回答2:服务端channel,TcpReactor构造函数中,serverSocketChannel = ServerSocketChannel.open();客户端channel,Acceptor类中的run()方法,SocketChannel socketChannel = serverSocketChannel.accept(); 启动服务端后阻塞在这里。
问题3:如何理解channel?
回答3:服务端channel是打开来给客户端连接的,客户端channel表示服务端获取到的一个客户端连接,表示服务端获取到的一个客户端连接,这个对于channel的理解比较重要,写两遍。此外,服务端channel是open(),客户端channel是accept(),由此可见源码命名之优美。
方法2:TcpHandler类的read()方法
如果读完了,关闭连接,注意不是客户端发送-1,而是客户端发送exit,因为客户端发送exit会导致出现client.close,这个时候服务端可以感应到。

四、基于NIO的Reactor多线程模式

4.1 手写Reactor多线程模式

4.1.1 项目结构

在这里插入图片描述

HandlerState三个实现类,WorkState用来修改状态,ReadState用来完成服务端读出客户端传输的数据的操作,WriteState用来完成服务端写数据到客户端操作,将HandlerState类型的引用注册到TCPHandler类里面,其run()方法中直接调用

state.handle(this, sk, sc, pool);

这个模型中,TCPReactor单线程,Acceptor和TCPHandler里面,虽然实现Runnable接口,但是都只是在TCPReactor类中调用它们的run()方法,还是在main线程中执行其run()方法,但是TCPHandler里面有一个线程池,state.handle(this, sk, sc, pool); 表示读写操作是线程池中完成,这是这个模式的关键。

4.1.2 项目详细

Main类

package com.reactor2;

import java.io.IOException;

public class Main {

    // Reactor  多线程
    // Acceptor 多线程,处理acceptor请求
    // Handler  多线程,处理read,使用write写回去

    // 三种模型都是这样
    // Reactor:把IO事件分配给对应的handler处理
    // Acceptor:处理客户端连接事件
    // Handler HandlerState ReadState WriteState WorkState :处理非阻塞的任务

    public static void main(String[] args) {
        try {
            Reactor reactor = new Reactor(1333);  //使用者传入端口号,然后直接run
            reactor.run();  // 这里是run
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Reactor类

package com.reactor2;

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;

public class Reactor {  // main线程

    private final ServerSocketChannel ssc;
    private final Selector selector;

    public Reactor(int port) throws IOException {
        selector = Selector.open();   // selector
        ssc = ServerSocketChannel.open();  //  ServerSocketChannel
        // 在ServerSocketChannel绑定监听端口
        ssc.socket().bind(new InetSocketAddress(port));  // ServerSocketChannel 绑定端口
        // 设置ServerSocketChannel为非阻塞
        ssc.configureBlocking(false);  //  ServerSocketChannel  阻塞设置为false
        // ServerSocketChannel向selector注册一个OP_ACCEPT事件,然后返回该通道的key
        SelectionKey sk = ssc.register(selector, SelectionKey.OP_ACCEPT);  // ServerSocketChannel  op_accept
        sk.attach(new Acceptor(selector, ssc)); // 给定key一個附加的Acceptor对象
    }

    public void run() {
        while (!Thread.interrupted()) { // 在编程被中断前持续运行
            System.out.println("Waiting for new event on port: " + ssc.socket().getLocalPort() + "...");
            try {
                if (selector.select() == 0) // 若沒有事件就绪则不往下执行
                    continue;
            } catch (IOException e) {
                e.printStackTrace();
            }
            Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 取得所有已就绪事件的key集合
            Iterator<SelectionKey> it = selectedKeys.iterator();
            while (it.hasNext()) {
                dispatch((SelectionKey) (it.next())); // 根据事件的key进行调度
                it.remove();
            }
        }
    }

    private void dispatch(SelectionKey key) {
        Runnable r = (Runnable) (key.attachment()); // 根据事件之key绑定的对象开新线程
        if (r != null)
            r.run();   // Acceptor类run方法、
    }

}

Acceptor类

package com.reactor2;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class Acceptor implements Runnable {
    private final ServerSocketChannel ssc;
    private final Selector selector;

    public Acceptor(Selector selector, ServerSocketChannel ssc) {
        this.ssc = ssc;
        this.selector = selector;
    }

    @Override
    public void run() {
        try {
            // 接受client连接请求
            SocketChannel sc = ssc.accept();
            System.out.println(sc.socket().getRemoteSocketAddress().toString() + " is connected.");
            if (sc != null) {
                //设置为非阻塞
                sc.configureBlocking(false);
                // SocketChannel向selector注册一个OP_READ事件,然后返回该通道的key
                SelectionKey sk = sc.register(selector, SelectionKey.OP_READ);
                // 使一个阻塞住的selector操作立即返回
                selector.wakeup();
                // 给定key一个附加的TCPHandler对象
                sk.attach(new Handler(sk, sc));  // 继续绑定
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

HandlerState接口

package com.reactor2;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;

public interface HandlerState {

    void changeState(Handler h);   // 没使用过,可以去掉了

    void handle(Handler h, SelectionKey sk, SocketChannel sc,
                ThreadPoolExecutor pool) throws IOException;
}

ReadState类

package com.reactor2;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;

public class ReadState implements HandlerState {  // HandlerState有一个handle()方法,ReadState就是读操作,WriteState就是写操作

    private SelectionKey sk;

    public ReadState() {
    }

    @Override
    public void changeState(Handler h) {
        // TODO Auto-generated method stub
        h.setState(new WorkState());
    }

    @Override
    public void handle(Handler h, SelectionKey sk, SocketChannel sc,
                       ThreadPoolExecutor pool) throws IOException { // read()
        this.sk = sk;
        // non-blocking下不可用Readers,因为Readers不支持non-blocking
        byte[] arr = new byte[1024];
        ByteBuffer buf = ByteBuffer.wrap(arr);

        int numBytes = sc.read(buf); // 读取字符串
        if (numBytes == -1) {
            System.out.println("[Warning!] A client has been closed.");
            h.closeChannel();
            return;
        }
        String str = new String(arr); // 将读取到的byte內容转为字符串类型
        if ((str != null) && !str.equals(" ")) {
            h.setState(new WorkState()); // 改变状态(READING->WORKING)  设置读状态
            pool.execute(new WorkerThread(h, str)); // do process in worker thread   交给线程池去处理
            System.out.println(sc.socket().getRemoteSocketAddress().toString()
                    + " > " + str);      // 打印发送过来的
        }

    }

    /*
     * 执行逻辑处理之函数
     */
    synchronized void process(Handler h, String str) {
        // 三步一体
        h.setState(new WriteState()); // 改变状态WORKING->SENDING)
        this.sk.interestOps(SelectionKey.OP_WRITE); // 通过key改边通道的注册事件
        this.sk.selector().wakeup(); // 使一个阻塞住的selector操作立即返回
    }

    /*
     * 工作者线程
     */
    class WorkerThread implements Runnable {
        Handler h;
        String str;

        public WorkerThread(Handler h, String str) {
            this.h = h;
            this.str = str;
        }

        @Override
        public void run() {
            process(h, str);
        }   // 多线程要保证线程安全

    }
}

WorkState类

package com.reactor2;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;

public class WorkState implements HandlerState {   // WorkState 就是用来设置state的

    public WorkState() {
    }

    @Override
    public void changeState(Handler h) {
        h.setState(new WriteState());
    }

    @Override
    public void handle(Handler h, SelectionKey sk, SocketChannel sc,
                       ThreadPoolExecutor pool) throws IOException {
    }
}

WriteState类

package com.reactor2;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;

public class WriteState implements HandlerState {

    public WriteState() {
    }

    @Override
    public void changeState(Handler h) {
        h.setState(new ReadState());
    }   // 设置状态

    @Override
    public void handle(Handler h, SelectionKey sk, SocketChannel sc,
                       ThreadPoolExecutor pool) throws IOException { // send()
        String str = "Your message has sent to "
                + sc.socket().getLocalSocketAddress().toString() + "\r\n";  // 写回给服务端的内容
        ByteBuffer buf = ByteBuffer.wrap(str.getBytes()); // wrap自动把buf的position设为0,所以不需要再flip()

        while (buf.hasRemaining()) {
            sc.write(buf); // 回传给client回传字符串,发送buf的position位置到limit位置为止之间的內容
        }

        // 三步一体 good
        h.setState(new ReadState()); // 改变状态(SENDING->READING)
        sk.interestOps(SelectionKey.OP_READ); // 通过key改变通道注册的事件
        sk.selector().wakeup(); // 使一个阻塞住的selector操作立即返回
    }
}

Handler类

package com.reactor2;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Handler implements Runnable {   // Handler 变成了 Reactor Thread Pool 因为他注入了一个state和pool 处理IO的线程池
    private final SelectionKey sk;
    private final SocketChannel sc;
    private static final int THREAD_COUNTING = 10;  // 处理IO的线程数为10
    private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(
            THREAD_COUNTING, THREAD_COUNTING, 10, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()); // 线程池  保证插入顺序的队列

    HandlerState state; // 以状态模式实现Handler

    public Handler(SelectionKey sk, SocketChannel sc) {
        this.sk = sk;
        this.sc = sc;
        state = new ReadState(); // 初始状态设定为READING   还是初始读状态
        pool.setMaximumPoolSize(32); // 设置线程池的最大线程数    默认线程数为10,最大线程数为32
    }

    @Override
    public void run() {
        try {
            state.handle(this, sk, sc, pool);   // 调用state的handle()方法
        } catch (IOException e) {
            System.out.println("[Warning!] A client has been closed.");
            closeChannel();  // 出问题了关闭
        }
    }

    public void closeChannel() {
        try {
            sk.cancel();
            sc.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }

    public void setState(HandlerState state) {
        this.state = state;   // setState就是根据参数设置
    }
}

Client:无变化,略。

4.2 解释Reactor多线程模式

金手指:Reactor多线程模式

(1)服务端用于接收客户端连接的是个 1 个单独的 NIO 线程,然后,一组 NIO 线程上面完成所有的 IO 操作;

(2)Reactor 多线程模型有专门一个NIO 线程——Acceptor 线程用于监听服务端,接收客户端的 TCP 连接请求; 网络 IO 操作-读、写等由一个 NIO 线程池负责,线程池可以采用标准的 JDK 线程池实现,它包含一个任务队列和 N个可用的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送。

五、基于NIO的Reactor主从多线程模式

5.1 手写Reactor主从多线程模式

5.1.1 项目结构

在这里插入图片描述

5.1.2 项目详细

Main类

package com.reactor3;

import java.io.IOException;

public class Main {

    //    三种模型都是这样
    //    Reactor  SubReactor  :把IO事件分配给对应的handler处理    主从Reactor  为什么一主一从
    //    Acceptor:处理客户端连接事件
    //    Handler HandlerState ReadState WriteState WorkState :处理非阻塞的任务

    //  第一个good: 当Acceptor处理连接事件后,主reactor将连接分配给从Reactor  good
    //  从Reactor将连接加入到连接队列进行监听,并创建handler进行各种事件处理
    //  当有新事件发生时,从reactor就会对用对应的handler处理
    //  handler读取数据后,分发给后面的worker线程处理
    //   worker线程池分配独立的worker线程进行处理并返回结果
    //  handler收到结果后再讲结果返回给客户端

    // 第二个good   mainReactor只处理连接事件,读写事件交给subReactor来处理。   懂了
    // 业务逻辑还是由线程池来处理,mainRactor只处理连接事件,用一个线程来处理就好。
    // 处理读写事件的subReactor个数一般和CPU数量相等,一个subReactor对应一个线程,业务逻辑由线程池处理

    public static void main(String[] args) {
        try {
            Reactor reactor = new Reactor(1333);   // 这里新建一个TCPReactor,新建一个Acceptor,新建8个TCPSubReactor并子线程运行,
            // 所以对于8个TCPSubReactor,都会阻塞在run()中的   if (selector.select() == 0) 中的一句
            // 但是只是8个子线程的阻塞(所以这就是Acceptor类中新建8个TCPSubReactor类使用start()运行的原因,
            // 因为其run()中的selector.select()一定会阻塞,不要阻塞main线程)  main线程可以继续执行,看下面
            Thread thread = new Thread(reactor);
            thread.start();  //  为什么这里需要TCPReactor.start(),为什么不在main线程中执行
            // 表示新建子线程执行TCPReactor中的run()方法,但是TCPReactor的run()方法没有synchronized,
            // 第一start 第一个synchronized  就是因为start才需要synchronized
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Reactor类

package com.reactor3;

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.util.Iterator;
import java.util.Set;

public class Reactor implements Runnable {

    private final ServerSocketChannel ssc;
    private final Selector selector; // mainReactor用的selector

    public Reactor(int port) throws IOException {
        selector = Selector.open();   // selector
        ssc = ServerSocketChannel.open();  // ServerSocketChannel
        ssc.configureBlocking(false); // 设置ServerSocketChannel为非阻塞

        ssc.socket().bind( new InetSocketAddress(port)); // 在ServerSocketChannel绑定监听端口

        // 所有的都注册到同一个selector上面  这里是启动的时候注册到selector上面,等待连接过来   主线程服务端channel只要接收accept good
        SelectionKey sk = ssc.register(selector, SelectionKey.OP_ACCEPT); // ServerSocketChannel向selector注册一个OP_ACCEPT事件,然后返回该通道的key
        sk.attach(new Acceptor(ssc)); // 给定key一个附加的Acceptor对象

    }

    @Override
    public void run() {
        while (!Thread.interrupted()) { // 在线程被中断前持续运行
            System.out.println("mainReactor waiting for new event on port: "
                    + ssc.socket().getLocalPort() + "...");    // 第二,启动的时候这里打印一句
            try {
                if (selector.select() == 0) // 若沒有事件就绪则不往下执行   启动后这里一定会阻塞 ,有了连接过来之后才放开
                    continue;
            } catch (IOException e) {
                e.printStackTrace();
            }
            Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 取得所有已就绪事件的key集合
            Iterator<SelectionKey> it = selectedKeys.iterator();
            while (it.hasNext()) {
                dispatch((SelectionKey) (it.next())); // 根据事件的key进行调度
                it.remove();
            }
        }
    }

    private void dispatch(SelectionKey key) {
        Runnable r = (Runnable) (key.attachment()); // 根据事件之key绑定的对象开新线程
        if (r != null)
            r.run();
    }

}

SubReactor类

package com.reactor3;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;

public class SubReactor implements Runnable {   // 多线程

    private final ServerSocketChannel ssc;
    private final Selector selector;
    private boolean restart = false;
    int num;

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

    //SubReactor Reactor 和 Accept 的构造函数中执行  good  被阻塞了  当有用户请求发送过来的时候,accept read write
    //  再次打印了两三个 System.out.println("waiting for restart");
    // 两三个突破阻塞 ,并进行请求分发,交给对应的Accpet ReadState WriteState来处理

    @Override
    public void run() {   // 什么时候调用这个run方法
        while (!Thread.interrupted()) { // 在线程被中断前持续运行
            System.out.println("waiting for restart");   // 第一,启动的时候这里打印8个;第四,连接过来这里打印两句
            while (!Thread.interrupted() && !restart) { // 在线程被中断前以及被指定重启前持续运行
                try {
                    if (selector.select() == 0)
                        continue; // 若沒有事件就绪则不往下执行
                } catch (IOException e) {
                    e.printStackTrace();
                }
                Set<SelectionKey> selectedKeys = selector.selectedKeys(); // 取得所有已就绪事件的key集合
                Iterator<SelectionKey> it = selectedKeys.iterator();
                while (it.hasNext()) {
                    dispatch((SelectionKey) (it.next())); // 根据事件的key进行调度
                    it.remove();
                }
            }
        }
    }

    private void dispatch(SelectionKey key) {
        Runnable r = (Runnable) (key.attachment()); // 根据事件之key绑定的对象开启线程
        if (r != null)
            r.run();  // 对于每一个TCPSubReactor,调用它们的run()
    }

    public void setRestart(boolean restart) {
        this.restart = restart;
    }
}

Acceptor类

package com.reactor3;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class Acceptor implements Runnable {   // 之前的Accept变成了Accept线程池,因为这里面注入了一个TCPSubReactor[]

    private final ServerSocketChannel ssc; // mainReactor监听的socket通道
    private final int cores = Runtime.getRuntime().availableProcessors(); // 取得CPU核心数
    private final Selector[] selectors = new Selector[cores]; // 创建核心数个selector给subReactor用
    private int selIdx = 0; // 当前可使用的subReactor索引
    private SubReactor[] r = new SubReactor[cores]; // subReactor线程    这里开辟CPU核心数个子线程
    private Thread[] t = new Thread[cores]; // subReactor线程

    public Acceptor(ServerSocketChannel ssc) throws IOException {
        this.ssc = ssc;
        // 创建多个selector以及多个subReactor线程
        for (int i = 0; i < cores; i++) {
            selectors[i] = Selector.open();
            r[i] = new SubReactor(selectors[i], ssc, i);   //Acceptor里面新建一个TCPSubReactor线程组
            t[i] = new Thread(r[i]);
            t[i].start();  //Acceptor构造函数中,这里是TCPSubReactor.start(),表示新建子线程执行TCPSubReactor的run()方法,
            // 但是TCPSubReactor类的run()
        }
    }

    @Override
    public synchronized void run() {   // run()方法要使用同步的,之前都没有用到同步啊  Acceptor的run()方法
        // 为什么Acceptor的run上面使用synchronized 调用这个run的是TCPReactor里面的run()里面的dispatch()方法,直接调用run()方法
        // 而TCPReactor里面的run(),使用Main类中的start()调用
        try {
            SocketChannel sc = ssc.accept(); // 接受client连接请求
            System.out.println(sc.socket().getRemoteSocketAddress().toString()
                    + " is connected.");    // 第三,连接过来之后,这里打印一句
            if (sc != null) {
                sc.configureBlocking(false); // 设置为非阻塞   之前

                // 安排新任务的核心就是将当前的SocketChannel sc注册到指定的selector[selIdx]上面去
                r[selIdx].setRestart(true); // 暂停线程  将selIdx序号TCPSubReactor线程暂停,因为要给他安排新任务
                selectors[selIdx].wakeup(); // 使一个阻塞住的selector操作立即返回,要使用selectors[selIdx],要将channel注册到这个selectors[selIdx]上面去,
                // 所以要先唤醒它
                SelectionKey sk = sc.register(selectors[selIdx],
                        SelectionKey.OP_READ); // SocketChannel向selector[selIdx]注册一个OP_READ事件,然后返回该通道的key,
                // 这个很重要,表示客户端连接的channel,用来注册到线程池中第一个元素上面  selectors[selIdx]
                // 到底是哪一个元素 selIdx 表示 当前可使用的subReactor索引 ,初始值为0
                selectors[selIdx].wakeup(); // 使一个阻塞住的selector操作立即返回
                r[selIdx].setRestart(false); // 重启线程,将selIdx序号TCPSubReactor线程重启,任务安排好了

                // selectors[selIdx] 和  r[selIdx] 的关系是如何联系在一起的,
                // 回答:不会怎样特别连接,反正Acceptor类的时候,就使用Selector.open();得到一个selector对象,使用 selectors[i] 引用
                // 然后使用这个 selectors[i] 新建一个TcpSubReactor线程   r[i] = new SubReactor(selectors[i], ssc, i);
                // 并执行这个线程     t[i] = new Thread(r[i])  t[i].start();

                //然后,对于run()方法的执行,将新创建的 SocketChannel 注册到 IO 线程池(subReactor 线程池)的某个 IO 线程上,
                // 由它负责SocketChannel 的读写和编解码工作,就是绑定到相应的TCPHandler上面   sk.attach(new Handler(sk, sc));

                // TCPHandler两个参数  一个key 一个客户端channel  默认state为readState  线程最大数量为32
                // TCPHandler里面注入state  state就是读写
                //  很重要1: Acceptor只接受请求,然后就是交给TCPHandler  TCPHandler里面注入state  state就是读写
                // Acceptor 线程池仅仅只用于客户端的登陆、握手和安全
                // 认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 IO 线程上,由 IO 线程负
                //  责后续的 IO 操作。表示Acceptor只接受请求,然后就是交给TCPHandler  TCPHandler里面注入state  state就是读写
                // 很重要2:上面我们注册sc就是为了得到一个key ,这个key就是为构造TCPHandler准备的

                // 这里注册到TCPHandler,那么TCPHandler的run()什么时候执行呢  就是在

                sk.attach(new Handler(sk, sc)); // 给定key一个附加的TCPHandler对象

                if (++selIdx == selectors.length)  // 两种情况解释:如果selIdx++== selectors.length 就是 selIdx== selectors.length -1,就是到了最后一个
                    // 就循环,如果没有到最后一个,不不会执行 selIdx = 0;  反正selIdx还是要++的
                    selIdx = 0;

                //  之前就四句  这里加上而已  什么使用调用SubReactor的run()
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

HandlerState接口

package com.reactor3;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;

public interface HandlerState {

    void changeState(Handler h);

    void handle(Handler h, SelectionKey sk, SocketChannel sc,
                ThreadPoolExecutor pool) throws IOException;
}

ReadState类

package com.reactor3;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;

public class ReadState implements HandlerState {

    private SelectionKey sk;

    public ReadState() {
    }

    @Override
    public void changeState(Handler h) {
        h.setState(new WorkState());
    }

    @Override
    public void handle(Handler h, SelectionKey sk, SocketChannel sc,
                       ThreadPoolExecutor pool) throws IOException { // read()
        this.sk = sk;
        // non-blocking下不可用Readers,因为Readers不支持non-blocking
        byte[] arr = new byte[1024];
        ByteBuffer buf = ByteBuffer.wrap(arr);

        int numBytes = sc.read(buf); // 读取字符串
        if (numBytes == -1) {
            System.out.println("[Warning!] A client has been closed.");
            h.closeChannel();
            return;
        }
        String str = new String(arr); // 将读取到的byte內容转为字符串类型
        if ((str != null) && !str.equals(" ")) {
            h.setState(new WorkState()); // 改变状态(READING->WORKING)
            pool.execute(new WorkerThread(h, str)); // do process in worker thread
            System.out.println(sc.socket().getRemoteSocketAddress().toString()
                    + " > " + str);
        }

    }

    /*
     * 执行逻辑处理之函数
     */
    synchronized void process(Handler h, String str) {
        h.setState(new WriteState()); // 改变状态(WORKING->SENDING)
        this.sk.interestOps(SelectionKey.OP_WRITE); // 通过key改变通道注册的事件
        this.sk.selector().wakeup(); // 使一个阻塞住的selector操作立即返回
    }

    /*
     * 工作者线程
     */
    class WorkerThread implements Runnable {

        Handler h;
        String str;

        public WorkerThread(Handler h, String str) {
            this.h = h;
            this.str = str;
        }

        @Override
        public void run() {
            process(h, str);
        }

    }
}

WorkState类

package com.reactor3;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;

public class WorkState implements HandlerState {

    public WorkState() {
    }

    @Override
    public void changeState(Handler h) {
        h.setState(new WriteState());
    }

    @Override
    public void handle(Handler h, SelectionKey sk, SocketChannel sc,
                       ThreadPoolExecutor pool) throws IOException {

    }

}

WriteState类

package com.reactor3;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ThreadPoolExecutor;

public class WriteState implements HandlerState {

    public WriteState() {
    }

    @Override
    public void changeState(Handler h) {
        h.setState(new ReadState());
    }

    @Override
    public void handle(Handler h, SelectionKey sk, SocketChannel sc,
                       ThreadPoolExecutor pool) throws IOException { // send()

        String str = "Your message has sent to "
                + sc.socket().getLocalSocketAddress().toString() + "\r\n";
        ByteBuffer buf = ByteBuffer.wrap(str.getBytes()); // wrap自动把buf的position设为0,所以不需要再flip()

        while (buf.hasRemaining()) {
            sc.write(buf); // 回传给client回传字符串,发送buf的position位置 到limit位置为止之间的內容
        }

        // 三步一体
        h.setState(new ReadState()); // 改变状态(SENDING->READING)
        sk.interestOps(SelectionKey.OP_READ); // 通过key改变通道注册的事件
        sk.selector().wakeup(); // 使一个阻塞住的selector操作立即返回
    }
}

Handler类

package com.reactor3;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Handler implements Runnable { // 这里就是处理IO的线程池  从线程池

    private final SelectionKey sk;   // 一个key
    private final SocketChannel sc;   // 一个客户端channel
    private static final int THREAD_COUNTING = 10;
    private static final ThreadPoolExecutor pool = new ThreadPoolExecutor(
            THREAD_COUNTING, THREAD_COUNTING, 10, TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()); // 线程池

    HandlerState state; // 以状态模式实现Handler

    public Handler(SelectionKey sk, SocketChannel sc) {
        this.sk = sk;
        this.sc = sc;
        state = new ReadState(); // 初始状态设定为READING
        pool.setMaximumPoolSize(32); // 设置线程池的最大线程数
    }

    @Override
    public void run() {
        try {
            state.handle(this, sk, sc, pool);   //
        } catch (IOException e) {
            System.out.println("[Warning!] A client has been closed.");
            closeChannel();
        }
    }

    public void closeChannel() {
        try {
            sk.cancel();
            sc.close();
        } catch (IOException e1) {
            e1.printStackTrace();
        }
    }

    public void setState(HandlerState state) {
        this.state = state;
    }
}

Client:无变化,略。

5.2 解释Reactor主从多线程模式

金手指:Reactor主从线程池

含义:服务端用于接收客户端连接的是一个独立的 NIO 线程池,而且由一组 NIO 线程上面完成所有的 IO 操作。

解释:Acceptor 接收到客户端 TCP 连接请求处理完成后(可能包含接入认证等),将新创建的SocketChannel 注册到 IO 线程池(sub reactor 线程池)的某个 IO 线程上,由它负责SocketChannel 的读写和编解码工作。Acceptor 线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 IO 线程上,由 IO 线程负责后续的 IO 操作。

金手指:梳理下基于Reactor主从多线程模型的事件处理过程

Reactor主线程对象通过select监听连接事件,通过Acceptor处理连接事件,当Acceptor处理连接事件后,主reactor将连接分配给从Reactor。

从Reactor将连接加入到连接队列进行监听,并创建handler进行各种事件处理,当有新事件发生时,从reactor就会对用对应的handler处理handler读取数据后,分发给后面的worker线程处理worker线程池分配独立的worker线程进行处理并返回结果,handler收到结果后再讲结果返回给客户端

所以,在主从Reactor多线程模型中,父线程与子线程之间数据交互简单、责任明确,父线程只需接收新连接,后续的处理交给子线程完成即可;主从Reactor多线程模型中,Reactor线程拆分为mainReactor和subReactor两个部分,mainReactor只处理连接事件,读写事件交给subReactor来处理。业务逻辑还是由线程池来处理,mainRactor只处理连接事件,用一个线程来处理就好。处理读写事件的subReactor个数一般和CPU数量相等,一个subReactor对应一个线程,业务逻辑由线程池处理。

六、面试金手指

Reactor模型是基于事件驱动的线程模型,可以分为Reactor单线程模型、Reactor多线程模型、主从Reactor多线程模型,通常基于在I/O多路复用实现。三个不同的角色包括:Dispatcher、Acceptor、Handler。

Reactor:把IO事件分配给对应的handler处理
Acceptor:处理客户端连接事件
Handler:处理非阻塞/非连接事件,如读写事件

Reactor单线程模型中,
Reactor:把IO事件分配给对应的handler处理;
Acceptor:处理客户端连接事件(单线程处理连接事件,Acceptor实现Runnable接口忽略);
Handler:处理非阻塞的任务(单线程处理读写事件,Handler实现Runnable接口忽略)。

Reactor多线程模型中,
Reactor:把IO事件分配给对应的handler处理;
Acceptor:处理客户端连接事件(单线程处理连接事件,Acceptor实现Runnable接口忽略);
Handler:处理非阻塞的任务(Handler类中线程池处理读写事件,Handler实现Runnable接口忽略)。

Reactor主从多线程模型中,
Reactor:把IO事件分配给对应的handler处理;
Acceptor:处理客户端连接事件(Acceptor类中线程池处理连接事件,Acceptor实现Runnable接口忽略);
Handler:处理非阻塞的任务(Handler类中线程池处理读写事件,Handler实现Runnable接口忽略)。

6.1 Reactor单线程模型

1、原理图示

在Reactor单线程模型中,操作在同一个Reactor线程中完成。根据事件的不同类型,由Dispatcher将事件转发到不同的角色中处理。连接事件转发到Acceptor处理、读写事件转发到不同的Handler处理。

在这里插入图片描述

2、实现图示

NIO实现中,可以将Accept事件注册到select选择器中,轮询是否有“接受就绪”事件。如果为“连接就绪”分发给Acceptor角色处理;“写就绪”事件分发给负责写的Handler角色处理;“读就绪”事件分发给负责读的Handler角色处理。这是事情都在一个线程中处理。

在这里插入图片描述

6.2 Reactor多线程模型

1、原理图示

在Reactor多线程模型中。根据事件的不同类型,由Dispatcher将事件转发到不同的角色中处理。连接事件转发到Acceptor单线程处理、读写事件转发到不同的Handler由线程池处理。

在这里插入图片描述

2、实现图示

NIO实现中,可以将Accept事件注册到select选择器中,轮询是否有“接受就绪”事件。如果为“连接就绪”分发给Acceptor角色处理,此处处理“连接就绪”为一个线程;“写就绪”事件分发给负责写的Handler角色由线程池处理;“读就绪”事件分发给负责读的Handler角色由线程池处理。

在这里插入图片描述

6.3 Reactor主从多线程模型

1、原理图示

Reactor多线程模型,由Acceptor接受客户端连接请求后,创建SocketChannel注册到Main-Reactor线程池中某个线程的Select中;具体处理读写事件还是使用线程池处理(Sub-Reactor线程池)。

在这里插入图片描述

2、实现图示

将Accept事件注册到select选择器中,轮询是否有“接受就绪”事件;“连接就绪”分发给Acceptor角色处理,创建新的SocketChannel转发给Main-Reactor线程池中的某个线程处理;在指定的Main-Reactor某个线程中,将SocketChannel注册读写事件;当“写就绪/读就绪”事件分别由线程池(Sub-Reactor线程池)处理。

在这里插入图片描述

七、尾声

基于NIO的Reactor三种模式,完成了。

天天打码,天天进步!!!

源工程代码:

https://download.csdn.net/download/qq_36963950/12713959

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

祖母绿宝石

打赏一下

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值