细节重点!

Selector.select()只对这个方法被调用前注册进来的事件才会被监听。如果在这个方法阻塞后,有其他线程往这个selector注册事件,不会被监听到!

这个带来的影响直接影响多线程多Selector的程序设计!!!

初步引入netty原理图,帮助理解上述细节

下图中,select->processSelectedKeys->runAllTasks在while(true)中不断循环(loop),select从阻塞返回,处理相应的keys后,调用runAllTasks方法,将select阻塞过程中新注册进来的事件添加到selector中,这样再次返回到select阻塞方法后,新注册的事件就可以被selector监听到了!!!

es 多线程遍历数据 多线程select_线程组

使用多selector多线程模式

多路复用器线程类,同时也是一个ThreadLocal在生成对象前已经完成对selector的初始化。主要负责接收并监听新的事件请求。

public class SelectorThread  extends  ThreadLocal<LinkedBlockingQueue<Channel>>  implements   Runnable{
    // 一个线程对应一个selector,
    // 每个客户端,只绑定到其中一个selector
    Selector  selector = null;
    LinkedBlockingQueue<Channel> lbq = get();  //获取initialValue的值
    SelectorThreadGroup stg;

    @Override
    protected LinkedBlockingQueue<Channel> initialValue() {
        return new LinkedBlockingQueue<>();//初始化与当前线程绑定的ThreadLocal的值,通过get可以获取
    }

    SelectorThread(SelectorThreadGroup stg){
        try {
            this.stg = stg;
            selector = Selector.open();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        //Loop
        while (true){
            try {
                int nums = selector.select();  //阻塞,wakeup()可以让select()立即返回
                //2,处理selectkeys
                if(nums>0){
                    Set<SelectionKey> keys = selector.selectedKeys();
                    Iterator<SelectionKey> iter = keys.iterator();
                    while(iter.hasNext()){  //线程处理的过程
                        SelectionKey key = iter.next();
                        iter.remove();
                        if(key.isAcceptable()){  //复杂,接受客户端的过程(接收之后,要注册,多线程下,新的客户端,注册到那里呢?)
                            acceptHandler(key);
                        }else if(key.isReadable()){
                            readHander(key);
                        }else if(key.isWritable()){
                        }
                    }
                }
                //3,处理一些task :  listen  client
                if(!lbq.isEmpty()){   //如果队列不为空,表示有新的连接要注册到selector
                    Channel c = lbq.take();
                    if(c instanceof ServerSocketChannel){
                        ServerSocketChannel server = (ServerSocketChannel) c;
                        server.register(selector,SelectionKey.OP_ACCEPT);
                        System.out.println(Thread.currentThread().getName()+" register listen");
                    }else if(c instanceof  SocketChannel){
                        SocketChannel client = (SocketChannel) c;
                        ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
                        client.register(selector, SelectionKey.OP_READ, buffer);
                        System.out.println(Thread.currentThread().getName()+" register client: " + client.getRemoteAddress());
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    private void readHander(SelectionKey key) {
        System.out.println(Thread.currentThread().getName()+" read......");
        ByteBuffer buffer = (ByteBuffer)key.attachment();
        SocketChannel client = (SocketChannel)key.channel();
        buffer.clear();
        while(true){
            try {
                int num = client.read(buffer);
                if(num > 0){
                    buffer.flip();  //将读到的内容翻转,然后直接写出
                    while(buffer.hasRemaining()){
                        client.write(buffer);
                    }
                    buffer.clear();
                }else if(num == 0){
                    break;
                }else if(num < 0 ){
                    //客户端断开了
                    System.out.println("client: " + client.getRemoteAddress()+"closed......");
                    key.cancel();
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void acceptHandler(SelectionKey key) {
        System.out.println(Thread.currentThread().getName()+"   acceptHandler......");

        ServerSocketChannel server = (ServerSocketChannel)key.channel();
        try {
            SocketChannel client = server.accept();
            client.configureBlocking(false);
            stg.nextSelectorV3(client);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

	// 给当前多路复用器设置工作者线程
    public void setWorker(SelectorThreadGroup stgWorker) {
        this.stg =  stgWorker;
    }
}

线程组:包含多个SelectorThread,使用多个多路复用器,可以同时接收多个事件连接。

public class SelectorThreadGroup {

    ServerSocketChannel server=null;
    SelectorThread[] sts;
    AtomicInteger xid = new AtomicInteger(0);

    SelectorThreadGroup  stg =  this;// 默认是boss

    // 可以选择设置worker,处理客户端读写请求
    public void setWorker(SelectorThreadGroup  stg){
        this.stg =  stg;
    }

    SelectorThreadGroup(int num){
        //num  线程数
        sts = new SelectorThread[num];
        for (int i = 0; i < num; i++) {
            sts[i] = new SelectorThread(this);
            new Thread(sts[i]).start();
        }
    }

    public void bind(int port) {
        try {
            server =  ServerSocketChannel.open();
            server.configureBlocking(false);
            server.bind(new InetSocketAddress(port));
            //注册到某个selector
            nextSelectorV3(server);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void nextSelectorV3(Channel c) {
        try {
            if(c instanceof  ServerSocketChannel){
                SelectorThread st = nextBossThread();  //listen 选择了 boss组中的一个线程后,要更新这个线程的work组
                st.lbq.put(c);
                st.setWorker(stg);
                st.selector.wakeup();// 使得这个事件在线程st的selector中生效
            }else {
                SelectorThread st = nextWorkerThread();  //在 main线程种,取到堆里的selectorThread对象
                //1,通过队列传递数据 消息
                st.lbq.add(c);
                //2,通过打断阻塞,让对应的线程去自己在打断后完成注册selector
                st.selector.wakeup();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 返回boss的一个线程
    private SelectorThread nextBossThread() {
        int index = xid.incrementAndGet() % sts.length;
        return sts[index];
    }

    // 返回worker的一个线程
    private SelectorThread nextWorkerThread() {
        int index = xid.incrementAndGet() % stg.sts.length;
        return stg.sts[index];
    }
}

启动

public class MainThread {

    public static void main(String[] args) {
        //1,创建 IO Thread  (一个或者多个)
        //boss有自己的线程组
        SelectorThreadGroup boss = new SelectorThreadGroup(3);  //创建slector线程组并启动线程,一个线程一个selector
        //worker有自己的线程组
        SelectorThreadGroup worker = new SelectorThreadGroup(3);
        // boss线程组拥有worker线程组,因为boss一旦accept得到client后得去worker中 next出一个线程分配
        boss.setWorker(worker);
     
        // boss线程组可以同时监听多个端口
        boss.bind(9999);
        boss.bind(8888);
        boss.bind(6666);
        boss.bind(7777);

    }
}

总结

以上实现的功能:
1. 同一个端口进来的连接交给同一个boss线程处理,不同的连接会轮询boss线程组,交由不同的boss线程处理。
2. 不同的boss线程处理accept请求后,剩下的其它事件都交由同一个worker线程组处理。

这样就完成了boss只需要负责监听accept连接,后续的read/write事件交由worker去执行。