# 摘要

在前两篇《快速理解Linux网络I_O》、《java的I_O模型-BIO&NIO&AIO》两边中介绍了Linux下的I/O模型和java中的I/O模型,今天我们介绍Reactor模型,并探究Netty的实现

高性能服务器

在互联网时代,我们使用的软件基本上全是C/S架构,C/S架构的软件一个明显的好处就是:只要有网络,你可以在任何地方干同一件事。C/S架构可以抽象为如下模型:

  • C就是Client(客户端),上面的B是Browser(浏览器)
  • S就是Server(服务器):服务器管理某种资源,并且通过操作这种资源来为它的客户端提供某种服务

那服务器如何能快速的处理用户的请求呢?在我看来高性能服务器至少要满足如下几个需求:

  • 效率高:既然是高性能,那处理客户端请求的效率当然要很高了
  • 高可用:不能随便就挂掉了
  • 编程简单:基于此服务器进行业务开发需要足够简单
  • 可扩展:可方便的扩展功能
  • 可伸缩:可简单的通过部署的方式进行容量的伸缩,也就是服务需要无状态

而满足如上需求的一个基础就是高性能的IO!

Reactor模式

什么是Reactor模式?

两种I/O多路复用模式:Reactor和Proactor,两个与事件分离器有关的模式是Reactor和Proactor。Reactor模式采用同步IO,而Proactor采用异步IO。

在Reactor中,事件分离器负责等待文件描述符或socket为读写操作准备就绪,然后将就绪事件传递给对应的处理器,最后由处理器负责完成实际的读写工作。

在Proactor模式中,处理器--或者兼任处理器的事件分离器,只负责发起异步读写操作。IO操作本身由操作系统来完成。传递给操作系统的参数需要包括用户定义的数据缓冲区地址和数据大小,操作系统才能从中得到写出操作所需数据,或写入从socket读到的数据。事件分离器捕获IO操作完成事件,然后将事件传递给对应处理器。

说人话的方式理解:

  • reactor:能收了你跟俺说一声。
  • proactor: 你给我收十个字节,收好了跟俺说一声。

Doug Lea是这样类比的

  • Reactor通过调度适当的处理程序来响应IO事件;
  • 处理程序执行非阻塞操作
  • 通过将处理程序绑定到事件来管理;

Reactor单线程模型设计

单线程版本Java NIO的支持:

  • Channels:与支持非阻塞读取的文件,套接字等的连接

  • Buffers:类似于数组的对象,可由Channels直接读取或写入

  • Selectors:通知一组通道中哪一个有IO事件

  • SelectionKeys:维护IO事件状态和绑定

  • Reactor 代码如下

public class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel serverSocketChannel; public Reactor(int port) throws IOException {
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
key.attach(new Acceptor());
} @Override
public void run() {
while (!Thread.interrupted()) {
try {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
dispatch(selectionKey);
}
selectionKeys.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
} private void dispatch(SelectionKey selectionKey) {
Runnable run = (Runnable) selectionKey.attachment();
if (run != null) {
run.run();
}
} class Acceptor implements Runnable {
@Override
public void run() {
try {
SocketChannel channel = serverSocketChannel.accept();
if (channel != null) {
new Handler(selector, channel);
}
} catch (IOException e) {
e.printStackTrace();
}
}
} public static void main(String[] args) throws IOException {
new Thread(
new Reactor(1234)
).start();
} }
  • Handler代码如下:
public class Handler implements Runnable{
private final static int DEFAULT_SIZE = 1024;
private final SocketChannel socketChannel;
private final SelectionKey seletionKey;
private static final int READING = 0;
private static final int SENDING = 1;
private int state = READING; ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE); public Handler(Selector selector, SocketChannel channel) throws IOException {
this.socketChannel = channel;
socketChannel.configureBlocking(false);
this.seletionKey = socketChannel.register(selector, 0);
seletionKey.attach(this);
seletionKey.interestOps(SelectionKey.OP_READ);
selector.wakeup();
} @Override
public void run() {
if (state == READING) {
read();
} else if (state == SENDING) {
write();
}
} private void write() {
try {
socketChannel.write(outputBuffer);
} catch (IOException e) {
e.printStackTrace();
}
while (outIsComplete()) {
seletionKey.cancel();
}
} private void read() {
try {
socketChannel.read(inputBuffer);
if (inputIsComplete()) {
process();
System.out.println("接收到来自客户端(" + socketChannel.socket().getInetAddress().getHostAddress()
+ ")的消息:" + new String(inputBuffer.array()));
seletionKey.attach(new Sender());
seletionKey.interestOps(SelectionKey.OP_WRITE);
seletionKey.selector().wakeup();
}
} catch (IOException e) {
e.printStackTrace();
}
} public boolean inputIsComplete() {
return true;
}
public boolean outIsComplete() {
return true;
} public void process() {
// do something...
} class Sender implements Runnable {
@Override
public void run() {
try {
socketChannel.write(outputBuffer);
} catch (IOException e) {
e.printStackTrace();
}
if (outIsComplete()) {
seletionKey.cancel();
}
}
} }

这个模型和上面的NIO流程很类似,只是将消息相关处理独立到了Handler中去了!虽然说到NIO一个线程就可以支持所有的IO处理。但是瓶颈也是显而易见的!如果这个客户端多次进行请求,如果在Handler中的处理速度较慢,那么后续的客户端请求都会被积压,导致响应变慢!所以引入了Reactor多线程模型!

Reactor多线程模型设计

Reactor多线程模型就是将Handler中的IO操作和非IO操作分开,操作IO的线程称为IO线程,非IO操作的线程称为工作线程!这样的话,客户端的请求会直接被丢到线程池中,客户端发送请求就不会堵塞!

Reactor保持不变,仅需要改动Handler代码:

public class Handler implements Runnable{
private final static int DEFAULT_SIZE = 1024;
private final SocketChannel socketChannel;
private final SelectionKey seletionKey;
private static final int READING = 0;
private static final int SENDING = 1;
private int state = READING; ByteBuffer inputBuffer = ByteBuffer.allocate(DEFAULT_SIZE);
ByteBuffer outputBuffer = ByteBuffer.allocate(DEFAULT_SIZE); private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors());
private static final int PROCESSING = 3;
private Selector selector; public Handler(Selector selector, SocketChannel channel) throws IOException {
this.selector = selector;
this.socketChannel = channel;
socketChannel.configureBlocking(false);
this.seletionKey = socketChannel.register(selector, 0);
seletionKey.attach(this);
seletionKey.interestOps(SelectionKey.OP_READ);
selector.wakeup();
} @Override
public void run() {
if (state == READING) {
read();
} else if (state == SENDING) {
write();
}
} private void write() {
try {
socketChannel.write(outputBuffer);
} catch (IOException e) {
e.printStackTrace();
}
while (outIsComplete()) {
seletionKey.cancel();
}
} private void read() {
try {
socketChannel.read(inputBuffer);
if (inputIsComplete()) {
process();
executorService.execute(new Processer());
}
} catch (IOException e) {
e.printStackTrace();
}
} public boolean inputIsComplete() {
return true;
}
public boolean outIsComplete() {
return true;
} public void process() {
// do something...
} class Sender implements Runnable {
@Override
public void run() {
try {
socketChannel.write(outputBuffer);
} catch (IOException e) {
e.printStackTrace();
}
if (outIsComplete()) {
seletionKey.cancel();
}
}
} synchronized void processAndHandOff() {
process();
// or rebind attachment
state = SENDING;
seletionKey.interestOps(SelectionKey.OP_WRITE);
selector.wakeup();
} class Processer implements Runnable {
@Override
public void run() {
processAndHandOff();
}
} }

主从Reactor多线程模型设计

主从Reactor多线程模型是将Reactor分成两部分,mainReactor负责监听server socket,accept新连接,并将建立的socket分派给subReactor。subReactor负责多路分离已连接的socket,读写网络数据,对业务处理功能,其扔给worker线程池完成。通常,subReactor个数上可与CPU个数等同:

Handler保持不变,仅需要改动Reactor代码:

public class Reactor {
// also create threads
Selector[] selectors;
AtomicInteger next = new AtomicInteger(0);
final ServerSocketChannel serverSocketChannel; private static ExecutorService sunReactors = Executors.newFixedThreadPool(Runtime.getRuntime()
.availableProcessors());
private static final int PROCESSING = 3; public Reactor(int port) throws IOException {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
selectors = new Selector[4];
for (int i = 0; i < selectors.length; i++) {
Selector selector = selectors[i];
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
key.attach(new Acceptor());
new Thread(()->{
while (!Thread.interrupted()) {
try {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
dispatch(selectionKey);
}
selectionKeys.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
} private void dispatch(SelectionKey selectionKey) {
Runnable run = (Runnable) selectionKey.attachment();
if (run != null) {
run.run();
}
} class Acceptor implements Runnable {
@Override
public void run() {
try {
SocketChannel channel = serverSocketChannel.accept();
if (channel != null) {
sunReactors.execute(new Handler(selectors[next.getAndIncrement() % selectors.length], channel));
}
} catch (IOException e) {
e.printStackTrace();
}
}
} public static void main(String[] args) throws IOException {
new Reactor(1234);
} }

以上是三种不同的设计思路,接下来看一下Netty这个一个高性能NIO框架,其是如何实现Reactor模型的!

Netty Reactor模型设计

  • 看一个最简单的Netty服务端代码
public final class EchoServer {
static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));
public static void main(String[] args) throws Exception {
// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
final EchoServerHandler serverHandler = new EchoServerHandler();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(serverHandler);
}
});
ChannelFuture f = b.bind(PORT).sync();
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
  • Netty Server Handler
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.write(msg);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}

我们从Netty服务器代码来看,与Reactor模型进行对应!

  • EventLoopGroup就相当于是Reactor,bossGroup对应主Reactor,workerGroup对应从Reactor
  • TimeServerHandler就是Handler
  • child开头的方法配置的是客户端channel,非child开头的方法配置的是服务端channel

参考

Scalable IO in Java

高性能Server---Reactor模型

NIO技术概览


你的鼓励也是我创作的动力

打赏地址

网络编程-Netty-Reactor模型的更多相关文章

  1. Java网络编程 -- Netty入门

    Netty简介 Netty是一个高性能,高可扩展性的异步事件驱动的网络应用程序框架,它极大的简化了TCP和UDP客户端和服务器端网络开发.它是一个NIO框架,对Java NIO进行了良好的封装.作为一 ...

  2. 网络编程Netty入门:Netty简介及其特性

    目录 Netty的简介 Netty的特性 Netty的整体结构 Netty的核心组件 Netty的线程模型 结束语 Netty的简介 Netty是一个java开源框架,是基于NIO的高性能.高可扩展性 ...

  3. Linux 网络编程(IO模型)

    针对linux 操作系统的5类IO模型,阻塞式.非阻塞式.多路复用.信号驱动和异步IO进行整理,参考<linux网络编程>及相关网络资料. 阻塞模式 在socket编程(如下图)中调用如下 ...

  4. Unix 网络编程 I/O 模型 第六章

    前提,也是重点是, 当接收收据.或者读取数据时,分两步 1 等待数据准备好. 2 从内核拷贝数据到进程. 对于一个network IO 即 socket(这里我们以read举例),它会涉及到两个系统对 ...

  5. 网络编程基础--IO模型

    一 IO模型介绍: 背景 是 Linux环境下 的 network IO , Third Edition: The Sockets Networking ”,.2节“I/O Models ”,Stev ...

  6. 网络编程Netty入门:EventLoopGroup分析

    目录 Netty线程模型 代码示例 NioEventLoopGroup初始化过程 NioEventLoopGroup启动过程 channel的初始化过程 Netty线程模型 Netty实现了React ...

  7. Python网络编程(OSI模型、网络协议、TCP)

    前言: 什么是网络? 网络是由节点和连线构成,表示诸多对象及其相互联系. 在数学上,网络是一种图,一般认为专指加权图. 网络除了数学定义外,还有具体的物理含义,即网络是从某种相同类 型的实际问题中抽象 ...

  8. 网络编程 生产者消费者模型 GiL

    守护进程: 注意事项: 1.必须在p.start()前 2.守护进程不能开子进程 3.如果主进程的运行时间快于子进程,那么就只有主进程的结果,没有守护进程的结果,因为守护进程没有进行完.反之会得到两个 ...

  9. 网络编程Netty IoT百万长连接优化

    目录 IoT推送系统 IoT是什么 IoT推送系统的设计 心跳检测机制 简述心跳检测 心跳检测机制代码示例 百万长连接优化 连接优化代码示例 TCP连接四元组 配置优化 IoT推送系统 IoT是什么 ...

  10. Socket 网络编程和IO模型

    最近做了一个织机数据采集的服务器程序. 结构也非常简单,织机上的嵌入式设备,会通过Tcp 不停的往服务器发送一些即时数据.织机大改有个几十台到几百台不定把 刨去业务,先分析一下网络层的大概情况.每台织 ...

随机推荐

  1. 洛谷P2765 魔术球问题

    题目链接:https://www.luogu.org/problemnew/show/P2765 知识点: 最大流 解题思路: 本题所有边的容量均为 \(1\). 从 \(1\) 开始加入数字,将这个 ...

  2. Java 对象的封装,继承,抽象,接口写法

    面向对象的封装写法        关键字 private class A    {        private int a=1;        private void work()         ...

  3. Jmeter(三) - 从入门到精通 - 测试计划(Test Plan)的元件(详解教程)

    1.简介 上一篇中宏哥已经教你如何通过JMeter来创建一个测试计划(Test Plan),那么这一篇我们就将JMeter启动起来,创建一个测试计划(Test plan),然后宏哥给大家介绍一下测试计 ...

  4. Python数据分析:pandas玩转Excel(三)

    将对象写入Excel工作表. 要将单个对象写入 Excel .xlsx 文件,只需指定目标文件名即可.要写入多个工作表,必须创建具有目标文件名的ExcelWriter对象,并在文件中指定要写入的工作表 ...

  5. Java IO(七)ByteArrayInputStream 和 ByteArrayOutputStream

    Java IO(七)ByteArrayInputStream 和 ByteArrayOutputStream 一.介绍 ByteArrayInputStream 和 ByteArrayOutputSt ...

  6. 【转】B树、B+树、B*树、R 树、QuadTree

    从B树.B+树.B*树谈到R 树 QuadTree(四叉树) 四叉树空间索引原理及其实现

  7. 五、Java - 集合

    一.集合 Java 中的集合类存放于 java.util 包中,是一个存放对象的容器. 集合存放的是对对象的引用,对象本身还是存在于 JVM 堆内存中. 存放的是对象,即引用数据类型,对于基本数据类型 ...

  8. Linux下安装vim-plug报错:Failed to connect to raw.githubusercontent.com port 443: Connection refused

    安装vim-plug时,输入以下命令: curl -fLo ~/.vim/autoload/plug.vim --create-dirs https://raw.githubusercontent.c ...

  9. PYTHON-函数调用函数名

    def fun1(x): print('......................') x() def fun2(): print('**********************') fun1(fu ...

  10. 使用turtle库画国际象棋棋盘

    import turtle n = 60 # 每行间隔,小格子边长 x = -300 # x初始值 y = -300 # x初始值 def main(): turtle.speed(11) turtl ...