# 摘要

在前两篇《快速理解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. python机器学习(五)回归算法-线性回归

      一.线性回归的概念 1.1.定义 线性回归通过一个或者多个自变量与因变量之间之间进行建模的回归分析.其中特点为一个或多个称为回归系数的模型参数的线性组合. 优点:结果易于理解,计算不复杂. 缺点: ...

  2. flask之路由route

    ''' app.py中的源码def route(self, rule, **options) @app.route()路由参数使用: 1.第一个位置参数为路由分发的请求路径 ①静态参数路由:/inde ...

  3. nginx配置之站点服务请求功能配置

    站点服务请求功能配置:html/ nginx.conf中的http{}中的server{}: server { listen 85; server_name localhost; #charset k ...

  4. 阿里云服务器 ECS Ubuntu系统下PHP,MYSQL,APACHE2的安装配置

    1.系统更新,必须更新,否则有些软件会找不到. apt-get update apt-get upgrade 2.安装mysql sudo apt-get install mysql-server 3 ...

  5. MySQL(3)— 数据管理

    三.MySQL数据管理(DML) 3-1.外键(了解即可) ALTER TABLE `aa表名` ADD CONSTRAINT `约束名` FOREIGN KEY (字段名) REFERENCES ` ...

  6. PAT-1135 Is It A Red-Black Tree(二叉查找树的创建和遍历)

    There is a kind of balanced binary search tree named red-black tree in the data structure. It has th ...

  7. BZOJ1082 二分搜索

    1082: [SCOI2005]栅栏 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2247  Solved: 952[Submit][Status] ...

  8. 【MySQL】MySQL5.7等以上版本在Windows上的配置

    由于本人是win10系统,所以说下win10系统以管理员身份打开cmd 1. 配置环境变量 我这边是安装在了C:\Program Files\MySQL\MySQL Server 5.7在path中加 ...

  9. wordpress获取文章特色图像路径函数wp_get_attachment_image_src()

    特色图像是wordpress主要的文章缩略图功能,几乎全部wordpress模板都使用或支持特色图像.今天介绍的wp_get_attachment_image_src()函数就是获取文章特色图像路径的 ...

  10. [Objective-C] 021 KVC、KVO

    写过C#的都知道C#通过反射读写一个对象的属性特别方便,可以利用字符串的方式去动态控制一个对象.其实在ObjC中,我们可以更高级点,根本不必进行任何操作就可以进行属性的动态读写,这种方式就是Key V ...