Java NIO Reactor 模式

相比于 Java BIO 一请求一线程的模式,底层使用 epoll 的 Java NIO 的 IO 多路复用模式可以处理更多的请求。

一个简单的NIO demo 一般就是这样,只有一个主线程完成监听和处理工作,可以同时处理多个请求。

java 复制代码
public class NioMain {
    private static final int PORT = 9999;

    private Selector selector;

    private ServerSocketChannel serverSocketChannel;

    public static void main(String[] args) throws IOException {
        new NioMain().startServer();
    }

    private void startServer() {
        try {
            selector = Selector.open();
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            serverChannel.configureBlocking(false);
            serverChannel.socket().bind(new InetSocketAddress(PORT));
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("NIO 服务器启动成功,监听端口:" + PORT);
            while (true) {
                int selectChannels = selector.select();
                if (selectChannels == 0) {
                    continue;
                }
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey selectionKey = keyIterator.next();
                    keyIterator.remove();
                    if (selectionKey.isAcceptable()) {
                        handleAccept(selectionKey);
                    } else if (selectionKey.isReadable()) {
                        handleRead(selectionKey);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (serverSocketChannel != null) serverSocketChannel.close();
                if (selector != null) selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    private void handleRead(SelectionKey key) {
        try {
            SocketChannel clientChannel = (SocketChannel) key.channel();
            // 获取客户端专属的读缓冲区
            ByteBuffer readBuffer = (ByteBuffer) key.attachment();
            readBuffer.clear(); // 重置缓冲区:position=0, limit=capacity

            // 1. 读取客户端数据(非阻塞,返回读取的字节数)
            int readBytes = clientChannel.read(readBuffer);
            if (readBytes == -1) {
                // 客户端关闭连接
                System.out.println("客户端断开连接:" + clientChannel.getRemoteAddress());
                clientChannel.close();
                key.cancel();
                return;
            }
            if (readBytes == 0) {
                return; // 无数据可读,直接返回
            }

            // 2. 解码完整字符串(关键:flip() 切换为读模式)
            readBuffer.flip(); // position=0, limit=已读取字节数
            // 读取实际有效的字节(避免空字节干扰)
            byte[] data = new byte[readBuffer.remaining()];
            readBuffer.get(data);
            String receivedMsg = new String(data, StandardCharsets.UTF_8).trim();
            System.out.println("收到客户端消息:" + receivedMsg);

            // 3. 处理退出指令
            if ("exit".equalsIgnoreCase(receivedMsg)) {
                System.out.println("客户端主动断开连接:" + clientChannel.getRemoteAddress());
                clientChannel.close();
                key.cancel();
                return;
            }

            // 4. 回复客户端(核心修复:循环写入,确保完整发送)
            String response = "服务器已接收:" + receivedMsg + "\r\n"; // 加换行,客户端可换行显示
            ByteBuffer writeBuffer = StandardCharsets.UTF_8.encode(response);
            // 非阻塞写可能只发送部分字节,需循环写入直到全部发送
            while (writeBuffer.hasRemaining()) {
                clientChannel.write(writeBuffer);
            }

            // 5. 重置读缓冲区,准备下次读取
            readBuffer.clear();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void handleAccept(SelectionKey selectionKey) {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
        try {
            SocketChannel clientChannel = serverSocketChannel.accept();
            clientChannel.configureBlocking(false);
            SocketAddress remoteAddress = clientChannel.getRemoteAddress();
            System.out.println("客户端[" + remoteAddress + "]连接成功:" + remoteAddress);
            clientChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
        } catch (IOException e) {
            throw new RuntimeException(e);
        } 
    }
}

如果请求过多,一般会分成两个部分,一个部分即主线程负责处理客户端的连接请求,每个主线程持有一个 ServerSocketChannel,当它监听到有新连接时;另一个部分即工作线程池负责处理已连接客户端的读写请求,每一个工作线程持有一个 Selector 选择器,在客户端建立连接时把客户端 Channel 注册到这个选择器上,后续的读写请求都通过唤醒这个 工作Selector 来进行处理。这就是 Reactor 模式,

java 复制代码
public class MasterSlaveReactorServer {
    // 监听端口
    private static final int PORT = 9999;
    // CPU核心数(用于计算从Reactor线程数)
    private static final int CPU_CORES = Runtime.getRuntime().availableProcessors();
    // 主Reactor线程数(建议1~2个)
    private static final int MASTER_THREAD_NUM = 2;
    // 从Reactor线程数(CPU核心数*2)
    private static final int SLAVE_THREAD_NUM = 8 ;

    // 主Reactor线程池(处理Accept事件)
    private final ExecutorService masterExecutor;
    // 从Reactor线程池(处理Read/Write事件)
    private final ExecutorService slaveExecutor;
    // 从Reactor的Selector数组(每个从Reactor一个Selector)
    private final Selector[] slaveSelectors;
    // 轮询索引(负载均衡:分配连接到不同从Reactor)
    private final AtomicInteger slaveIndex = new AtomicInteger(0);

    public static void main(String[] args) throws IOException {
        MasterSlaveReactorServer server = new MasterSlaveReactorServer();
        server.start();

        Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown));
    }

    public MasterSlaveReactorServer() throws IOException {

        this.masterExecutor = Executors.newFixedThreadPool(MASTER_THREAD_NUM);
        // 初始化从Reactor线程池(固定线程数)
        this.slaveExecutor = Executors.newFixedThreadPool(SLAVE_THREAD_NUM);
        // 初始化从Reactor的Selector
        this.slaveSelectors = new Selector[SLAVE_THREAD_NUM];
        for (int i = 0; i < SLAVE_THREAD_NUM; i++) {
            slaveSelectors[i] = Selector.open();
            // 启动从Reactor线程
            slaveExecutor.execute(new SlaveReactorRunnable(slaveSelectors[i]));
        }
    }

    public void start() throws IOException {


        System.out.println("多线程主Reactor服务器启动,监听端口:" + PORT);
        System.out.println("主Reactor线程数:" + MASTER_THREAD_NUM + ",从Reactor线程数:" + SLAVE_THREAD_NUM);
        // 启动多个主Reactor线程(每个线程绑定同一个端口)
        for (int i = 0; i < MASTER_THREAD_NUM; i++) {
            // 每个主Reactor线程创建独立的ServerSocketChannel(共享端口)
            ServerSocketChannel serverChannel = createServerSocketChannel();
            // 提交主Reactor任务
            masterExecutor.execute(new MasterReactorRunnable(serverChannel, i));
        }


    }

    /**
     * 创建ServerSocketChannel(开启端口复用,支持多线程共享端口)
     */
    private ServerSocketChannel createServerSocketChannel() throws IOException {
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);
        // 核心:开启端口复用,允许多个 ServerSocketChannel 绑定同一端口
        serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
        // 增大监听队列长度(Accept队列)
//        serverChannel.setOption(StandardSocketOptions.SO_BACKLOG, 65535);
        // 绑定端口(多个主Reactor线程的ServerSocketChannel都绑定此端口)
        serverChannel.socket().bind(new InetSocketAddress(PORT));
        return serverChannel;
    }

    public void shutdown() {
        masterExecutor.shutdown();
        slaveExecutor.shutdown();
        for (Selector selector : slaveSelectors) {
            try {
                selector.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        System.out.println("服务器已优雅关闭");
    }

    class SlaveReactorRunnable implements Runnable {
        private final Selector slaveSelector;

        public SlaveReactorRunnable(Selector slaveSelector) {
            this.slaveSelector = slaveSelector;
        }

        @Override
        public void run() {
            System.out.println("从Reactor线程启动:" + Thread.currentThread().getName());
            int emptyPollCount = 0;
            final int MAX_EMPTY_POLL = 100;
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    int readKeys = slaveSelector.select(1000);
                    if (readKeys == 0) {
                        emptyPollCount++;
                        if (emptyPollCount > MAX_EMPTY_POLL) {
                            rebuildSelector();
                            emptyPollCount = 0;
                        }
                        continue;
                    }
                    emptyPollCount = 0;

                    Set<SelectionKey> keys = slaveSelector.selectedKeys();
                    Iterator<SelectionKey> iterator = keys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        if (key.isReadable()) {
                            handleRead(key);

                        } else if (key.isWritable()) {
                            handleWrite(key);
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        private void handleWrite(SelectionKey key) {
            SocketChannel clientChannel = (SocketChannel) key.channel();
            ByteBuffer writeBuffer = (ByteBuffer) key.attachment();
            try {
                while (writeBuffer.hasRemaining()) {
                    clientChannel.write(writeBuffer);
                }
                key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
                key.attach(ByteBuffer.allocateDirect(4096));

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

        private void handleRead(SelectionKey key) {
            SocketChannel clientChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = (ByteBuffer) key.attachment();
            buffer.clear();
            try {
                int readBytes = clientChannel.read(buffer);
                if (readBytes == -1) {
                    System.out.println("客户端断开连接:" + clientChannel.getRemoteAddress());
                    key.cancel();
                    clientChannel.close();
                    return;
                }
                if (readBytes == 0) {
                    return;
                }

                // 解码数据
                buffer.flip();
                byte[] data = new byte[buffer.remaining()];
                buffer.get(data);
                String msg = new String(data, StandardCharsets.UTF_8).trim();
                System.out.println("[" + Thread.currentThread().getName() + "] 收到消息:" + clientChannel.getRemoteAddress() + " -> " + msg);

                // 退出指令
                if ("exit".equalsIgnoreCase(msg)) {
                    System.out.println("客户端主动退出:" + clientChannel.getRemoteAddress());
                    key.cancel();
                    clientChannel.close();
                    return;
                }
                // 构建响应
                String response = "[" + Thread.currentThread().getName() + "] 服务器已接收:" + msg + "\r\n";
                ByteBuffer writeBuffer = StandardCharsets.UTF_8.encode(response);
                while (writeBuffer.hasRemaining()) {
                    int writeBytes = clientChannel.write(writeBuffer);
                    if (writeBytes == 0) {
                        key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
                        key.attach(writeBuffer);
                        break;
                    }
                }
                if (!writeBuffer.hasRemaining()) {
                    key.attach(writeBuffer);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        private void rebuildSelector() {
            Selector oldSelector = this.slaveSelector;
            try {
                Selector newSelector = Selector.open();
                for (SelectionKey key : oldSelector.keys()) {
                    if (key.isValid()) {
                        key.channel().register(newSelector, key.interestOps(), key.attachment());
                    }
                }
                synchronized (this) {
                    oldSelector.close();
                }
                System.out.println("从Reactor重建Selector:" + Thread.currentThread().getName());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }


    class MasterReactorRunnable implements Runnable {

        private final ServerSocketChannel serverSocketChannel;

        private final int masterId;

        public MasterReactorRunnable(ServerSocketChannel serverSocketChannel, int masterId) {
            this.serverSocketChannel = serverSocketChannel;
            this.masterId = masterId;
        }

        @Override
        public void run() {
            System.out.println("主Reactor线程" + masterId + "启动:" + Thread.currentThread().getName());
            try {
                Selector masterSelector = Selector.open();
                serverSocketChannel.register(masterSelector, SelectionKey.OP_ACCEPT);

                while (!Thread.currentThread().isInterrupted()) {
                    // 阻塞等待Accept事件(超时1s,避免永久阻塞)
                    int readyKeys = masterSelector.select(1000);
                    if (readyKeys == 0) {
                        continue;
                    }

                    Set<SelectionKey> keys = masterSelector.selectedKeys();
                    Iterator<SelectionKey> iterator = keys.iterator();
                    while (iterator.hasNext()) {
                        SelectionKey key = iterator.next();
                        iterator.remove();

                        if (key.isAcceptable()) {
                            // 处理客户端连接(多主Reactor线程竞争Accept)
                            handleAccept(key);
                        }
                    }
                }
            } catch (IOException e) {
                System.err.println("主Reactor线程" + masterId + "异常:" + e.getMessage());
            } finally {
                try {
                    serverSocketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        private void handleAccept(SelectionKey key) {
            ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
            try {
                SocketChannel clientChannel = serverChannel.accept();
                if (clientChannel == null) return;

                // 客户端通道配置
                clientChannel.configureBlocking(false);
                clientChannel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
                clientChannel.setOption(StandardSocketOptions.TCP_NODELAY, true);
                clientChannel.setOption(StandardSocketOptions.SO_RCVBUF, 4096);
                clientChannel.setOption(StandardSocketOptions.SO_SNDBUF, 4096);

                System.out.println("主Reactor线程" + masterId + " 接受连接:" + clientChannel.getRemoteAddress());

                // 负载均衡:轮询选择从Reactor
                int slaveIdx = slaveIndex.getAndIncrement() % SLAVE_THREAD_NUM;
                Selector slaveSelector = slaveSelectors[slaveIdx];
                // 唤醒从Reactor(避免select阻塞)
                slaveSelector.wakeup();
                // 注册到从Reactor,监听Read事件
                clientChannel.register(slaveSelector, SelectionKey.OP_READ, ByteBuffer.allocateDirect(4096));

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

        }
    }
}
相关推荐
缘来是庄3 小时前
找不到符号
java·intellij-idea
一人の梅雨3 小时前
1688 商品详情接口深度解析:从百川签名突破到供应链数据重构
java·微服务·重构
jiayong233 小时前
IntelliJ IDEA 使用指南
java·ide·intellij-idea
XiaoHu02073 小时前
C++特殊类设计与类型转换
开发语言·c++
AM越.3 小时前
Java设计模式超详解--状态设计模式
java·开发语言·设计模式
古城小栈3 小时前
教育科技:AI+Java 智能题库系统开发实践
java·人工智能·科技
BD_Marathon3 小时前
【JavaWeb】乱码问题_HTML_Tomcat日志_sout乱码问题
java·tomcat·html
冰冰菜的扣jio3 小时前
JVM中的垃圾回收详解
java·jvm