NIO--主从reactor模式学习--孙哥&不良人课程,整理学习笔记文章 【https://space.bilibili.com/284638819】

92 阅读4分钟

NIO (New I/O) 是 Java 中的一种非阻塞 I/O 处理机制,它允许你使用单线程或有限数量的线程来处理多个客户端连接。NIO 提供了一种高效的方式来开发网络应用程序,其中主从 Reactor 模式是一种常见的设计模式之一,用于处理并发的网络请求。

主从 Reactor 模式的核心思想是将网络请求的处理分为两个主要部分:主 Reactor 和从 Reactor。主 Reactor 负责接收客户端连接请求,并分派给从 Reactor 处理具体的网络 I/O 操作。这种模式允许你在一个线程池中高效地管理多个并发连接。

下面是主从 Reactor 模式的基本工作流程:

  1. 主 Reactor:主 Reactor 负责监听服务器端口,等待客户端的连接请求。一旦有连接请求到达,主 Reactor 接受连接并将连接注册到从 Reactor 上。主 Reactor 可以是单线程或者少量线程。

  2. 从 Reactor:从 Reactor 负责处理已连接的客户端,包括读取数据、处理请求、发送响应等操作。从 Reactor 通常有一个线程池,允许并行处理多个连接。

  3. 事件分发:主 Reactor 和从 Reactor 都需要实时监听事件(如连接、读、写等),并将事件分发给相应的处理程序。这可以使用事件循环或选择器(Selector)来实现。

  4. 非阻塞 I/O:NIO 使用非阻塞 I/O 操作,这意味着每个连接都可以在不阻塞整个应用程序的情况下进行读取和写入操作。

  5. 多路复用:通过 Selector,NIO 可以同时管理多个连接,这可以减少线程数量,提高性能。

  6. 线程安全:在多线程环境下,必须确保数据共享和并发访问的线程安全。

使用主从 Reactor 模式时,你需要学习 Java NIO API,包括 SelectorServerSocketChannelSocketChannel 等类,以及如何使用它们来实现服务器端和客户端的网络通信。你还需要了解如何处理事件和管理多个连接。

NIO 和主从 Reactor 模式在高性能网络编程中非常有用,但也需要深入的理解和编程技巧。你可以查阅相关的文档和教程,以帮助你更深入地学习这些主题。


1.  服务器读操作  write---->client读

2.

3.  reactor模式    多路复用
    1.  单线程版 ---> netty

多路复用器  Selector  分发的工作
连接器  Accept     SelectKey.isAcceptable   ssc.accpet()
存在问题:即负责连接,又负责读写,效率低

2.  主从版(主从架构  master slave(从属  努力)------》  SM----》M  奴隶

主备架构    master  backup()
Mysql  Redis 存储产品   读写功能分离 概念     是主从架构
主从架构:   主干活  从也干活
不一样的活     主要写 主   次要读从

反向代理--服务端

正向代理--client--VPN
主从reactor模式
  
public class ReactorBossServer {

    private static final Logger log = LoggerFactory.getLogger(ReactorBossServer.class);

    public static void main(String[] args) throws IOException, InterruptedException {
        log.debug("boss thread start ....");

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(8000));

        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        //模拟多线程的环境,在实际开发中,还是要使用线程池
        /*
        Worker worker = new Worker("worker1");
        */

        Worker[] workers = new Worker[2];
        for (int i = 0; i < workers.length; i++) {
            workers[i] = new Worker("worker - " + i);//worker-0 worker-1
        }

        AtomicInteger index = new AtomicInteger();


        while (true) {
            selector.select();

            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey sscSelectionKey = iterator.next();
                iterator.remove();

                if (sscSelectionKey.isAcceptable()) {
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);

                    //sc.register(selector, SelectionKey.OP_READ);
                    log.debug("boss invoke worker register ...");
                    //worker-0 worker-1 worker-0 worker-1
                    //hash取摸    x%2= 0  1 [0,1,0,1]
                    workers[index.getAndIncrement()% workers.length].register(sc);
                    log.debug("boss invoked worker register");
                }
            }
        }


    }
}
//从  处理  READ  WRITER

public class Worker implements Runnable {
    private static final Logger log = LoggerFactory.getLogger(Worker.class);
    private Selector selector;
    private Thread thread;
    private String name;

    private volatile boolean isCreated;//false

    private ConcurrentLinkedQueue<Runnable> runnables = new ConcurrentLinkedQueue<>();
    //构造方法

    //为什么不好?
    //Select Thread
    public Worker(String name) throws IOException {
        this.name = name;
       /* thread = new Thread(this, name);
        thread.start();
        selector = Selector.open();*/
    }

    //线程的任务
    public void register(SocketChannel sc) throws IOException, InterruptedException {
        log.debug("worker register invoke....");
        if (!isCreated) {
            thread = new Thread(this, name);
            thread.start();
            selector = Selector.open();
            isCreated = true;
        }
        runnables.add(() -> {
            try {
                sc.register(selector, SelectionKey.OP_READ);//reigster  select方法之前运行 。。
            } catch (ClosedChannelException e) {
                throw new RuntimeException(e);
            }
        });
        selector.wakeup();//select
    }

    @Override
    public void run() {
        while (true) {
            log.debug("worker run method invoke....");
            try {
                selector.select();

                Runnable poll = runnables.poll();
                if (poll != null) {
                    poll.run();
                }

                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey sckey = iterator.next();
                    iterator.remove();

                    if (sckey.isReadable()) {
                        SocketChannel sc = (SocketChannel) sckey.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(30);
                        sc.read(buffer);
                        buffer.flip();
                        String result = Charset.defaultCharset().decode(buffer).toString();
                        System.out.println("result = " + result);
                    }

                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }
    }
}
iterator.remove()
把用过的SelectKey从SelectKeys集合中剔除,避免没意义的循环
selectKey.cancel()
把一些无法实际解决的内容,通过cancel来取消。避免每一次都被select()获取到,重新仅需循环过程
读取数据:

1.  通过附件的形式,把byteBuffer和channel进行绑定,从而可以多次处理数据
2.  ByteBuffer的扩容

数据的写出:

1.  第一个问题,写一次数据,当数据没有写完,设置WRITE监听状态
2.  每一次发生Write的状态,都把数据写出去

主从reactor模式
5.3 零拷贝的问题

:::info JVM 不能直接操作硬件,需要操作系统

内存 应用程序(JVM) 用户地址空间 进程级 OS 内核地址空间的 内存是独立的,但是能通信----> OS API 贵的资源--->自身java代码--->OS--->关闭资源 :::

1. 什么是零拷贝 【概念】