网络编程NIO之Reactor线程模型
上篇文章中写了一些NIO相关的知识以及简单的NIO实现示例,但是示例中,客户端连接以及读取、写入、处理客户端数据时都是在一个线程中,单个线程处理客户端的数据,性能会很差,而且不能充分利用服务器的性能,这篇文章主要介绍Reactor线程模型,NIO的多路复用知识,用以提供服务端的性能。
单Reactor线程模型

单Reactor线程模型,最终还是使用了一个线程,和上篇文章最后的示例基本上没啥差别,只是拆分了三个类来进行处理。
基于工作线程的Reactor线程模型

这里的线程模型,是在客户端连接后,业务数据的部分抽出来,放到线程池中处理,这样做的好处是,如果处理业务数据时间很长,也不会影响客户端的读写操作。

上面图示是简单的基于工作线程的工作模式,把业务数据处理单独抽出来,在线程池处理。
多Reactor线程模型

最终的线程模型,是多Reactor线程模型。
客户端数据的读写也是在多个线程中进行处理,充分提高了性能。

多Reactor线程模型示例
MainReactor-Thread和Acceptor代码:
/**
* mainReactor-Thread
* 接收客户端连接,然后交给Acceptor处理
*/
class MainReactor extends Thread {
//创建一个Selector
public Selector selector;
AtomicInteger integer = new AtomicInteger(0);
public MainReactor() throws IOException {
selector = Selector.open();
}
@Override
public void run() {
while (true) {
try {
//启动Selector
selector.select();
//获取事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//遍历事件
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
//获取客户端通道
SocketChannel socketChannel = ((ServerSocketChannel)selectionKey.channel()).accept();
Acceptor acceptor = new Acceptor();
//把客户端通道交给Acceptor去处理
acceptor.register(socketChannel);
}
//处理完之后要移除
iterator.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void register(ServerSocketChannel serverSocketChannel) throws ClosedChannelException {
//注册OP_ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
/**
* 处理客户端的连接
* 给每个客户端连接分配一个subReactor-Thread
*/
class Acceptor {
public void register(SocketChannel socketChannel) throws IOException {
//设置为非阻塞模式
socketChannel.configureBlocking(false);
int index = integer.getAndIncrement() % subReactors.length;
SubReactor subReactor = subReactors[index];
//给客户端连接分配一个subReactor线程
subReactor.register(socketChannel);
//启动subReactor线程
subReactor.start();
System.out.println("收到新连接:" + socketChannel.getRemoteAddress());
}
}
}
SubReactor-Threads代码:
/**
* 一个线程负责多个客户端连接
* 从channel中读数据、写数据
*/
class SubReactor extends Thread {
//创建一个Selector
public Selector selector;
//用于判断SubReactor线程是否已经启动
public volatile boolean isRunning = false;
public SubReactor() throws IOException {
selector = Selector.open();
}
@Override
public void start() {
//判断SubReactor线程是否已经启动
//如果没有启动,就启动SubReactor线程
if (!isRunning) {
isRunning = true;
super.start();
}
}
@Override
public void run() {
while (isRunning) {
try {
//启动Selector
selector.select();
//获取事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//遍历事件
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isReadable()) {
try {
SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
new Handler(socketChannel);
} catch (Exception e) {
e.printStackTrace();
selectionKey.cancel();
}
}
//处理完之后要移除
iterator.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void register(SocketChannel socketChannel) throws IOException {
//注册OP_READ事件
socketChannel.register(selector, SelectionKey.OP_READ);
}
/** 读取或者写入数据 */
class Handler {
//用来读取或者写入数据
public Handler(SocketChannel socketChannel) throws IOException {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
while (socketChannel.isOpen() && socketChannel.read(readBuffer) != -1) {
//如果有数据可读,简单的判断一下大于0
if (readBuffer.position() > 0) {
break;
}
}
//没有数据可读,就直接返回
if (readBuffer.position() == 0) {
return;
}
//转换为读取模式
readBuffer.flip();
byte[] bytes = new byte[readBuffer.limit()];
readBuffer.get(bytes);
System.out.println("获取到新的数据:" + new String(bytes));
System.out.println("获取到新的数据,来自:" + socketChannel.getRemoteAddress());
//线程池,用来处理业务数据
threadPool.execute(new Runnable() {
@Override
public void run() {
}
});
//向客户端写数据
String response = "HTTP/1.1 200 OK\r\n" +
"Content-Length: 11\r\n\r\n" +
"hello world";
ByteBuffer writeBuffer = ByteBuffer.wrap(response.getBytes());
while (writeBuffer.hasRemaining()) {
socketChannel.write(writeBuffer);
}
}
}
}
初始化代码:
/** 服务端通道 */
public ServerSocketChannel serverSocketChannel;
/** 用来接收客户端连接 */
public MainReactor mainReactor;
/** 用来处理客户端连接的读取、写入 */
public SubReactor[] subReactors = new SubReactor[10];
/** 线程池,用来处理客户端连接后的业务逻辑 */
public ExecutorService threadPool = Executors.newCachedThreadPool();
public static void main(String[] args) throws IOException {
NioReactor nioReactor = new NioReactor();
nioReactor.initAndRegister();
nioReactor.init();
nioReactor.bind();
}
/** 初始化服务端 */
public void init() throws IOException {
//创建一个服务端通道
serverSocketChannel = ServerSocketChannel.open();
//设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
//注册到mainReactor-Thread
mainReactor.register(serverSocketChannel);
//启动mainReactor-Thread线程
mainReactor.start();
}
/** 服务端绑定端口 */
public void bind() throws IOException {
serverSocketChannel.socket().bind(new InetSocketAddress(8056));
System.out.println("服务端启动成功");
}
/** 初始化MainReactor和SubReactor */
public void initAndRegister() throws IOException {
mainReactor = new MainReactor();
for (int i=0; i<subReactors.length; i++) {
subReactors[i] = new SubReactor();
}
}
上面代码mainReactorThread和subReactorThread中有大量的重复代码,可以提取出来处理:
/** 多路复用,reactor线程模型 */
public class NioReactor2 {
abstract class ReactorThread extends Thread {
//创建一个Selector
public Selector selector;
//用于判断线程是否已经启动
public volatile boolean isRunning = false;
/** 有事件发生,就调用这个方法 */
public abstract void handler(SelectableChannel channel) throws IOException;
public ReactorThread() throws IOException {
selector = Selector.open();
}
@Override
public void run() {
while (isRunning) {
//启动Selector
try {
selector.select();
//获取事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//遍历事件
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
int readyOps = key.readyOps();
//只关注是否有OP_ACCEPT和OP_READ事件
if ((readyOps & (SelectionKey.OP_ACCEPT | SelectionKey.OP_READ)) != 0 || readyOps == 0) {
try {
//获取channel
SelectableChannel channel = key.channel();
//设置为非阻塞模式
channel.configureBlocking(false);
//有事件,就调用handler方法
handler(channel);
if (!channel.isOpen()) {
//如果channel关闭,就取消key
key.cancel();
}
} catch (Exception e) {
e.printStackTrace();
//如果有异常,就取消key
key.cancel();
}
}
//处理完之后要移除
iterator.remove();
}
selector.selectNow();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public SelectionKey register(SelectableChannel channel) throws ClosedChannelException {
//先注册到Selector,并没有注册任何事件
return channel.register(selector, 0);
}
@Override
public void start() {
//判断SubReactor线程是否已经启动
//如果没有启动,就启动SubReactor线程
if (!isRunning) {
isRunning = true;
super.start();
}
}
}
/** 服务端通道 */
public ServerSocketChannel serverSocketChannel;
/** 用来接收客户端连接 */
public ReactorThread[] mainReactors = new ReactorThread[1];;
/** 用来处理客户端连接的读取、写入 */
public ReactorThread[] subReactors = new ReactorThread[10];
/** 线程池,用来处理客户端连接后的业务逻辑 */
public ExecutorService threadPool = Executors.newCachedThreadPool();
/**
* 初始化mainReactors和subReactors
*/
public void initAndRegister() throws IOException {
//subReactors线程,用来客户端连接后的读写
for (int i=0; i<subReactors.length; i++) {
subReactors[i] = new ReactorThread() {
@Override
public void handler(SelectableChannel channel) throws IOException {
SocketChannel socketChannel = (SocketChannel)channel;
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
while (socketChannel.isOpen() && socketChannel.read(readBuffer) != -1) {
//如果有数据可读,简单的判断一下大于0
if (readBuffer.position() > 0) {
break;
}
}
//没有数据可读,就直接返回
if (readBuffer.position() == 0) {
return;
}
//转换为读取模式
readBuffer.flip();
byte[] bytes = new byte[readBuffer.limit()];
readBuffer.get(bytes);
System.out.println("获取到新的数据:" + new String(bytes));
System.out.println("获取到新的数据,来自:" + socketChannel.getRemoteAddress());
//线程池,用来处理业务数据
threadPool.execute(new Runnable() {
@Override
public void run() {
}
});
//向客户端写数据
String response = "HTTP/1.1 200 OK\r\n" +
"Content-Length: 11\r\n\r\n" +
"hello world";
ByteBuffer writeBuffer = ByteBuffer.wrap(response.getBytes());
while (writeBuffer.hasRemaining()) {
socketChannel.write(writeBuffer);
}
}
};
}
//mainReactors线程,用于客户端的连接
for (int i=0; i<mainReactors.length; i++) {
mainReactors[i] = new ReactorThread() {
AtomicInteger integer = new AtomicInteger(0);
@Override
public void handler(SelectableChannel channel) throws IOException {
//获取客户端通道
SocketChannel socketChannel = ((ServerSocketChannel)channel).accept();
//设置为非阻塞模式
socketChannel.configureBlocking(false);
int index = integer.getAndIncrement() % subReactors.length;
ReactorThread subReactor = subReactors[index];
//启动线程
subReactor.start();
//注册事件
SelectionKey key = subReactor.register(socketChannel);
key.interestOps(SelectionKey.OP_READ);
System.out.println("收到新连接:" + socketChannel.getRemoteAddress());
}
};
}
}
/** 初始化服务端 */
public void init() throws IOException {
//创建一个服务端通道
serverSocketChannel = ServerSocketChannel.open();
//设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
//注册到mainReactor-Thread
int index = new Random().nextInt(mainReactors.length);
SelectionKey keys = mainReactors[index].register(serverSocketChannel);
keys.interestOps(SelectionKey.OP_ACCEPT);
//启动mainReactor-Thread线程
mainReactors[index].start();
}
/** 服务端绑定端口 */
public void bind() throws IOException {
serverSocketChannel.socket().bind(new InetSocketAddress(8056));
System.out.println("服务端启动成功");
}
public static void main(String[] args) throws IOException {
NioReactor2 nioReactor = new NioReactor2();
nioReactor.initAndRegister();
nioReactor.init();
nioReactor.bind();
}
}
结束语
到此,NIO中的Reactor线程模型就结束了,上面的示例可以拆分几个类进行处理,还可以根据HTTP协议的部分,解析请求,做一个简单的tomcat服务器。
网络编程NIO之Reactor线程模型的更多相关文章
- 原生JDK网络编程- NIO之Reactor模式
“反应”器名字中”反应“的由来: “反应”即“倒置”,“控制逆转”,具体事件处理程序不调用反应器,而向反应器注册一个事件处理器,表示自己对某些事件感兴趣,有时间来了,具体事件处理程序通过事件处理器对某 ...
- 【Netty源码分析】Reactor线程模型
1. 背景 1.1. Java线程模型的演进 1.1.1. 单线程 时间回到十几年前,那时主流的CPU都还是单核(除了商用高性能的小机),CPU的核心频率是机器最重要的指标之一. 在Java领域当时比 ...
- Netty高性能之Reactor线程模型
Netty是一个高性能.异步事件驱动的NIO框架,它提供了对TCP.UDP和文件传输的支持,作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用 ...
- Linux 网络编程的5种IO模型:多路复用(select/poll/epoll)
Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 背景 我们在上一讲 Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO中,对于其中的 阻塞/非阻塞IO 进行了 ...
- Netty Reactor 线程模型笔记
引用: https://www.cnblogs.com/TomSnail/p/6158249.html https://www.cnblogs.com/heavenhome/articles/6554 ...
- Reactor 线程模型以及在netty中的应用
这里我们需要理解的一点是Reactor线程模型是基于同步非阻塞IO实现的.对于异步非阻塞IO的实现是Proactor模型. 一 Reactor 单线程模型 Reactor单线程模型就是指所有的IO操作 ...
- Linux 网络编程的5种IO模型:异步IO模型
Linux 网络编程的5种IO模型:异步IO模型 资料已经整理好,但是还有未竟之业:复习多路复用epoll 阅读例程, 异步IO 函数实现 背景 上一讲< Linux 网络编程的5种IO模型:信 ...
- 深入Netty逻辑架构,从Reactor线程模型开始
本文是Netty系列第6篇 上一篇文章我们从一个Netty的使用Demo,了解了用Netty构建一个Server服务端应用的基本方式.并且从这个Demo出发,简述了Netty的逻辑架构,并对Chann ...
- Linux 网络编程的5种IO模型:信号驱动IO模型
Linux 网络编程的5种IO模型:信号驱动IO模型 背景 上一讲 Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 我们讲解了多路复用等方面的知识,以及有关例程. ...
随机推荐
- 生成类库项目时同时生成的pdb文件是什么东东?
英文全称:Program Database File Debug里的PDB是full,保存着调试和项目状态信息.有断言.堆栈检查等代码.可以对程序的调试配置进行增量链接.Release 里的PDB是p ...
- StrictMode 检测应用
Application, Activity, or other application component's onCreate() method:if (BuildConfig.SHOW_LOG) ...
- js中数据、内存、变量的概念及三者之间的关系
目录 数据.内存.变量的概念及三者之间的关系 什么是数据 数据的特点 什么是内存 栈内存 堆内存 JS引擎如何管理内存 什么是变量 变量是普通类型时 变量是引用类型时 数据.内存.变量的三者之间的关系 ...
- Hi3559AV100外接UVC/MJPEG相机实时采图设计(二):V4L2接口的实现(以YUV422为例)
下面将给出Hi3559AV100外接UVC/MJPEG相机实时采图设计的整体流程,主要实现是通过V4L2接口将UVC/MJPEG相机采集的数据送入至MPP平台,经过VDEC.VPSS.VO最后通过HD ...
- Codeforces 1167c(ccpc wannafly camp day1) News Distribution 并查集模板
题目: In some social network, there are nn users communicating with each other in mm groups of friends ...
- 【老孟Flutter】Flutter 2.0 重磅更新
老孟导读:昨天期待已久的 Flutter 2.0 终于发布了,Web 端终于提正了,春季期间我发布的一篇文章,其中的一个预测就是 Web 正式发布,已经实现了,还有一个预测是:2021年将是 Flut ...
- C#中事件流程的简单理解
C#中事件流程的简单理解 C#中事件基于委托,要理解事件要先理解委托,但是现在我还没想好怎么写委托,如果不懂委托可以先找找委托的文章 事件基于委托,为委托提供了一种发布/订阅机制 一上来就是这句话,很 ...
- python爬虫(正则取数据)读取表格内的基金代码后爬取基金最新净值,同时写到对应的表格中,基于最近一次购买净值计算出涨跌幅(名字有点长)
最近基金跌的真够猛,虽说是定投,但大幅度下跌,有时候适当的增加定投数也是降低平均成本的一种方式 每天去看去算太费时间,写了个爬虫,让他自动抓数据后自动计算出来吧 实现逻辑: 1.创建了一个excel表 ...
- 苹果M1处理器Mac“翻车”:用户吐槽SSD
SSD如今被不少PC用户所推崇,其优势在于读写速度快.工作无噪音,不过,缺点是寿命较机械硬盘差点.厂商对SSD通常都会标注一个最大可写入量(TBW),提醒用户关注健康数据,以免掉盘等严重问题. 越来越 ...
- Python3读取网页HTML代码,并保存在本地文件中
旧版Python中urllib模块内有一个urlopen方法可打开网页,但新版python中没有了,新版的urllib模块里面只有4个子模块(error,request,response,parse) ...