网络编程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) 我们讲解了多路复用等方面的知识,以及有关例程. ...
随机推荐
- 1053 Path of Equal Weight——PAT甲级真题
1053 Path of Equal Weight 给定一个非空的树,树根为 RR. 树中每个节点 TiTi 的权重为 WiWi. 从 RR 到 LL 的路径权重定义为从根节点 RR 到任何叶节点 L ...
- WPF MVVM实例三
在没给大家讲解wpf mwm示例之前先给大家简单说下MVVM理论知识: WPF技术的主要特点是数据驱动UI,所以在使用WPF技术开发的过程中是以数据为核心的,WPF提供了数据绑定机制,当数据发生变化时 ...
- 将项目加载到tomcat中的时候报错:Tomcat version 6.0 only supports J2EE 1.2, 1.3, 1.4, and Java EE 5 Web modules
转自:http://jingwang0523.blog.163.com/blog/static/9090710320113294551497/ 最近在用eclipse做项目,新建项目时什么都贪新,用最 ...
- 【重磅】iNeuOS工业互联平台,系统集成业务模型和WEB组态视图建模集成3D模型
目 录 1. 概述... 1 2. 平台演示... 2 3. 系统集成业务模型... 2 4. WEB组态视图建模集成3D模型... 3 5. ...
- Django登录使用的技术和组件
登录 ''' 获取用户所有的数据 每条数据请求的验证 成功之后获取所有正确的信息 失败则显示错误信息 ''' #登陆页面管理 def login(request): if request.method ...
- vscode中js文件使用typescript语法报错,如何解决
原因:由于vcode自身的语法检查有些问题 解决办法:在设置里面加上 "javascript.validate.enable": false 禁用默认的 js 验证 总结: 由于v ...
- C# smtp邮件发送
第一种方式发送邮件,不读取配置文件发送邮件,(本地测试可以,但是服务器上不行) /// <summary> /// 发送邮件 /// </summary> /// <pa ...
- slickgrid ( nsunleo-slickgrid ) 2 修正区域选择不能跨冻结列的问题
slickgrid( nsunleo-slickgrid ) 2 修正区域选择不能跨冻结列的问题 周六的时候,留了个小小的尾巴,区域选择的问题进做到了定位: 问题原因,在slickgrid启动冻结之 ...
- 2018.8.30 nowcoder oi赛制测试1
2018.8.30 nowcoder oi赛制测试1 普及组难度,发现了一些问题 A 题目大意:求斐波那契数列\(f(k-1)f(k+1)-f(k)^2\),范围极大 打表可得规律 其实是卡西尼恒等式 ...
- HDU_6693 Valentine's Day 【概率问题】
一.题目 Valentine's Day 二.分析 假设$ s_0 $代表不开心的概率,$ s_1 $代表开心一次的概率. 那么随便取一个物品,那么它的开心概率为$ p _i $,可以推导加入之后使女 ...