多线程Reactor模式
多线程Reactor模式旨在分配多个reactor每一个reactor独立拥有一个selector,在网络通信中大体设计为负责连接的主Reactor,其中在主Reactor的run函数中若selector检测到了连接事件的发生则dispatch该事件。
让负责管理连接的Handler处理连接,其中在这个负责连接的Handler处理器中创建子Handler用以处理IO请求。这样一来连接请求与IO请求分开执行提高通道的并发量。同时多个Reactor带来的好处是多个selector可以提高通道的检索速度
1.1 主服务器
package com.crazymakercircle.ReactorModel;
import com.crazymakercircle.NioDemoConfig;
import com.crazymakercircle.util.Logger;
import java.io.IOException;
import java.net.InetSocketAddress;
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.atomic.AtomicInteger;
class MultiThreadEchoServerReactor {
ServerSocketChannel serverSocket;
AtomicInteger next = new AtomicInteger(0);
Selector bossSelector = null;
Reactor bossReactor = null;
//selectors集合,引入多个selector选择器
//多个选择器可以更好的提高通道的并发量
Selector[] workSelectors = new Selector[2];
//引入多个子反应器
//如果CPU是多核的可以开启多个子Reactor反应器,这样每一个子Reactor反应器还可以独立分配一个线程。
//每一个线程可以单独绑定一个单独的Selector选择器以提高通道并发量
Reactor[] workReactors = null;
MultiThreadEchoServerReactor() throws IOException {
bossSelector = Selector.open();
//初始化多个selector选择器
workSelectors[0] = Selector.open();
workSelectors[1] = Selector.open();
serverSocket = ServerSocketChannel.open();
InetSocketAddress address =
new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP,
NioDemoConfig.SOCKET_SERVER_PORT);
serverSocket.socket().bind(address);
//非阻塞
serverSocket.configureBlocking(false);
//第一个selector,负责监控新连接事件
SelectionKey sk =
serverSocket.register(bossSelector, SelectionKey.OP_ACCEPT);
//附加新连接处理handler处理器到SelectionKey(选择键)
sk.attach(new AcceptorHandler());
//处理新连接的反应器
bossReactor = new Reactor(bossSelector);
//第一个子反应器,一子反应器负责一个选择器
Reactor subReactor1 = new Reactor(workSelectors[0]);
//第二个子反应器,一子反应器负责一个选择器
Reactor subReactor2 = new Reactor(workSelectors[1]);
workReactors = new Reactor[]{subReactor1, subReactor2};
}
private void startService() {
new Thread(bossReactor).start();
// 一子反应器对应一条线程
new Thread(workReactors[0]).start();
new Thread(workReactors[1]).start();
}
//反应器
class Reactor implements Runnable {
//每条线程负责一个选择器的查询
final Selector selector;
public Reactor(Selector selector) {
this.selector = selector;
}
public void run() {
try {
while (!Thread.interrupted()) {
//单位为毫秒
//每隔一秒列出选择器感应列表
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
if (null == selectedKeys || selectedKeys.size() == 0) {
//如果列表中的通道注册事件没有发生那就继续执行
continue;
}
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
//Reactor负责dispatch收到的事件
SelectionKey sk = it.next();
dispatch(sk);
}
//清楚掉已经处理过的感应事件,防止重复处理
selectedKeys.clear();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
void dispatch(SelectionKey sk) {
Runnable handler = (Runnable) sk.attachment();
//调用之前attach绑定到选择键的handler处理器对象
if (handler != null) {
handler.run();
}
}
}
// Handler:新连接处理器
class AcceptorHandler implements Runnable {
public void run() {
try {
SocketChannel channel = serverSocket.accept();
Logger.info("接收到一个新的连接");
if (channel != null) {
int index = next.get();
Logger.info("选择器的编号:" + index);
Selector selector = workSelectors[index];
new MultiThreadEchoHandler(selector, channel);
}
} catch (IOException e) {
e.printStackTrace();
}
if (next.incrementAndGet() == workSelectors.length) {
next.set(0);
}
}
}
public static void main(String[] args) throws IOException {
MultiThreadEchoServerReactor server =
new MultiThreadEchoServerReactor();
server.startService();
}
}
按上述的设计思想,在主服务器中实际上设计了三个Reactor,一个主Reactor专门负责连接请求并配已单独的selector,但是三个Reactor的线程Run函数是做的相同的功能,都是根据每个线程内部的selector进行检索事件列表,若注册的监听事件发生了则调用dispactch分发到每个Reactor对应的Handler。
这里需要注意的一开始其实只有负责连接事件的主Reactor在注册selector的时候给相应的key配了一个AcceptorHandler()。
//第一个selector,负责监控新连接事件
SelectionKey sk =
serverSocket.register(bossSelector, SelectionKey.OP_ACCEPT);
//附加新连接处理handler处理器到SelectionKey(选择键)
sk.attach(new AcceptorHandler());
但是Reactor的run方法里若相应的selector key发生了便要dispatch到一个Handler。这里其他两个子Reactor的Handler在哪里赋值的呢?其实在处理连接请求的Reactor中便创建了各个子Handler,如下代码所示:
主Handler中先是根据服务器channel创建出客服端channel,在进行子selector与channel的绑定。
int index = next.get();
Logger.info("选择器的编号:" + index);
Selector selector = workSelectors[index];
new MultiThreadEchoHandler(selector, channel);
2.1 IO请求handler+线程池
package com.crazymakercircle.ReactorModel;
import com.crazymakercircle.util.Logger;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class MultiThreadEchoHandler implements Runnable {
final SocketChannel channel;
final SelectionKey sk;
final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
static final int RECIEVING = 0, SENDING = 1;
int state = RECIEVING;
//引入线程池
static ExecutorService pool = Executors.newFixedThreadPool(4);
MultiThreadEchoHandler(Selector selector, SocketChannel c) throws IOException {
channel = c;
channel.configureBlocking(false);
//唤醒选择,防止register时 boss线程被阻塞,netty 处理方式比较优雅,会在同一个线程注册事件,避免阻塞boss
selector.wakeup();
//仅仅取得选择键,后设置感兴趣的IO事件
sk = channel.register(selector, 0);
//将本Handler作为sk选择键的附件,方便事件dispatch
sk.attach(this);
//向sk选择键注册Read就绪事件
sk.interestOps(SelectionKey.OP_READ);
//唤醒选择,是的OP_READ生效
selector.wakeup();
Logger.info("新的连接 注册完成");
}
public void run() {
//异步任务,在独立的线程池中执行
pool.execute(new AsyncTask());
}
//异步任务,不在Reactor线程中执行
public synchronized void asyncRun() {
try {
if (state == SENDING) {
//写入通道
channel.write(byteBuffer);
//写完后,准备开始从通道读,byteBuffer切换成写模式
byteBuffer.clear();
//写完后,注册read就绪事件
sk.interestOps(SelectionKey.OP_READ);
//写完后,进入接收的状态
state = RECIEVING;
} else if (state == RECIEVING) {
//从通道读
int length = 0;
while ((length = channel.read(byteBuffer)) > 0) {
Logger.info(new String(byteBuffer.array(), 0, length));
}
//读完后,准备开始写入通道,byteBuffer切换成读模式
byteBuffer.flip();
//读完后,注册write就绪事件
sk.interestOps(SelectionKey.OP_WRITE);
//读完后,进入发送的状态
state = SENDING;
}
//处理结束了, 这里不能关闭select key,需要重复使用
//sk.cancel();
} catch (IOException ex) {
ex.printStackTrace();
}
}
//异步任务的内部类
class AsyncTask implements Runnable {
public void run() {
MultiThreadEchoHandler.this.asyncRun();
}
}
}
在处理IO请求的Handler中采用了线程池,已达到异步处理的目的。
3.1 客户端
package com.crazymakercircle.ReactorModel;
import com.crazymakercircle.NioDemoConfig;
import com.crazymakercircle.util.Dateutil;
import com.crazymakercircle.util.Logger;
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.util.Iterator;
import java.util.Scanner;
import java.util.Set;
/**
* create by 尼恩 @ 疯狂创客圈
**/
public class EchoClient {
public void start() throws IOException {
InetSocketAddress address =
new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_IP,
NioDemoConfig.SOCKET_SERVER_PORT);
// 1、获取通道(channel)
SocketChannel socketChannel = SocketChannel.open(address);
Logger.info("客户端连接成功");
// 2、切换成非阻塞模式
socketChannel.configureBlocking(false);
//不断的自旋、等待连接完成,或者做一些其他的事情
while (!socketChannel.finishConnect()) {
}
Logger.tcfo("客户端启动成功!");
//启动接受线程
Processer processer = new Processer(socketChannel);
new Thread(processer).start();
}
static class Processer implements Runnable {
final Selector selector;
final SocketChannel channel;
Processer(SocketChannel channel) throws IOException {
//Reactor初始化
selector = Selector.open();
this.channel = channel;
channel.register(selector,
SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
public void run() {
try {
while (!Thread.interrupted()) {
selector.select();
Set<SelectionKey> selected = selector.selectedKeys();
Iterator<SelectionKey> it = selected.iterator();
while (it.hasNext()) {
SelectionKey sk = it.next();
if (sk.isWritable()) {
ByteBuffer buffer = ByteBuffer.allocate(NioDemoConfig.SEND_BUFFER_SIZE);
Scanner scanner = new Scanner(System.in);
Logger.tcfo("请输入发送内容:");
if (scanner.hasNext()) {
SocketChannel socketChannel = (SocketChannel) sk.channel();
String next = scanner.next();
buffer.put((Dateutil.getNow() + " >>" + next).getBytes());
buffer.flip();
// 操作三:发送数据
socketChannel.write(buffer);
buffer.clear();
}
}
if (sk.isReadable()) {
// 若选择键的IO事件是“可读”事件,读取数据
SocketChannel socketChannel = (SocketChannel) sk.channel();
//读取数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int length = 0;
while ((length = socketChannel.read(byteBuffer)) > 0) {
byteBuffer.flip();
Logger.info("server echo:" + new String(byteBuffer.array(), 0, length));
byteBuffer.clear();
}
}
//处理结束了, 这里不能关闭select key,需要重复使用
//selectionKey.cancel();
}
selected.clear();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
public static void main(String[] args) throws IOException {
new EchoClient().start();
}
}
多线程Reactor模式的更多相关文章
- Java进阶(五)Java I/O模型从BIO到NIO和Reactor模式
原创文章,同步发自作者个人博客,http://www.jasongj.com/java/nio_reactor/ Java I/O模型 同步 vs. 异步 同步I/O 每个请求必须逐个地被处理,一个请 ...
- Java I/O模型从BIO到NIO和Reactor模式(转)
原创文章,转载请务必将下面这段话置于文章开头处(保留超链接).本文转发自技术世界,原文链接 http://www.jasongj.com/java/nio_reactor/ Java I/O模型 同步 ...
- reactor模式:多线程的reactor模式
上文说到单线程的reactor模式 reactor模式:单线程的reactor模式 单线程的reactor模式并没有解决IO和CPU处理速度不匹配问题,所以多线程的reactor模式引入线程池的概念, ...
- 什么是Reactor模式,或者叫反应器模式
Reactor这个词译成汉语还真没有什么合适的,很多地方叫反应器模式,但更多好像就直接叫reactor模式了,其实我觉着叫应答者模式更好理解一些.通过了解,这个模式更像一个侍卫,一直在等待你的召唤,或 ...
- Reactor模式通俗解释
Reactor这个词译成汉语还真没有什么合适的,很多地方叫反应器模式,但更多好像就直接叫reactor模式了,其实我觉着叫应答者模式更好理解一些.通过了解,这个模式更像一个侍卫,一直在等待你的召唤,或 ...
- Reactor模式与Proactor模式
该文章总结了网上资源对这两种模式的描述 原文地址:http://www.cnblogs.com/dawen/archive/2011/05/18/2050358.html 1.标准定义 两种I/O多路 ...
- Reactor模式详解
转自:http://www.blogjava.net/DLevin/archive/2015/09/02/427045.html 前记 第一次听到Reactor模式是三年前的某个晚上,一个室友突然跑过 ...
- Reactor模式解析——muduo网络库
最近一段时间阅读了muduo源码,读完的感受有一个感受就是有点乱.当然不是说代码乱,是我可能还没有完全消化和理解.为了更好的学习这个库,还是要来写一些东西促进一下. 我一边读一边尝试在一些地方改用c+ ...
- Reactor模式
对象行为类的设计模式,对同步事件分拣和派发.别名Dispatcher(分发器) Reactor模式是处理并发I/O比较常见的一种模式,用于同步I/O,中心思想是将所有要处理的I/O事件注册到一个中心I ...
随机推荐
- python socket zmq
本篇博客将介绍zmq应答模式,所谓应答模式,就是一问一答,规则有这么几条 1. 必须先提问,后回答 2. 对于一个提问,只能回答一次 3. 在没有收到回答前不能再次提问 上代码,服务端: #codin ...
- 【c++ Prime 学习笔记】第15章 面向对象程序设计
15.1 OOP:概述 面向对象程序设计(object-oriented programming)的核心思想是:数据抽象.继承.动态绑定 使用数据抽象,可将类的接口与实现分离 使用继承,可定义相似的类 ...
- 【数据结构与算法Python版学习笔记】递归(Recursion)——定义及应用:分形树、谢尔宾斯基三角、汉诺塔、迷宫
定义 递归是一种解决问题的方法,它把一个问题分解为越来越小的子问题,直到问题的规模小到可以被很简单直接解决. 通常为了达到分解问题的效果,递归过程中要引入一个调用自身的函数. 举例 数列求和 def ...
- Java中的函数式编程(六)流Stream基础
写在前面 如果说函数式接口和lambda表达式是Java中函数式编程的基石,那么stream就是在基石上的最富丽堂皇的大厦. 只有熟悉了stream,你才能说熟悉了Java 的函数式编程. 本文主要介 ...
- 【二食堂】Beta - Scrum Meeting 10
Scrum Meeting 10 例会时间:5.25 18:30~18:50 进度情况 组员 当前进度 今日任务 李健 1. 继续文本导入.保存部分的工作issue 2. 完成了技术博客 1. 继续文 ...
- UltraSoft - Alpha - Scrum Meeting 3
Date: Apr 15th, 2020. 会议内容为 贡献分确定与进度汇报. Scrum 情况汇报 进度情况 组员 负责 昨日进度 后两日任务 CookieLau PM.后端 学习前后端分离技术的项 ...
- 人人都写过的5个Bug!
大家好,我是良许. 计算机专业的小伙伴,在学校期间一定学过 C 语言.它是众多高级语言的鼻祖,深入学习这门语言会对计算机原理.操作系统.内存管理等等底层相关的知识会有更深入的了解,所以我在直播的时候, ...
- 安装hexo博客
前言 ** 跟着步骤一步一步来进行安装 ** 准备环境:node.js和包管理器npm 1:查看包文件 接着安装 淘宝镜像源 sudo这个需要添加获取文件夹访问权限 sudo npm install ...
- python生成有声小说模拟真人发音
生成有声小说原理 文字是1500字内的生成微软文档说说 用代码实现小说爬取正本 实现每章小说1450字 实现自动剪切后添加封面 实现自动上传 用python代码实现爬取小说,本案列以一本小说为实列代码 ...
- Pytorch中stack()方法的理解
Torch.stack() 1. 概念 在一个新的维度上连接一个张量序列 2. 参数 tensors (sequence)需要连接的张量序列 dim (int)在第dim个维度上连接 注意输入的张量s ...