多线程Reactor模式旨在分配多个reactor每一个reactor独立拥有一个selector,在网络通信中大体设计为负责连接的主Reactor,其中在主Reactor的run函数中若selector检测到了连接事件的发生则dispatch该事件。

让负责管理连接的Handler处理连接,其中在这个负责连接的Handler处理器中创建子Handler用以处理IO请求。这样一来连接请求与IO请求分开执行提高通道的并发量。同时多个Reactor带来的好处是多个selector可以提高通道的检索速度

1.1 主服务器

  1. package com.crazymakercircle.ReactorModel;
  2. import com.crazymakercircle.NioDemoConfig;
  3. import com.crazymakercircle.util.Logger;
  4. import java.io.IOException;
  5. import java.net.InetSocketAddress;
  6. import java.nio.channels.SelectionKey;
  7. import java.nio.channels.Selector;
  8. import java.nio.channels.ServerSocketChannel;
  9. import java.nio.channels.SocketChannel;
  10. import java.util.Iterator;
  11. import java.util.Set;
  12. import java.util.concurrent.atomic.AtomicInteger;
  13. class MultiThreadEchoServerReactor {
  14. ServerSocketChannel serverSocket;
  15. AtomicInteger next = new AtomicInteger(0);
  16. Selector bossSelector = null;
  17. Reactor bossReactor = null;
  18. //selectors集合,引入多个selector选择器
  19. //多个选择器可以更好的提高通道的并发量
  20. Selector[] workSelectors = new Selector[2];
  21. //引入多个子反应器
  22. //如果CPU是多核的可以开启多个子Reactor反应器,这样每一个子Reactor反应器还可以独立分配一个线程。
  23. //每一个线程可以单独绑定一个单独的Selector选择器以提高通道并发量
  24. Reactor[] workReactors = null;
  25. MultiThreadEchoServerReactor() throws IOException {
  26. bossSelector = Selector.open();
  27. //初始化多个selector选择器
  28. workSelectors[0] = Selector.open();
  29. workSelectors[1] = Selector.open();
  30. serverSocket = ServerSocketChannel.open();
  31. InetSocketAddress address =
  32. new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP,
  33. NioDemoConfig.SOCKET_SERVER_PORT);
  34. serverSocket.socket().bind(address);
  35. //非阻塞
  36. serverSocket.configureBlocking(false);
  37. //第一个selector,负责监控新连接事件
  38. SelectionKey sk =
  39. serverSocket.register(bossSelector, SelectionKey.OP_ACCEPT);
  40. //附加新连接处理handler处理器到SelectionKey(选择键)
  41. sk.attach(new AcceptorHandler());
  42. //处理新连接的反应器
  43. bossReactor = new Reactor(bossSelector);
  44. //第一个子反应器,一子反应器负责一个选择器
  45. Reactor subReactor1 = new Reactor(workSelectors[0]);
  46. //第二个子反应器,一子反应器负责一个选择器
  47. Reactor subReactor2 = new Reactor(workSelectors[1]);
  48. workReactors = new Reactor[]{subReactor1, subReactor2};
  49. }
  50. private void startService() {
  51. new Thread(bossReactor).start();
  52. // 一子反应器对应一条线程
  53. new Thread(workReactors[0]).start();
  54. new Thread(workReactors[1]).start();
  55. }
  56. //反应器
  57. class Reactor implements Runnable {
  58. //每条线程负责一个选择器的查询
  59. final Selector selector;
  60. public Reactor(Selector selector) {
  61. this.selector = selector;
  62. }
  63. public void run() {
  64. try {
  65. while (!Thread.interrupted()) {
  66. //单位为毫秒
  67. //每隔一秒列出选择器感应列表
  68. selector.select(1000);
  69. Set<SelectionKey> selectedKeys = selector.selectedKeys();
  70. if (null == selectedKeys || selectedKeys.size() == 0) {
  71. //如果列表中的通道注册事件没有发生那就继续执行
  72. continue;
  73. }
  74. Iterator<SelectionKey> it = selectedKeys.iterator();
  75. while (it.hasNext()) {
  76. //Reactor负责dispatch收到的事件
  77. SelectionKey sk = it.next();
  78. dispatch(sk);
  79. }
  80. //清楚掉已经处理过的感应事件,防止重复处理
  81. selectedKeys.clear();
  82. }
  83. } catch (IOException ex) {
  84. ex.printStackTrace();
  85. }
  86. }
  87. void dispatch(SelectionKey sk) {
  88. Runnable handler = (Runnable) sk.attachment();
  89. //调用之前attach绑定到选择键的handler处理器对象
  90. if (handler != null) {
  91. handler.run();
  92. }
  93. }
  94. }
  95. // Handler:新连接处理器
  96. class AcceptorHandler implements Runnable {
  97. public void run() {
  98. try {
  99. SocketChannel channel = serverSocket.accept();
  100. Logger.info("接收到一个新的连接");
  101. if (channel != null) {
  102. int index = next.get();
  103. Logger.info("选择器的编号:" + index);
  104. Selector selector = workSelectors[index];
  105. new MultiThreadEchoHandler(selector, channel);
  106. }
  107. } catch (IOException e) {
  108. e.printStackTrace();
  109. }
  110. if (next.incrementAndGet() == workSelectors.length) {
  111. next.set(0);
  112. }
  113. }
  114. }
  115. public static void main(String[] args) throws IOException {
  116. MultiThreadEchoServerReactor server =
  117. new MultiThreadEchoServerReactor();
  118. server.startService();
  119. }
  120. }

按上述的设计思想,在主服务器中实际上设计了三个Reactor,一个主Reactor专门负责连接请求并配已单独的selector,但是三个Reactor的线程Run函数是做的相同的功能,都是根据每个线程内部的selector进行检索事件列表,若注册的监听事件发生了则调用dispactch分发到每个Reactor对应的Handler。

这里需要注意的一开始其实只有负责连接事件的主Reactor在注册selector的时候给相应的key配了一个AcceptorHandler()。

  1. //第一个selector,负责监控新连接事件
  2. SelectionKey sk =
  3. serverSocket.register(bossSelector, SelectionKey.OP_ACCEPT);
  4. //附加新连接处理handler处理器到SelectionKey(选择键)
  5. sk.attach(new AcceptorHandler());

但是Reactor的run方法里若相应的selector key发生了便要dispatch到一个Handler。这里其他两个子Reactor的Handler在哪里赋值的呢?其实在处理连接请求的Reactor中便创建了各个子Handler,如下代码所示:

主Handler中先是根据服务器channel创建出客服端channel,在进行子selector与channel的绑定。

  1. int index = next.get();
  2. Logger.info("选择器的编号:" + index);
  3. Selector selector = workSelectors[index];
  4. new MultiThreadEchoHandler(selector, channel);

2.1 IO请求handler+线程池

  1. package com.crazymakercircle.ReactorModel;
  2. import com.crazymakercircle.util.Logger;
  3. import java.io.IOException;
  4. import java.nio.ByteBuffer;
  5. import java.nio.channels.SelectionKey;
  6. import java.nio.channels.Selector;
  7. import java.nio.channels.SocketChannel;
  8. import java.util.concurrent.ExecutorService;
  9. import java.util.concurrent.Executors;
  10. class MultiThreadEchoHandler implements Runnable {
  11. final SocketChannel channel;
  12. final SelectionKey sk;
  13. final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  14. static final int RECIEVING = 0, SENDING = 1;
  15. int state = RECIEVING;
  16. //引入线程池
  17. static ExecutorService pool = Executors.newFixedThreadPool(4);
  18. MultiThreadEchoHandler(Selector selector, SocketChannel c) throws IOException {
  19. channel = c;
  20. channel.configureBlocking(false);
  21. //唤醒选择,防止register时 boss线程被阻塞,netty 处理方式比较优雅,会在同一个线程注册事件,避免阻塞boss
  22. selector.wakeup();
  23. //仅仅取得选择键,后设置感兴趣的IO事件
  24. sk = channel.register(selector, 0);
  25. //将本Handler作为sk选择键的附件,方便事件dispatch
  26. sk.attach(this);
  27. //向sk选择键注册Read就绪事件
  28. sk.interestOps(SelectionKey.OP_READ);
  29. //唤醒选择,是的OP_READ生效
  30. selector.wakeup();
  31. Logger.info("新的连接 注册完成");
  32. }
  33. public void run() {
  34. //异步任务,在独立的线程池中执行
  35. pool.execute(new AsyncTask());
  36. }
  37. //异步任务,不在Reactor线程中执行
  38. public synchronized void asyncRun() {
  39. try {
  40. if (state == SENDING) {
  41. //写入通道
  42. channel.write(byteBuffer);
  43. //写完后,准备开始从通道读,byteBuffer切换成写模式
  44. byteBuffer.clear();
  45. //写完后,注册read就绪事件
  46. sk.interestOps(SelectionKey.OP_READ);
  47. //写完后,进入接收的状态
  48. state = RECIEVING;
  49. } else if (state == RECIEVING) {
  50. //从通道读
  51. int length = 0;
  52. while ((length = channel.read(byteBuffer)) > 0) {
  53. Logger.info(new String(byteBuffer.array(), 0, length));
  54. }
  55. //读完后,准备开始写入通道,byteBuffer切换成读模式
  56. byteBuffer.flip();
  57. //读完后,注册write就绪事件
  58. sk.interestOps(SelectionKey.OP_WRITE);
  59. //读完后,进入发送的状态
  60. state = SENDING;
  61. }
  62. //处理结束了, 这里不能关闭select key,需要重复使用
  63. //sk.cancel();
  64. } catch (IOException ex) {
  65. ex.printStackTrace();
  66. }
  67. }
  68. //异步任务的内部类
  69. class AsyncTask implements Runnable {
  70. public void run() {
  71. MultiThreadEchoHandler.this.asyncRun();
  72. }
  73. }
  74. }

在处理IO请求的Handler中采用了线程池,已达到异步处理的目的。

3.1 客户端

  1. package com.crazymakercircle.ReactorModel;
  2. import com.crazymakercircle.NioDemoConfig;
  3. import com.crazymakercircle.util.Dateutil;
  4. import com.crazymakercircle.util.Logger;
  5. import java.io.IOException;
  6. import java.net.InetSocketAddress;
  7. import java.nio.ByteBuffer;
  8. import java.nio.channels.SelectionKey;
  9. import java.nio.channels.Selector;
  10. import java.nio.channels.SocketChannel;
  11. import java.util.Iterator;
  12. import java.util.Scanner;
  13. import java.util.Set;
  14. /**
  15. * create by 尼恩 @ 疯狂创客圈
  16. **/
  17. public class EchoClient {
  18. public void start() throws IOException {
  19. InetSocketAddress address =
  20. new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP,
  21. NioDemoConfig.SOCKET_SERVER_PORT);
  22. // 1、获取通道(channel)
  23. SocketChannel socketChannel = SocketChannel.open(address);
  24. Logger.info("客户端连接成功");
  25. // 2、切换成非阻塞模式
  26. socketChannel.configureBlocking(false);
  27. //不断的自旋、等待连接完成,或者做一些其他的事情
  28. while (!socketChannel.finishConnect()) {
  29. }
  30. Logger.tcfo("客户端启动成功!");
  31. //启动接受线程
  32. Processer processer = new Processer(socketChannel);
  33. new Thread(processer).start();
  34. }
  35. static class Processer implements Runnable {
  36. final Selector selector;
  37. final SocketChannel channel;
  38. Processer(SocketChannel channel) throws IOException {
  39. //Reactor初始化
  40. selector = Selector.open();
  41. this.channel = channel;
  42. channel.register(selector,
  43. SelectionKey.OP_READ | SelectionKey.OP_WRITE);
  44. }
  45. public void run() {
  46. try {
  47. while (!Thread.interrupted()) {
  48. selector.select();
  49. Set<SelectionKey> selected = selector.selectedKeys();
  50. Iterator<SelectionKey> it = selected.iterator();
  51. while (it.hasNext()) {
  52. SelectionKey sk = it.next();
  53. if (sk.isWritable()) {
  54. ByteBuffer buffer = ByteBuffer.allocate(NioDemoConfig.SEND_BUFFER_SIZE);
  55. Scanner scanner = new Scanner(System.in);
  56. Logger.tcfo("请输入发送内容:");
  57. if (scanner.hasNext()) {
  58. SocketChannel socketChannel = (SocketChannel) sk.channel();
  59. String next = scanner.next();
  60. buffer.put((Dateutil.getNow() + " >>" + next).getBytes());
  61. buffer.flip();
  62. // 操作三:发送数据
  63. socketChannel.write(buffer);
  64. buffer.clear();
  65. }
  66. }
  67. if (sk.isReadable()) {
  68. // 若选择键的IO事件是“可读”事件,读取数据
  69. SocketChannel socketChannel = (SocketChannel) sk.channel();
  70. //读取数据
  71. ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
  72. int length = 0;
  73. while ((length = socketChannel.read(byteBuffer)) > 0) {
  74. byteBuffer.flip();
  75. Logger.info("server echo:" + new String(byteBuffer.array(), 0, length));
  76. byteBuffer.clear();
  77. }
  78. }
  79. //处理结束了, 这里不能关闭select key,需要重复使用
  80. //selectionKey.cancel();
  81. }
  82. selected.clear();
  83. }
  84. } catch (IOException ex) {
  85. ex.printStackTrace();
  86. }
  87. }
  88. }
  89. public static void main(String[] args) throws IOException {
  90. new EchoClient().start();
  91. }
  92. }

多线程Reactor模式的更多相关文章

  1. Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式

    原创文章,同步发自作者个人博客,http://www.jasongj.com/java/nio_reactor/ Java I/O模型 同步 vs. 异步 同步I/O 每个请求必须逐个地被处理,一个请 ...

  2. Java I/O模型从BIO到NIO和Reactor模式(转)

    原创文章,转载请务必将下面这段话置于文章开头处(保留超链接).本文转发自技术世界,原文链接 http://www.jasongj.com/java/nio_reactor/ Java I/O模型 同步 ...

  3. reactor模式:多线程的reactor模式

    上文说到单线程的reactor模式 reactor模式:单线程的reactor模式 单线程的reactor模式并没有解决IO和CPU处理速度不匹配问题,所以多线程的reactor模式引入线程池的概念, ...

  4. 什么是Reactor模式,或者叫反应器模式

    Reactor这个词译成汉语还真没有什么合适的,很多地方叫反应器模式,但更多好像就直接叫reactor模式了,其实我觉着叫应答者模式更好理解一些.通过了解,这个模式更像一个侍卫,一直在等待你的召唤,或 ...

  5. Reactor模式通俗解释

    Reactor这个词译成汉语还真没有什么合适的,很多地方叫反应器模式,但更多好像就直接叫reactor模式了,其实我觉着叫应答者模式更好理解一些.通过了解,这个模式更像一个侍卫,一直在等待你的召唤,或 ...

  6. Reactor模式与Proactor模式

    该文章总结了网上资源对这两种模式的描述 原文地址:http://www.cnblogs.com/dawen/archive/2011/05/18/2050358.html 1.标准定义 两种I/O多路 ...

  7. Reactor模式详解

    转自:http://www.blogjava.net/DLevin/archive/2015/09/02/427045.html 前记 第一次听到Reactor模式是三年前的某个晚上,一个室友突然跑过 ...

  8. Reactor模式解析——muduo网络库

    最近一段时间阅读了muduo源码,读完的感受有一个感受就是有点乱.当然不是说代码乱,是我可能还没有完全消化和理解.为了更好的学习这个库,还是要来写一些东西促进一下. 我一边读一边尝试在一些地方改用c+ ...

  9. Reactor模式

    对象行为类的设计模式,对同步事件分拣和派发.别名Dispatcher(分发器) Reactor模式是处理并发I/O比较常见的一种模式,用于同步I/O,中心思想是将所有要处理的I/O事件注册到一个中心I ...

随机推荐

  1. DL4J实战之五:矩阵操作基本功

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  2. jenkins的安装、配置使用

    1.jenkins的使用 (1).需要先下载安装JDK 配置jdk的环境 变量JAVA_HOME的值是 jdk 的安装位置, 然后下载安装tomcat 安装好了之后,打开tomcat下的bin文件夹, ...

  3. (课内)信安数基RSA-level1&&2

    注:(不求甚解的)攻击原理 以及(浅层的)算法解释已在图片中给出:文字部分主要讲一些python语法的东西. 代码需要库 gmpy2和libnum:加密算法还需要Crypto.Util.number ...

  4. “介绍一下自己吧”——记2020BUAA软工团队介绍和采访

    写在前面 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任建) 这个作业的要求在哪里 团队作业-团队介绍和采访 团队介绍 团队名称 我们是 BUAA软软软件工程小队 ,简称 ...

  5. AOP源码解析:AspectJAwareAdvisorAutoProxyCreator类的介绍

    AspectJAwareAdvisorAutoProxyCreator 的类图 上图中一些 类/接口 的介绍: AspectJAwareAdvisorAutoProxyCreator : 公开了Asp ...

  6. 手把手教你学Dapr - 3. 使用Dapr运行第一个.Net程序

    上一篇:手把手教你学Dapr - 2. 必须知道的概念 注意: 文章中提到的命令行工具即是Windows Terminal/PowerShell/cmd其中的一个,推荐使用Windows Termin ...

  7. Harbour.Space Scholarship Contest 2021-2022 (Div. 1 + Div. 2) Editorial题解

    A 略,发现只有当末尾为9时才满足条件.. B 简单模拟,注意数组大小!!! C 简单模拟. D 比较暴力的一个做法就是每次找一个开始匹配的起始点,然后每次不同时向后跳2就行了. 注意这里最后还要判断 ...

  8. [源码解析] PyTorch 分布式(1)------历史和概述

    [源码解析] PyTorch 分布式(1)------历史和概述 目录 [源码解析] PyTorch 分布式(1)------历史和概述 0x00 摘要 0x01 PyTorch分布式的历史 1.1 ...

  9. hash 哈希表 缓存表

    系统初始hash表为空,当外部命令执行时,默认会从 PATH路径下寻找该命令,找到后会将这条命令的路径记录到 hash表中,当再次使用该命令时,shell解释器首先会查看hash 表,存在将执行之,如 ...

  10. 对于multitaper多窗口谱估计的理解及步骤 (对应matlab中pmtm函数)谱减法相关

    对于多窗口谱估计的理解 目录 对于多窗口谱估计的理解 0. 缘起 1. PMTM 含义 2. 与我们常用的周期谱估计的区别 3. 计算过程 5. 多窗/单窗谱估计结果对比 6. 程序如何生成多窗 - ...