select 实现类似多线程_七、Selector实现Netty中Reactor主从模型

导论

前面几篇文章我们分别对一、JAVA IO/NIO体系介绍 、二、网络IO原理-创建ServerSocket-彻底弄懂IO 、三、JAVA中ServerSocket调用Linux系统内核 、四、「大厂职员教你」IO进化过程之BIO 、五、「大厂职员教你」Java-IO进化过程之NIO 、六、Selector实现Netty中Reactor单线程模型 等几个纬度对JavaIO和NIO体系做了详细介绍,并由简到深的根据IO体系的升级过程做了系统分析。今天我们开始讲解NIO体系下的多路复用器(Selector),并用实例教你如何实现Netty中Reactor主从模型。这篇文章是与上篇文章【六、Selector实现Netty中Reactor单线程模型 】紧紧结合的,因此要读懂这篇文章,请先以上篇文章做铺垫。

多路复用器-selector多线程版本- Reactor主从模型

代码示例

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.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Set;import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.atomic.AtomicInteger;/** * Created by Bruce on 2020/9/18 * * 多路复用器-selector多线程版本 *  *  网络IO之SELECT-服务端 *  *  *  * SELECT-写法 *  *  *  ServerSocketChannel **/public class SocketServerSelectorMultiplexingThreads {    /**     * 服务端通道     */    private ServerSocketChannel serverSocketChannel;    /**     * 服务端端口     */    private int serverPort;    /**     * 主选择器-主要用于客户端的的接入-OP_ACCEPT-事件     */    private Selector bossSelector;    /**     * 任务选择器-主要用于客户端数据的传入处理-OP_READ-事件     */    private Selector[] workerSelectors;    /**     * 任务选择器数量     */    private int workerNum;    /**     * @param serverPort  服务端端口     * @param workerNum 任务选择器数量     * @throws IOException     */    public SocketServerSelectorMultiplexingThreads(int serverPort ,int workerNum) throws IOException {        this.serverPort = serverPort;        this.workerNum = workerNum;        /**         * 创建并绑定端口号         */        serverSocketChannel = ServerSocketChannel.open();        serverSocketChannel.bind(new InetSocketAddress(serverPort));        serverSocketChannel.configureBlocking(false);        System.out.println("step1 : new ServerSocket(" + serverPort+ ") ");        /**         * 创建主(boss)选择器         */        bossSelector = Selector.open();        /**         * serverSocket注册到         */        serverSocketChannel.register(bossSelector, SelectionKey.OP_ACCEPT);        /**         * 初始化任务选择器-根据设定的任务选择器数量         */        initWorderSelector();    }    /**     * 初始化 任务选择器     * @throws IOException     */    private void initWorderSelector() throws IOException {        workerSelectors = new Selector[workerNum];        for(int i = 0; i < workerNum; i++){            workerSelectors[i] = Selector.open();        }    }    public Selector getBossSelector() {        return bossSelector;    }    public Selector[] getWorkerSelectors() {        return workerSelectors;    }    public int getWorkerNum() {        return workerNum;    }    public static void main(String[] args) throws IOException, InterruptedException {        int serverPort = 8080;        int workerNum = 3;        System.out.println("准备启动服务端端口号:" + serverPort + "---准备启动worker数量:" + workerNum);        SocketServerSelectorMultiplexingThreads server = new SocketServerSelectorMultiplexingThreads(serverPort,workerNum);        NioThread bossThreadRunnable = new NioThread(server.getBossSelector(),workerNum);        Thread bossThread = new Thread(bossThreadRunnable);        bossThread.start();        Thread.currentThread().sleep(2000);        Thread workerThread = null;        Selector[] workerSelectors = server.getWorkerSelectors();        for(Selector selector : workerSelectors){            NioThread workerNioThread = new NioThread(selector);            workerThread = new Thread(workerNioThread);            workerThread.start();        }    }}class NioThread implements Runnable{    /**     * 传入的  选择器-私有     */    private Selector selector;    /**     * 任务选择器数量-多线程可见     */    private static int workerSelectorNum;    /**     * 每个任务选择器对应一个 队列  -多线程可见     */    static BlockingQueue[] clientSocketChannelQueues;    /**     * 任务选择器下标的数值-用于需任务选择器队列中获取自己的阻塞队列     * 每个worker生成自己的ID。     *      * 这个workerID是与

BlockingQueue[] clientSocketChannelQueues

中阻塞队列进行间接绑定了的     */    int id = 0;    /**     * 是否为工作任务的选择器线程     * 默认为工作任务的选择器线程     * 如果为非工作任务的选择器线程则设置为false     */    private boolean workerSign = true;    /**     * 自增ID-多线程可见     */    static AtomicInteger idx = new AtomicInteger();    /**     * Boss  选择器专用构造器     * @param selector  boss任务选择器     * @param workerSelectorNum 任务选择器数量     */    public NioThread(Selector selector, int workerSelectorNum){        this.selector = selector;        this.workerSelectorNum = workerSelectorNum;        this.workerSign = false;        clientSocketChannelQueues = new LinkedBlockingQueue[workerSelectorNum];        initWorderSocketChannelQueues();        System.out.println("Boss 启动");    }    /**     * 初始化任务选择器所需的阻塞队列数组     * 一个阻塞队列对应一个任务选择器     * @throws IOException     */    private void initWorderSocketChannelQueues() {        for (int i = 0; i < workerSelectorNum; i++){            clientSocketChannelQueues[i] = new LinkedBlockingQueue<>();        }    }    /**     * worker 任务选择器专用构造器     * @param selector     */    public NioThread(Selector selector) {        this.selector = selector;        /**         * 每个worker生成自己的ID。         */        id = idx.getAndIncrement() % workerSelectorNum;//任务选择器队列下标        System.out.println("任务线程 【worker---" + id + "】启动");    }    @Override    public void run() {        try {            while (true){                if(selector.select(10) > 0){//10毫秒延迟获取  不完全阻塞                    Set selectionKeys = selector.selectedKeys();                    Iterator iterator = selectionKeys.iterator();                    while (iterator.hasNext()){                        SelectionKey selectionKey = iterator.next();                        iterator.remove();                        if(selectionKey.isConnectable()){                            System.out.println("---------------selectionKey.isConnectable()......");                        }else if(selectionKey.isAcceptable()){//客户端请求连接事件---其时只有boss选择器才能走到这一步                            acceptHandler(selectionKey);                        }else if(selectionKey.isReadable()){//客户端数据到达事件                            readHandler(selectionKey);                        }else if(selectionKey.isValid()){                            System.out.println("---------------selectionKey.isValid()......");                        }else if(selectionKey.isWritable()){                            System.out.println("---------------selectionKey.isWritable()......");                        }                    }                }                /**                 * boss 不参与这个过程                 * 只有工作任务的选择器线程                 * 且                 * 对应的阻塞队列不为空的时候,才会执行                 */                if(workerSign && !clientSocketChannelQueues[id].isEmpty()){                    //默认创建一个8字节的缓冲区                    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(8192);                    //从阻塞队列中取出对应的客户端                    SocketChannel clientSocketChannel = clientSocketChannelQueues[id].take();                    clientSocketChannel.register(selector,SelectionKey.OP_READ,byteBuffer);                    System.out.println("-----------------------------------------------------");                    System.out.println("客户端连接进入:" + clientSocketChannel.socket().getPort() + "分配到workder ---" + id);                    System.out.println("-----------------------------------------------------");                }            }        } catch (IOException e) {            e.printStackTrace();        } catch (InterruptedException e) {            e.printStackTrace();        }    }    /**     * 其时这一步 只有 boss选择器可以进入     * @param selectionKey     * @throws IOException     */    private void acceptHandler(SelectionKey selectionKey) throws IOException {        /**         * 从选择器中获取服务端注册时的通道         */        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();        /**         * 接收传入的客户端SocketChannel         */        SocketChannel clientSocketChannel = serverSocketChannel.accept();        clientSocketChannel.configureBlocking(false);//设置客户端类型为非阻塞        /**         * 由于boss选择器所在的线程并不会处理客户端的链接,         * 他只是把接受到的选择器按照自增的规则取模后放到不同的阻塞队列当中         * 每次来一个客户端  idx都会自增1 ,然后取模后放到不同的任务队列。         */        //轮询分配        int index = idx.getAndIncrement() % workerSelectorNum;        /**         * 把接受到的客户端放入不同任务选择器归属的阻塞队列中         */        clientSocketChannelQueues[index].add(clientSocketChannel);    }    /**     * 其时这一步只有worker     * @param selectionKey     * @throws IOException     */    private void readHandler(SelectionKey selectionKey) throws IOException {        SocketChannel clientSocketChannel = (SocketChannel) selectionKey.channel();        ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();        byteBuffer.clear();        int readNum = 0;        try {            while (true){                readNum = clientSocketChannel.read(byteBuffer);                if(readNum < 0){                    System.out.println("client port --" + clientSocketChannel.socket().getPort() + "---offline---");                    selectionKey.cancel();                    clientSocketChannel.socket().close();                    clientSocketChannel.close();                }else if(readNum == 0){                    break;                }else {                    byteBuffer.flip();//每次读取之前都要反转一次                    byte[] bytes = new byte[readNum];                    byteBuffer.get(bytes);                    String clientStr = new String(bytes);                    System.out.println("client port --" + clientSocketChannel.socket().getPort() + "---data---" + clientStr);                    String returnStr = "server get client data" + clientStr;                    byteBuffer.clear();//把数据返回                    byteBuffer.put(returnStr.getBytes());                    byteBuffer.flip();//每次写出之前都要反转一次                    while (byteBuffer.hasRemaining()){//判断当前缓冲区中是否有数据                        clientSocketChannel.write(byteBuffer);//把当前缓冲区中数据写回客户端。                    }                    byteBuffer.clear();//写完之后清空。                }            }        }catch (IOException e) {            e.printStackTrace();            System.out.println("client port --" + clientSocketChannel.socket().getPort() + "---offline---");            selectionKey.cancel();            clientSocketChannel.socket().close();            clientSocketChannel.close();        }    }}  

多路复用器-selector多线程版本- Reactor主从模型-打印输出

在linux环境或者windows环境下使用nc命令链接服务端,查看服务端打印过程。

具体linux系统或者windows系统如何安装nc命令,请从网络搜索或查看目录文档 ‘网络IO涉及到的-linux指令.docx’。

1.nc客户端1打印(Windows-nc命令打印)

C:甥敳獲Administrator>nc 127.0.0.1 8080nc111server get client datanc111client111server get client dataclient111

2.nc客户端2打印(Windows-nc命令打印)

C:甥敳獲Administrator>nc 127.0.0.1 8080nc222server get client datanc222client222server get client dataclient222

3. nc客户端2打印(Windows-nc命令打印)

C:甥敳獲Administrator>nc 127.0.0.1 8080nc333server get client datanc333chient333server get client datachient333

4.nc客户端2打印(Windows-nc命令打印)

C:甥敳獲Administrator>nc 127.0.0.1 8080nc444server get client datanc444client444server get client dataclient444

5.服务端打印

准备启动服务端端口号:8080---准备启动worker数量:3step1 : new ServerSocket(8080)Boss 启动任务线程 【worker---0】启动任务线程 【worker---1】启动任务线程 【worker---2】启动-----------------------------------------------------客户端连接进入:8822分配到workder ---0----------------------------------------------------------------------------------------------------------客户端连接进入:8825分配到workder ---1----------------------------------------------------------------------------------------------------------客户端连接进入:8828分配到workder ---2----------------------------------------------------------------------------------------------------------客户端连接进入:8830分配到workder ---0-----------------------------------------------------client port --8822---data---nc111 client port --8825---data---nc222 client port --8828---data---nc333 client port --8830---data---nc444 client port --8830---data---client444 client port --8828---data---chient333 client port --8825---data---client222 client port --8822---data---client111

多线程版本的多路复用器图示-Reactor主从模型示意图

c2be5a475f80002b7033477d3807f5a3.png

多线程版本的多路复用器的几种模型-Reactor模型的几种类型

a08ea3456b59d9517120912016cd6c5e.png

往期文章链接

一、JAVA IO/NIO体系介绍

二、网络IO原理-创建ServerSocket-彻底弄懂IO

三、JAVA中ServerSocket调用Linux系统内核

四、「大厂职员教你」IO进化过程之BIO

五、「大厂职员教你」Java-IO进化过程之NIO

六、Selector实现Netty中Reactor单线程模型

如需了解更多更详细内容也可关注本人CSDN博客:不吃_花椒

后续文章中的整体JavaIO体系文章概览

a7393832211156036e5ce5b43cec34aa.png
46b97cab973209c02a904c441941d533.png
9e673541755301b320590c4f6f7cef02.png
项目简介: 采用I/O复用技术select实现socket通信,采用多线程负责每个客户操作处理,完成Linux下的多客户聊天室! OS:Ubuntu 15.04 IDE:vim gcc make DB:Sqlite 3 Time:2015-12-09 ~ 2012-12-21 项目功能架构: 1. 采用client/server结构; 2. 给出客户操作主界面(注册、登录、帮助和退出)、登录后主界面(查看在线列表、私聊、群聊、查看聊天记录、退出); 3. 多客户可同时连接服务器进行自己操作; ##服务器端## 1. server.c:服务器端主程序代码文件; 2. config.h:服务器端配置文件(包含需要的头文件、常量、数据结构及函数声明); 3. config.c:服务器端公共函数的实现文件; 4. list.c:链表实现文件,用于维护在线用户链表的添加、更新、删除操作; 5. register.c:服务器端实现用户注册; 6. login.c:服务器端实现用户登录; 7. chat.c:服务器端实现用户的聊天互动操作; 8. Makefile:服务器端make文件,控制台执行make命令可直接生成可执行文件server ##客户端## 1. client.c:客户端主程序代码文件; 2. config.h:客户端配置文件(包含需要的头文件、常量、数据结构及函数声明); 3. config.c:客户端公共函数的实现文件; 4. register.c:客户端实现用户注册; 5. login.c:客户端实现用户登录; 6. chat.c:客户端实现用户的聊天互动操作; 7. Makefile:客户端make文件,控制台执行make命令可直接生成可执行文件client;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值