java NIO模拟 Netty线程模型

本文通过NioAcceptor和NioReactor两个类,详细阐述了如何使用Java NIO模拟Netty的线程模型。NioAcceptor负责监听客户端连接,并将通道传递给NioReactor线程池进行处理。NioReactor内部的worker线程通过不断的select操作检查读事件,并对就绪的通道进行响应。通过代码注释,深入理解这一线程模型的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我们都知道netty是boss worker 线程模型。底层使用Javanio。拨开netty其他的功能,我们简单的自己实现一下这个线程模型。

两个类,NioAcceptor,处理连接 单线程类似boss。NioReactor处理读写,多线程l类似worker。

NioAcceptor 监听来自客户端的链接,来了链接后把通道传递给一个NioReactor(它的个数就是线程池的大小),NioReactor把通道注册到worker 的 队列上,该任务不断轮训队列里面的通道的读事件 做出反应。

解释放在代码中

NioAcceptor

package com.zwj.myNio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Set;

public class NioAcceptor {
    private volatile Selector selector;
    private final ServerSocketChannel serverSocketChannel;
    private String iP;
    private int port;
    // 对于以字符方式读取和处理的数据必须要进行字符集编码和解码
    String encoding = System.getProperty("file.encoding");
    // 加载字节编码集
    Charset charse = Charset.forName(encoding);
    private final NioReactor[] nioReactors;
    private volatile int nextReactor;

    public NioAcceptor(String ip, int port, int threadN) {
        if (ip == null || ip == "") {
            throw new IllegalArgumentException();
        }
        this.iP = ip;
        if (port < 1) {
            throw new IllegalArgumentException();
        }
        this.port = port;

        if (threadN < 1) {
            throw new IllegalArgumentException();
        }
        //新建处理读写的线程池
        nioReactors = new NioReactor[threadN];
        for (int i = 0; i < threadN; i++) {
            nioReactors[i] = new NioReactor();
            nioReactors[i].start();
        }
        try {
            this.selector = Selector.open();
            this.serverSocketChannel = ServerSocketChannel.open();
            this.serverSocketChannel.configureBlocking(false);
            /** 设置TCP属性 */
            serverSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
            serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 1024 * 16 * 2);
            // backlog=100
            serverSocketChannel.bind(new InetSocketAddress(ip, port), 100);
            this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("server start!");
        } catch (IOException e) {
            e.printStackTrace();
            throw new IllegalArgumentException();
        }
    }

    private NioReactor nextNioReactor() {

        int i = nextReactor++;
        if (i >= nioReactors.length) {
            i = nextReactor = 0;
        }
        return nioReactors[i];
    }

    public void run() {
        while (true) {
            try {
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                if (!keys.isEmpty()) {
                    for (SelectionKey key : keys) {
                        keys.remove(key);
                        if (key.isValid() && key.isAcceptable()) {
                            SocketChannel ch = serverSocketChannel.accept();
                            ch.configureBlocking(false);
                            String tomessage = "welcome,this is server!";
                            try {
                                ch.write(charse.encode(tomessage));
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            //把这个通道交给Reactor处理,获取NioReactor的方式非常原始就是轮询这个数组,从0开始那,到了末尾就又从0开始
                            NioReactor nioReactor = nextNioReactor();
                            nioReactor.postRegister(ch);
                        } else {
                            //acceptor 只接受 accept事件,如果有其他事件 把这个通道从selector 移除
                            key.cancel();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

NioReactor 内部的worker 是一个 runnable 不断的select 检查就绪的读事件。同时有一保存了该worker需要处理通道的队列。详细解释看代码注释

package com.zwj.myNio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

public class NioReactor {

    final String encoding = System.getProperty("file.encoding");
    final Charset charse = Charset.forName(encoding);
    private final Worker worker;

    public NioReactor() {
        //新建一个worker,worker 是一个 任务,实现 了Runnable接口他的功能就是不断的轮训注册在他的selector 上的通过(channel)就绪读事件
        this.worker = new Worker();
    }

    public void postRegister(SocketChannel socketChannel) {
        //把 通道放到worker 的register队列上面,每次worker轮训的时候会去检查这个队列,如果有新的通道,就把通道注册到自己的selector
        this.worker.registerQueue.add(socketChannel);
        this.worker.selector.wakeup();
    }

    public void start() {
        //启动一个线程来执行worker
        new Thread(worker).start();
    }

    private class Worker implements Runnable {

        private volatile Selector selector;
        private ConcurrentLinkedQueue<SocketChannel> registerQueue = new ConcurrentLinkedQueue<SocketChannel>();
        public Worker() {
            try {
                this.selector = Selector.open();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        private void register(Selector selector) {
            if (registerQueue.isEmpty()) {
                return;
            }
            SocketChannel socketChannel = null;
            while ((socketChannel = registerQueue.poll()) != null) {
                try {
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } catch (ClosedChannelException e) {
                    e.printStackTrace();
                }
            }
        }
        @Override
        public void run() {
            while (true) {
                try {
                    register(selector);
                    selector.select();
                    Set<SelectionKey> keys = selector.selectedKeys();
                    for (SelectionKey key : keys) {
                        keys.remove(key);
                        if (key.isValid() && key.isReadable()) {
                            SocketChannel rchannel = null;
                            try {
                                rchannel = (SocketChannel) key.channel();
                                ByteBuffer readByteBuffer = ByteBuffer.allocate(2048);
                                String content = "";
                                int result= rchannel.read(readByteBuffer);
                                if(result == -1 ){
                                    rchannel.close();
                                }else{
                                    readByteBuffer.flip();
                                    content += charse.decode(readByteBuffer);
                                    //to do business
                                    System.out.println("server rec:" + content);
                                    String tomessage = "this is server!i have rec you mess";
                                    rchannel.write(charse.encode(tomessage));
                                }

                            } catch (IOException e) {
                                if (rchannel != null) {
                                    key.cancel();
                                    rchannel.close();
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }


}

最后一个 客户端程序 来连接 服务

package com.zwj.myNio;


import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;

public class NIOClient {
    private static final int SIZE = 1024;
    private static NIOClient instance = new NIOClient();
    public String IP = "127.0.0.1";// 10.50.200.120
    public int CLIENT_PORT = 8090;// 4444 9666
    private SocketChannel channel;
    private Selector selector = null;

    String encoding = System.getProperty("file.encoding");
    Charset charset = Charset.forName(encoding);

    private NIOClient() {
    }

    public static NIOClient getInstance() {
        return instance;
    }

    public void send(String content) throws IOException {
        selector = Selector.open();
        channel = SocketChannel.open();
        // channel = SocketChannel.open(new InetSocketAddress(IP,CLIENT_PORT));
        InetSocketAddress remote = new InetSocketAddress(IP, CLIENT_PORT);
        channel.connect(remote);
        // 设置该sc以非阻塞的方式工作
        channel.configureBlocking(false);
        // 将SocketChannel对象注册到指定的Selector
        // SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT
        channel.register(selector, SelectionKey.OP_READ);//这里注册的是read读,即从服务端读数据过来
        // 启动读取服务器数据端的线程
        new ClientThread().start();
        channel.write(charset.encode(content));
        // 创建键盘输入流
        Scanner scan = new Scanner(System.in);//这里向服务端发送数据,同时启动了一个键盘监听器
        while (scan.hasNextLine()) {
            System.out.println("输入数据:\n");
            // 读取键盘的输入
            String line = scan.nextLine();
            // 将键盘的内容输出到SocketChanenel中
            channel.write(charset.encode(line));
        }
        scan.close();
    }

    /**
     * 从服务端读入数据的线程
     */
    private class ClientThread extends Thread {
        @Override
        public void run() {
            try {
                while (selector.select() > 0) {
                    // 遍历每个有可能的IO操作的Channel对银行的SelectionKey
                    for (SelectionKey sk : selector.selectedKeys()) {
                        // 删除正在处理的SelectionKey
                        selector.selectedKeys().remove(sk);
                        // 如果该SelectionKey对应的Channel中有可读的数据
                        if (sk.isReadable()) {
                            // 使用NIO读取Channel中的数据
                            SocketChannel sc = (SocketChannel) sk.channel();
                            String content = "";
                            ByteBuffer bff = ByteBuffer.allocate(SIZE);
                            while (sc.read(bff) > 0) {
                                sc.read(bff);
                                bff.flip();
                                content += charset.decode(bff);
                            }
                            // 打印读取的内容
                            System.out.println("服务端返回数据:" + content);
                            // 处理下一次读
                            sk.interestOps(SelectionKey.OP_READ);
                        }
                    }
                }

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

    /**
     * 接受服务端的数据
     *
     * @param channel
     * @return
     * @throws Exception
     */
    protected void receiveData(SocketChannel channel) throws Exception {
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        int count = 0;
        while ((count = channel.read(buffer)) != -1) {
            if (count == 0) {
                Thread.sleep(100); // 等等一下
                continue;
            }
            // 转到最开始
            buffer.flip();
            while (buffer.remaining() > 0) {
                System.out.print((char) buffer.get());
            }
            buffer.clear();
        }
    }

    public static void main(String[] args) {
        try {
            NIOClient nio = new NIOClient();
            nio.send("test");//向服务端发送数据
            //nio.send("metrics:memory:	swap:	cpu:	network i/o:	disks i/o:	tcp:\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

服务启动的服务端main 程序

package com.zwj.myNio;

public class MyNio {


    public static void main(String[] args) {
        // write your code here
        NioAcceptor nioAcceptor = new NioAcceptor("127.0.0.1", 8090, 1);
        nioAcceptor.run();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值