一,reactor模式介绍
假如你现在还在为自己的技术担忧,假如你现在想提升自己的工资,假如你想在职场上获得更多的话语权,假如你想顺利的度过35岁这个魔咒,假如你想体验BAT的工作环境,那么现在请我们一起开启提升技术之旅吧,详情请点击http://106.12.206.16:8080/qingruihappy/index.html
Reactor模式经常被翻译为“反应器”模式,但我认为这个翻译并不夠直观。说白了,所谓react就是对外界事件的一种被动的处理。比如在零下的室外环境中,我们会情不自禁的起鸡皮疙瘩,这就是我们对“感到寒冷”这个事件的反应;再比如我们因中了六合彩而高兴的手舞足蹈,这就是对“中六合彩”这个事件的反应;对于一个软件而言,我们点击了界面上的某个按钮时会弹出对话框,弹出对话框就是对“点击按钮”事件的反应。上述几种情形中都不是主动的发出动作,而是由于事件的到来而触发了反应的产生。软件在很多时候都是对现实生活中各种现象建模再通过计算机完成求解的过程,而Reactor模式就是针对上述这些现象而提出的。
回到网络编程,我们处理的大部分问题其实就是各种各样的事件。服务器接收到客户端的连接、读数据、写数据、接收到错误,这些都是事件。一般的处理是一种主动调用函数的方式,比如最简单的TCP服务器模型:
1 socket(...); 2 bind(...); 3 listen(...); 4 for ( ; ; ) { 5 accept(...); 6 if ((childpid = fork()) == 0) { 7 .... 8 } 9 }
Reactor模式的结构
Reactor模式包括四个部分的组件[1]:
Initialization Dispatcher
Synchronous Event Demultiplexer
Handles
Event Handler
其中的Handles用于标识通过操作系统管理的各种资源,包括套接字、打开文件、锁、定时器等,在Unix系统中是文件描述符,在Windows系统中是句柄。Initialization Dispatcher实现了事件的注册、删除、分发的接口,是用于驱动的主模块。Synchronous Event Demultiplexer是它的一个组件,用于等待新事件的发生,其实这就是解复用的系统,包括我们熟知的select、poll、epoll等。当有事件在Handles上发生时,我们可以通过Synchronous Event Demultiplexer得知。此时Synchronous Event Demultiplexer通知Initialization Dispatcher,Dispatcher再根据事件的类型使用相应的Event Handler完成事件的处理。所以Event Handler是最终用于实际处理事件的组件,一般来说会根据事件类型的不同实现各种类型的钩子函数。下图就是Reactor模式的结构:
具体的处理过程如下:
一个应用将某个Event Handler注册到Initialization Dispatcher上,并且指定其感兴趣的事件,希望当有感兴趣的事件在关联的handle上发生时去通知这个Event Handler。
当Event Handler注册完毕后,Initialization Dispatcher在一个事件循环中通过Synchronous Event Demultiplexer等待事件的发生。比如一个监听套接字等待accept一个新的客户端连接。
当与Handle关联的资源就绪时(比如当一个TCP套接字可读),Synchronous Event Demultiplexer会通知Initialization Dispatcher有事件发生。
此时Initialization Dispatcher找到和这个Handle关联的Event Handler,根据事件的类型调用Event Handler相应的钩子函数完成事件处理。
Reactor模型
- AWT Events
Reactor模型和AWT事件模型很像,就是将消息放到了一个队列中,通过异步线程池对其进行消费!
Reactor中的组件
- Reactor:Reactor是IO事件的派发者。
- Acceptor:Acceptor接受client连接,建立对应client的Handler,并向Reactor注册此Handler。
- Handler:和一个client通讯的实体,按这样的过程实现业务的处理。一般在基本的Handler基础上还会有更进一步的层次划分, 用来抽象诸如decode,process和encoder这些过程。比如对Web Server而言,decode通常是HTTP请求的解析, process的过程会进一步涉及到Listener和Servlet的调用。业务逻辑的处理在Reactor模式里被分散的IO事件所打破, 所以Handler需要有适当的机制在所需的信息还不全(读到一半)的时候保存上下文,并在下一次IO事件到来的时候(另一半可读了)能继续中断的处理。为了简化设计,Handler通常被设计成状态机,按GoF的state pattern来实现。
对应上面的NIO代码来看:
- Reactor:相当于有分发功能的Selector
- Acceptor:NIO中建立连接的那个判断分支
- Handler:消息读写处理等操作类
Reactor从线程池和Reactor的选择上可以细分为如下几种:
Reactor单线程模型
这个模型和上面的NIO流程很类似,只是将消息相关处理独立到了Handler中去了!
虽然上面说到NIO一个线程就可以支持所有的IO处理。但是瓶颈也是显而易见的!我们看一个客户端的情况,如果这个客户端多次进行请求,如果在Handler中的处理速度较慢,那么后续的客户端请求都会被积压,导致响应变慢!所以引入了Reactor多线程模型!
Reactor多线程模型
Reactor多线程模型就是将Handler中的IO操作和非IO操作分开,操作IO的线程称为IO线程,非IO操作的线程称为工作线程!这样的话,客户端的请求会直接被丢到线程池中,客户端发送请求就不会堵塞!
但是当用户进一步增加的时候,Reactor会出现瓶颈!因为Reactor既要处理IO操作请求,又要响应连接请求!为了分担Reactor的负担,所以引入了主从Reactor模型!
主从Reactor模型
主Reactor用于响应连接请求,从Reactor用于处理IO操作请求!
Netty
Netty是一个高性能NIO框架,其是对Reactor模型的一个实现!
- Netty客户端代码
1 EventLoopGroup workerGroup = new NioEventLoopGroup(); 2 try { 3 Bootstrap b = new Bootstrap(); 4 b.group(workerGroup); 5 b.channel(NioSocketChannel.class); 6 b.option(ChannelOption.SO_KEEPALIVE, true); 7 b.handler(new ChannelInitializer<SocketChannel>() { 8 @Override 9 public void initChannel(SocketChannel ch) throws Exception { 10 ch.pipeline().addLast(new TimeClientHandler()); 11 } 12 }); 13 14 ChannelFuture f = b.connect(host, port).sync(); 15 16 f.channel().closeFuture().sync(); 17 } finally { 18 workerGroup.shutdownGracefully(); 19 }
- Netty Client Handler
-
1 public class TimeClientHandler extends ChannelInboundHandlerAdapter { 2 @Override 3 public void channelRead(ChannelHandlerContext ctx, Object msg) { 4 ByteBuf m = (ByteBuf) msg; 5 try { 6 long currentTimeMillis = 7 (m.readUnsignedInt() - 2208988800L) * 1000L; 8 System.out.println(new Date(currentTimeMillis)); 9 ctx.close(); 10 } finally { 11 m.release(); 12 } 13 } 14 15 @Override 16 public void exceptionCaught(ChannelHandlerContext ctx, 17 Throwable cause) { 18 cause.printStackTrace(); 19 ctx.close(); 20 } 21 }
- Netty服务端代码
-
1 EventLoopGroup bossGroup = new NioEventLoopGroup(); 2 EventLoopGroup workerGroup = new NioEventLoopGroup(); 3 try { 4 ServerBootstrap b = new ServerBootstrap(); 5 b.group(bossGroup, workerGroup) 6 .channel(NioServerSocketChannel.class) 7 .childHandler(new ChannelInitializer<SocketChannel>() { 8 @Override 9 public void initChannel(SocketChannel ch) throws Exception { 10 ch.pipeline().addLast(new TimeServerHandler()); 11 } 12 }) 13 .option(ChannelOption.SO_BACKLOG, 128) 14 .childOption(ChannelOption.SO_KEEPALIVE, true); 15 // Bind and start to accept incoming connections. 16 ChannelFuture f = b.bind(port).sync(); 17 f.channel().closeFuture().sync(); 18 } finally { 19 workerGroup.shutdownGracefully(); 20 bossGroup.shutdownGracefully(); 21 }
- Netty Server Handler
-
1 public class TimeServerHandler extends ChannelInboundHandlerAdapter { 2 3 @Override 4 public void channelActive(final ChannelHandlerContext ctx) { 5 final ByteBuf time = ctx.alloc().buffer(4); 6 time.writeInt((int) 7 (System.currentTimeMillis() / 1000L + 2208988800L)); 8 9 final ChannelFuture f = ctx.writeAndFlush(time); 10 f.addListener(new ChannelFutureListener() { 11 @Override 12 public void operationComplete(ChannelFuture future) { 13 assert f == future; 14 ctx.close(); 15 } 16 }); 17 } 18 19 @Override 20 public void exceptionCaught(ChannelHandlerContext ctx, 21 Throwable cause) { 22 cause.printStackTrace(); 23 ctx.close(); 24 } 25
我们从Netty服务器代码来看,与Reactor模型进行对应!
EventLoopGroup就相当于是Reactor,bossGroup对应主Reactor,workerGroup对应从Reactor
TimeServerHandler就是Handler
child开头的方法配置的是客户端channel,非child开头的方法配置的是服务端channe
二:Reactor,反应堆还是核电站?
1、Reactor的由来
Reactor是一种广泛应用在服务器端开发的设计模式。Reactor中文大多译为“反应堆”,我当初接触这个概念的时候,就感觉很厉害,是不是它的原理就跟“核反应”差不多?后来才知道其实没有什么关系,从Reactor的兄弟“Proactor”(多译为前摄器)就能看得出来,这两个词的中文翻译其实都不是太好,不够形象。实际上,Reactor模式又有别名“Dispatcher”或者“Notifier”,我觉得这两个都更加能表明它的本质。
那么,Reactor模式究竟是个什么东西呢?这要从事件驱动的开发方式说起。我们知道,对于应用服务器,一个主要规律就是,CPU的处理速度是要远远快于IO速度的,如果CPU为了IO操作(例如从Socket读取一段数据)而阻塞显然是不划算的。好一点的方法是分为多进程或者线程去进行处理,但是这样会带来一些进程切换的开销,试想一个进程一个数据读了500ms,期间进程切换到它3次,但是CPU却什么都不能干,就这么切换走了,是不是也不划算?
这时先驱们找到了事件驱动,或者叫回调的方式,来完成这件事情。这种方式就是,应用业务向一个中间人注册一个回调(event handler),当IO就绪后,就这个中间人产生一个事件,并通知此handler进行处理。这种回调的方式,也体现了“好莱坞原则”(Hollywood principle)-“Don’t call us, we’ll call you”,在我们熟悉的IoC中也有用到。看来软件开发真是互通的!
好了,我们现在来看Reactor模式。在前面事件驱动的例子里有个问题:我们如何知道IO就绪这个事件,谁来充当这个中间人?Reactor模式的答案是:由一个不断等待和循环的单独进程(线程)来做这件事,它接受所有handler的注册,并负责先操作系统查询IO是否就绪,在就绪后就调用指定handler进行处理,这个角色的名字就叫做Reactor。
2、Reactor与NIO
Java中的NIO可以很好的和Reactor模式结合。关于NIO中的Reactor模式,我想没有什么资料能比Doug Lea大神(不知道Doug Lea?看看JDK集合包和并发包的作者吧)在《Scalable IO in Java》解释的更简洁和全面了。NIO中Reactor的核心是Selector
,我写了一个简单的Reactor示例,这里我贴一个核心的Reactor的循环(这种循环结构又叫做EventLoop
),剩余代码在这里。
1 public void run() { 2 try { 3 while (!Thread.interrupted()) { 4 selector.select(); 5 Set selected = selector.selectedKeys(); 6 Iterator it = selected.iterator(); 7 while (it.hasNext()) 8 dispatch((SelectionKey) (it.next())); 9 selected.clear(); 10 } 11 } catch (IOException ex) { /* ... */ 12 } 13 }
3、与Reactor相关的其他概念
前面提到了Proactor模式,这又是什么呢?简单来说,Reactor模式里,操作系统只负责通知IO就绪,具体的IO操作(例如读写)仍然是要在业务进程里阻塞的去做的,而Proactor模式则更进一步,由操作系统将IO操作执行好(例如读取,会将数据直接读到内存buffer中),而handler只负责处理自己的逻辑,真正做到了IO与程序处理异步执行。所以我们一般又说Reactor是同步IO,Proactor是异步IO。
关于阻塞和非阻塞、异步和非异步,以及UNIX底层的机制,大家可以看看这篇文章IO – 同步,异步,阻塞,非阻塞 (亡羊补牢篇),以及陶辉(《深入理解nginx》的作者)《高性能网络编程》的系列。
三:由Reactor出发来理解Netty
1、多线程下的Reactor
讲了一堆Reactor,我们回到Netty。在《Scalable IO in Java》中讲到了一种多线程下的Reactor模式。在这个模式里,mainReactor只有一个,负责响应client的连接请求,并建立连接,它使用一个NIO Selector;subReactor可以有一个或者多个,每个subReactor都会在一个独立线程中执行,并且维护一个独立的NIO Selector。
这样的好处很明显,因为subReactor也会执行一些比较耗时的IO操作,例如消息的读写,使用多个线程去执行,则更加有利于发挥CPU的运算能力,减少IO等待时间。
2、Netty中的Reactor与NIO
好了,了解了多线程下的Reactor模式,我们来看看Netty吧(以下部分主要针对NIO,OIO部分更加简单一点,不重复介绍了)。Netty里对应mainReactor的角色叫做“Boss”,而对应subReactor的角色叫做”Worker”。Boss负责分配请求,Worker负责执行,好像也很贴切!以TCP的Server端为例,这两个对应的实现类分别为NioServerBoss
和NioWorker
(Server和Client的Worker没有区别,因为建立连接之后,双方就是对等的进行传输了)。
Netty 3.7中Reactor的EventLoop在AbstractNioSelector.run()
中,它实现了Runnable
接口。这个类是Netty NIO部分的核心。它的逻辑非常复杂,其中还包括一些对JDK Bug的处理(例如rebuildSelector
),刚开始读的时候不需要深入那么细节。我精简了大部分代码,保留主干如下:
1 abstract class AbstractNioSelector implements NioSelector { 2 3 //NIO Selector 4 protected volatile Selector selector; 5 6 //内部任务队列 7 private final Queue taskQueue = new ConcurrentLinkedQueue(); 8 9 //selector循环 10 public void run() { 11 for (;;) { 12 try { 13 //处理内部任务队列 14 processTaskQueue(); 15 //处理selector事件对应逻辑 16 process(selector); 17 } catch (Throwable t) { 18 try { 19 Thread.sleep(1000); 20 } catch (InterruptedException e) { 21 // Ignore. 22 } 23 } 24 } 25 } 26 27 private void processTaskQueue() { 28 for (;;) { 29 final Runnable task = taskQueue.poll(); 30 if (task == null) { 31 break; 32 } 33 task.run(); 34 } 35 } 36 37 protected abstract void process(Selector selector) throws IOException;
其中process是主要的处理事件的逻辑,例如在AbstractNioWorker
中,处理逻辑如下:
1 protected void process(Selector selector) throws IOException { 2 Set selectedKeys = selector.selectedKeys(); 3 if (selectedKeys.isEmpty()) { 4 return; 5 } 6 for (Iterator i = selectedKeys.iterator(); i.hasNext();) { 7 SelectionKey k = i.next(); 8 i.remove(); 9 try { 10 int readyOps = k.readyOps(); 11 if ((readyOps & SelectionKey.OP_READ) != 0 || readyOps == 0) { 12 if (!read(k)) { 13 // Connection already closed - no need to handle write. 14 continue; 15 } 16 } 17 if ((readyOps & SelectionKey.OP_WRITE) != 0) { 18 writeFromSelectorLoop(k); 19 } 20 } catch (CancelledKeyException e) { 21 close(k); 22 } 23 24 if (cleanUpCancelledKeys()) { 25 break; // break the loop to avoid ConcurrentModificationException 26 } 27 } 28 }
这不就是第二部分提到的selector经典用法了么?
在4.0之后,作者觉得NioSelector
这个叫法,以及区分NioBoss
和NioWorker
的做法稍微繁琐了点,干脆就将这些合并成了NioEventLoop
,从此这两个角色就不做区分了。我倒是觉得新版本的会更优雅一点。
3、Netty中的多线程
下面我们来看Netty的多线程部分。一旦对应的Boss或者Worker启动,就会分配给它们一个线程去一直执行。对应的概念为BossPool
和WorkerPool
。对于每个NioServerSocketChannel
,Boss的Reactor有一个线程,而Worker的线程数由Worker线程池大小决定,但是默认最大不会超过CPU核数*2,当然,这个参数可以通过NioServerSocketChannelFactory
构造函数的参数来设置。
1 public NioServerSocketChannelFactory( 2 Executor bossExecutor, Executor workerExecutor, 3 int workerCount) { 4 this(bossExecutor, 1, workerExecutor, workerCount); 5 }
最后我们比较关心一个问题,我们之前ChannlePipeline
中的ChannleHandler是在哪个线程执行的呢?答案是在Worker线程里执行的,并且会阻塞Worker的EventLoop。例如,在NioWorker
中,读取消息完毕之后,会触发MessageReceived
事件,这会使得Pipeline中的handler都得到执行。
1 protected boolean read(SelectionKey k) { 2 .... 3 4 if (readBytes > 0) { 5 // Fire the event. 6 fireMessageReceived(channel, buffer); 7 } 8 9 return true; 10 }
自己的理解:
1,首先我们来看什么是reactor模式?
reactor是一种被动的反应模式,就是只有别的事件来触发它才会运行。是一种被动的机制。也是一种监听机制。有点类似监听器的意思。
2,reactor模式的四要素。
Initialization Dispatcher
Synchronous Event Demultiplexer
Handles
Event Handler
具体的处理过程如下:
一个应用将某个Event Handler注册到Initialization Dispatcher上,并且指定其感兴趣的事件,希望当有感兴趣的事件在关联的handle上发生时去通知这个Event Handler。
当Event Handler注册完毕后,Initialization Dispatcher在一个事件循环中通过Synchronous Event Demultiplexer等待事件的发生。比如一个监听套接字等待accept一个新的客户端连接。
当与Handle关联的资源就绪时(比如当一个TCP套接字可读),Synchronous Event Demultiplexer会通知Initialization Dispatcher有事件发生。
此时Initialization Dispatcher找到和这个Handle关联的Event Handler,根据事件的类型调用Event Handler相应的钩子函数完成事件处理。
Handle:即操作系统中的句柄,是对资源在操作系统层面上的一种抽象,它可以是打开的文件、一个连接(Socket)、Timer等。由于Reactor模式一般使用在网络编程中,因而这里一般指Socket Handle,即一个网络连接(Connection,在Java NIO中的Channel)。这个Channel注册到Synchronous Event Demultiplexer中,以监听Handle中发生的事件,对ServerSocketChannnel可以是CONNECT事件,对SocketChannel可以是READ、WRITE、CLOSE事件等。
Synchronous Event Demultiplexer:阻塞等待一系列的Handle中的事件到来,如果阻塞等待返回,即表示在返回的Handle中可以不阻塞的执行返回的事件类型。这个模块一般使用操作系统的select来实现。在Java NIO中用Selector来封装,当Selector.select()返回时,可以调用Selector的selectedKeys()方法获取Set<SelectionKey>,一个SelectionKey表达一个有事件发生的Channel以及该Channel上的事件类型。上图的“Synchronous Event Demultiplexer ---notifies--> Handle”的流程如果是对的,那内部实现应该是select()方法在事件到来后会先设置Handle的状态,然后返回。不了解内部实现机制,因而保留原图。
Initiation Dispatcher:用于管理Event Handler,即EventHandler的容器,用以注册、移除EventHandler等;另外,它还作为Reactor模式的入口调用Synchronous Event Demultiplexer的select方法以阻塞等待事件返回,当阻塞等待返回时,根据事件发生的Handle将其分发给对应的Event Handler处理,即回调EventHandler中的handle_event()方法。
Event Handler:定义事件处理方法:handle_event(),以供InitiationDispatcher回调使用。
Concrete Event Handler:事件EventHandler接口,实现特定事件处理逻辑。
Reactor模式模块之间的交互
简单描述一下Reactor各个模块之间的交互流程,先从序列图开始:
1. 初始化InitiationDispatcher,并初始化一个Handle到EventHandler的Map。
2. 注册EventHandler到InitiationDispatcher中,每个EventHandler包含对相应Handle的引用,从而建立Handle到EventHandler的映射(Map)。
3. 调用InitiationDispatcher的handle_events()方法以启动Event Loop。在Event Loop中,调用select()方法(Synchronous Event Demultiplexer)阻塞等待Event发生。
4. 当某个或某些Handle的Event发生后,select()方法返回,InitiationDispatcher根据返回的Handle找到注册的EventHandler,并回调该EventHandler的handle_events()方法。
5. 在EventHandler的handle_events()方法中还可以向InitiationDispatcher中注册新的Eventhandler,比如对AcceptorEventHandler来,当有新的client连接时,它会产生新的EventHandler以处理新的连接,并注册到InitiationDispatcher中。
下面我们来加上一段nio的代码来结合者nio理解这个模型
1 package bhz.nio; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.SocketChannel; 7 8 9 public class Client { 10 11 12 public static void main(String[] args) { 13 //1 描述套接字的ip地址 端口号 14 InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 8765); 15 //2 写入缓冲区 16 ByteBuffer writeBuf = ByteBuffer.allocate(1024); 17 //3 声明一下客户端通道 18 try { 19 //4 打开客户端通道 20 SocketChannel sc = SocketChannel.open(); 21 //5 与服务器地址进行连接 22 sc.connect(addr); 23 24 while(true){ 25 byte[] data = new byte[1024]; 26 System.in.read(data); 27 writeBuf.put(data); 28 29 writeBuf.flip(); 30 sc.write(writeBuf); 31 writeBuf.clear(); 32 } 33 34 } catch (IOException e) { 35 e.printStackTrace(); 36 } 37 38 } 39 }
1 package bhz.nio; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.SelectionKey; 7 import java.nio.channels.Selector; 8 import java.nio.channels.ServerSocketChannel; 9 import java.nio.channels.SocketChannel; 10 import java.util.Iterator; 11 12 public class Server implements Runnable { 13 14 //1 读取缓冲区 15 private ByteBuffer readBuf = ByteBuffer.allocate(1024); 16 //2 写入缓冲区 17 private ByteBuffer writeBuf = ByteBuffer.allocate(1024); 18 //2 多路复用器 19 private Selector selector; 20 21 public Server(int port) { 22 try { 23 //1 打开多路复用器 24 this.selector = Selector.open(); 25 //2 打开服务器端的通道 26 ServerSocketChannel ssc = ServerSocketChannel.open(); 27 //3 设置通道的阻塞模式 28 ssc.configureBlocking(false); 29 //4 绑定地址 30 ssc.bind(new InetSocketAddress(port)); 31 //5 把服务器通道注册到多路复用器上, 并且监听阻塞事件 32 ssc.register(this.selector, SelectionKey.OP_ACCEPT); 33 } catch (IOException e) { 34 e.printStackTrace(); 35 } 36 } 37 38 @Override 39 public void run() { 40 while(true){ 41 System.out.println("停止了吗"); 42 try { 43 //1 必须要让多路复用器开始监听 44 this.selector.select(); 45 //2 返回多路复用器里所有注册的通道Key 46 Iterator<SelectionKey> it = this.selector.selectedKeys().iterator(); 47 Thread.sleep(10000); 48 //3 遍历获取的key 49 while(it.hasNext()){ 50 //4 接受key值 51 SelectionKey key = it.next(); 52 //5 从容器中移除已经被选中的key 53 it.remove(); 54 //6 验证操作:判断key是否有效 55 if(key.isValid()){ 56 //SelectionKey.OP_CONNECT 57 //SelectionKey.OP_ACCEPT 58 //SelectionKey.OP_READ 59 //SelectionKey.OP_WRITE 60 61 //7 如果为阻塞状态: OP_ACCEPT 62 if(key.isAcceptable()){ 63 this.accept(key); 64 } 65 //8 如果为可读状态: OP_READ 66 if(key.isReadable()){ 67 this.read(key); 68 } 69 //9... 70 } 71 } 72 } catch (IOException | InterruptedException e) { 73 e.printStackTrace(); 74 } 75 } 76 } 77 78 /** 79 * 监听可读状态方法执行 80 * @param key 81 */ 82 private void read(SelectionKey key) { 83 try { 84 //1 对缓冲区进行清空 85 this.readBuf.clear(); 86 //2 获取之前注册的socketChannel通道对象 87 SocketChannel sc = (SocketChannel) key.channel(); 88 //3 从通道里获取数据 放入缓冲区 89 int index = sc.read(this.readBuf); 90 if(index == -1){ 91 key.channel().close(); 92 key.cancel(); 93 return; 94 } 95 //4 由于sc通道里的数据流入到readBuf容器中,所以 readBuf里面的position一定发生了变化,必须要进行复位 96 this.readBuf.flip(); 97 //读取readBuf数据 然后打印到控制台 98 byte[] bytes = new byte[this.readBuf.remaining()]; 99 this.readBuf.get(bytes); 100 101 String body = new String(bytes).trim(); 102 System.err.println("服务器接收数据: " + body); 103 104 //5 写出数据: 105 //... 106 107 108 } catch (IOException e) { 109 // TODO Auto-generated catch block 110 e.printStackTrace(); 111 } 112 113 } 114 115 /** 116 * 监听阻塞状态方法执行 117 * @param key 118 */ 119 private void accept(SelectionKey key) { 120 try { 121 //1 由于目前是server端,那么一定是server端启动 并且处于阻塞状态 所以获取阻塞状态为的key 一定是:ServerSocketChannel 122 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); 123 //2 通过调用 accept()方法, 返回一个具体的客户端连接句柄 124 SocketChannel sc = ssc.accept(); 125 //3 设置客户端通道为非阻塞 126 sc.configureBlocking(false); 127 //4 设置当前获取的客户端连接句柄为可读状态位 128 sc.register(this.selector, SelectionKey.OP_READ); 129 130 } catch (IOException e) { 131 // TODO Auto-generated catch block 132 e.printStackTrace(); 133 } 134 } 135 136 public static void main(String[] args) { 137 138 new Thread(new Server(8765)).start(); 139 140 } 141 142 }
自己的理解:
1,初始化的时候InitiationDispatcher注册了一组key是EventHandler,value值是Handle的map。
其中这个InitiationDispatcher相当于nio中的复用器selector,而Handle就是服务器的助手类。
2,调用Initiation Dispatcher时候会触发Synchronous Event Demultiplexer,Synchronous Event Demultiplexer就相当于nio代码中的Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();遍历channel中的key值。是出于一种阻塞的状态的。
3,当某个或某些Handle的Event发生后,就相当于nio代码中的key值是可读的状态就会回填到第二步当中去。这样就会触发去执行Handle里面同步阻塞的代码。
下面我们来理解一下代码
EventLoopGroup pGroup = new NioEventLoopGroup()
EventLoopGroup cGroup = new NioEventLoopGroup();
的含义。
可以通过
Reactor多线程模型
主从Reactor模型
图解来理解
//1 创建2个线程,一个是负责接收客户端的连接。一个是负责进行数据传输的
EventLoopGroup pGroup = new NioEventLoopGroup();//客户端请求的链接,单个的线程
EventLoopGroup cGroup = new NioEventLoopGroup();//从的,多个线程的,服务端传输数据局用的。
//返回数据就和这原理的核心意义不是特别大了。先不考虑
eventLoopGroup和EventLoop,netty服务端启动需要定义两个EventLoop。客户端只要一个,每一个EventLoop都是一个reactor线程组。服务端第一个EventLoop用于和客户端建立连接,另外一个用于处理IO操作或者执行系统任务和定时任务。
Netty用户接受客户端的线程池主要职责:
1:接受客户端的TCP连接,初始化channel参数
2:将链路状态变更事件通知给channelPipeLine
Netty处理IO的线程池主要职责:
1:异步读取通信对端的数据包,发送读取事件到channelPipeline
2:异步发送消息到通信对端,调用ChannelPipeline的消息发送接口
3:执行系统调用的任务(handler)
4:执行定时任务
1 package bhz.netty.start; 2 3 4 import io.netty.bootstrap.ServerBootstrap; 5 import io.netty.channel.ChannelFuture; 6 import io.netty.channel.ChannelInitializer; 7 import io.netty.channel.ChannelOption; 8 import io.netty.channel.EventLoopGroup; 9 import io.netty.channel.nio.NioEventLoopGroup; 10 import io.netty.channel.socket.SocketChannel; 11 import io.netty.channel.socket.nio.NioServerSocketChannel; 12 import io.netty.handler.timeout.ReadTimeoutHandler; 13 14 public class Server { 15 16 17 public static void main(String[] args) throws Exception { 18 //ONE: 19 //1 用于接受客户端连接的线程工作组 20 EventLoopGroup boss = new NioEventLoopGroup(); 21 //2 用于对接受客户端连接读写操作的线程工作组 22 EventLoopGroup work = new NioEventLoopGroup(); 23 24 //TWO: 25 //3 辅助类。用于帮助我们创建NETTY服务 26 ServerBootstrap b = new ServerBootstrap(); 27 b.group(boss, work) //绑定两个工作线程组 28 .channel(NioServerSocketChannel.class) //设置NIO的模式 29 .option(ChannelOption.SO_BACKLOG, 1024) //设置TCP缓冲区 30 //.option(ChannelOption.SO_SNDBUF, 32*1024) // 设置发送数据的缓存大小 31 .option(ChannelOption.SO_RCVBUF, 32*1024) // 设置接受数据的缓存大小 32 .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE) // 设置保持连接 33 .childOption(ChannelOption.SO_SNDBUF, 32*1024) 34 // 初始化绑定服务通道 35 .childHandler(new ChannelInitializer<SocketChannel>() { 36 @Override 37 protected void initChannel(SocketChannel sc) throws Exception { 38 // 为通道进行初始化: 数据传输过来的时候会进行拦截和执行 39 //sc.pipeline().addLast(new ReadTimeoutHandler(5)); 40 sc.pipeline().addLast(new ServerHandler()); 41 } 42 }); 43 44 ChannelFuture cf = b.bind(8765).sync(); 45 46 47 48 //释放连接 49 cf.channel().closeFuture().sync(); 50 work.shutdownGracefully(); 51 boss.shutdownGracefully(); 52 } 53 }
红色的由
boss 负责
蓝色的由
work 负责