Netty学习(9):结合NIO以及Netty分析Reactor模式5大角色的调用关系

概述

本篇文章承接Netty学习(7):详解《Scalable IO in Java》,认识Reactor模式

继续解读Reactor模式,因为Reactor模式对于NIO和Netty的学习太重要了

相关文档:腾讯文档】Reactor经典开创论文【腾讯文档】Scalable_IO_in_Java

本篇文章一定要结合这两个文档阅读,不要害怕读英文!!!

Reactor模式的角色构成

我们将从以上这个图来分析Reactor模式的角色构成(所有关于Reactor模式的文章,这张图一定会存在!)

Handle(句柄或者是描述符)

在Windows下称为句柄,在Linux下称为描述符。本质上表示的是一种资源,是由操作系统来提供的,该资源用于表示一个个的事件,比如:文件描述符、针对于网络编程种的Socket描述符,事件既可以来自于外部,也可以来自于内部,外部事件比如说客户端的连接请求,客户端发送过来的数据等,内部事件比如说操作系统产生的定时事件等。它本质上就是一个文件描述符,是事件的发生地(类比于网络编程中,客户端向服务端发送一个连接请求,这个连接的事件就是由Handle来产生的,客户端向服务端发送数据,Handle将产生一个Read事件)。

Synchronous Event Demultiplexer(同步事件分离器)

它本身是一个系统调用,用于等待事件的发生(事件可能是一个,也可能是多个),调用方在调用它的时候会被阻塞,一直阻塞到同步事件分离器上有事件产生为止。对于Linux来说,同步事件分离器指的就是常用的I/O多路复用机制,比如说select、poll、epoll等。

基于图中的Synchonous Event Demultiplexer中的selector(),很明显可以对应上Java NIO中的Selector,对应的阻塞方法就是select()方法。select()方法在不同的系统中对应的底层系统调用是不一样的(windows和linux),是一个操作系统级别的调用。

Event Handler(事件处理器)

本身由多个回调方法构成,这些回调方法构成了与应用相关的对于某个事件的反馈机制。在Java NIO领域中并没有提供事件处理器机制让我们调用或去进行回调(NIO中没有提供与Event Handler相对应的角色),是由我们自己编写代码完成的。比如说我们在Netty中编写的自定义处理器所继承的SimpleChannelInboundHandler,这些Handler有大量的回调方法,当channel处于不同的状态时,会有不同的事件被回调。

Netty相比于Java NIO来说,在事件处理器这个角色上进行了一个升级,它为我们开发者提供了大量的回调方法,供我们在特定事件产生时实现相应的回调方法进行业务逻辑的处理

Concrete Event Handler(具体事件处理器)

是Event Handler的具体实现,Event Handler显然是由框架或者库来提供的,而Concrete Event Handler是由开发者来编写的具体实现,从而实现了特定于业务的逻辑。在Netty当中举例:相当于我们编写的SimpleChannelInboundHandler的子类,重写了其回调方法。

Initiation Dispatcher(初始分发器)

其实Initiation Dispatcher实际上就是该图中的Reactor的角色。

它本身定义了一些规范,这些规范用于控制事件的调度方式,同时又提供了应用进行事件处理器的注册、删除等设施。

它本身是整个事件处理器的核心所在,Initiation Dispatcher会通过Synchronous Event Demultiplexer来等待事件的发生。一旦事件发生,Initiation Dispatcher首先会分离出每一个事件,然后调用事件处理器,最后调用相关的回调方法来处理这些事件,说人话:Initiation Dispatcher会使用Synchronous Event Demultiplexer中select()的结果,即使用各个事件(NIO中SelectionKey),Initiation Dispatcher拿到SelectionKey返回的集合,遍历可得出每一个元素(每一个SelectionKey)。同时提供了一些方法:register(h)、remove(h)......这些h就是Handle,通过Handle来注册和删除我们在对应的SelectionKey上提供的一个个处理器(Concrete Event Handler)

Netty中ChannelHandler里的一个个回调方法都是由bossGroup或workGroup中的某个EventLoop来调用的,workGroup对应是subReactor,subReactor和mainReactor本质上就是Initiation Dispatcher,我们编写的处理器中的channelRead0()实际上就是被subReactor调用的

调用关系

  • 当一个事件发生时,监听该事件的处理器(Event Handler)就会被调用,因为该处理器之前已经注册在了Initiation Dispatcher中,Event Handler.handle_event(type)中的type,其实就是对应的事件类型,同时可以通过get_handle()获取到对应的handle。
  • 当一个事件(Handle)发生时,由Synchronous Event Demultiplexer(同步事件分离器)检测到,并返回了事件对应的SelectionKey给到Initiation Dispatcher(初始分发器),Initiation Dispatcher通过不同的事件来调用不同的Event Handler(事件处理器),为什么Initiation Dispatcher可以直到不同的事件要调用哪个Event Handler(事件处理器)呢,Event Handler一定和Handle存在绑定关系——Event Handler拥有Handle。
  • 在服务器启动之前,即Initiation Dispatcher(初始分发器)线程循环开始之前,就会将若干Event Handler(事件处理器)注册到Initiation Dispatcher(初始分发器)中,然后线程循环开始不断执行。当事件Synchronous Event Demultiplexer(同步事件分离器)产生事件之后给到Initiation Dispatcher(初始分发器)之后,Initiation Dispatcher(初始分发器)遍历出跟当前时间相关的Event Handler(事件处理器)

这时候将调用事件对应的处理器,图中的handle_event(type)方法其实就是Event Handler(事件处理器)中的handle_event(type)方法。

  • 通过以上的分析,我们就可以解释我们在Netty中Handler的channelRead0方法的回调逻辑了,channelRead0实际是被workerGroup(对应Initiation Dispatcher)调用,我们编写的各种处理器添加到ChannelPipeline当中,实际上就是Event Handler(事件处理器)注册到Initiation Dispatcher(初始分发器)中,在这之后事件循环就开始执行了(EventLoop,即wokerGroup开始执行事件循环),并开始根据不同的事件来回调不同的事件处理器中的回调方法。

事件发生调用流程

  1. 首先执行Initiation Dispatcher(初始分发器),即Reactor角色,会将若干Concrete Event Handler(具体事件处理器)注册到其上,注册的同时指定处理器“感兴趣的事件”,这个“感兴趣的事件”是通过Handle来标识的,相当于NIO ServerSocketChannel.register(Selector sel, int ops),ops就是“感兴趣的事件”,随后开启事件循环。
  2. 当事件循环开始之后,Initiation Dispatcher(初始分发器)通过Synchronous Event Demultiplexer(同步事件分离器)来监听时间的发生。
  3. 当“感兴趣的事件”在Event Handler所关联的Handle上发生时,Synchronous Event Demultiplexer(同步事件分离器)获取到产生事件的集合给到Initiation Dispatcher(初始分发器)
  4. 由Initiation Dispatcher(初始分发器)遍历与接收到事件相关的处理器集合,根据事件的类型调用注册到其上的对应的Concrete Event Handler(具体事件处理器)的handle_event()方法来处理事件。

Reactor模式的流程

由事件发生调用流程我们分析可以得出Reactor模式的调用详细流程如下:

  1. 初始化Initiation Dispatcher,然后将若干个Concrete Event Handler注册到Initiation Dispatcher中。当应用向Initiation Dispatcher注册Concrete Event Handler时,会在注册的同时指定感兴趣的事件,即应用会标识出该事件处理器希望Initiation Dispatcher在某些事件发生时向其发出通知,事件通过Handle来标识,而Concrete Event Handler又持有该Handle。这样,事件 ————> Handle ————> Concrete Event Handler 就关联起来了。
  2. Initiation Dispatcher 会要求每个事件处理器向其传递内部的Handle。该Handle向操作系统标识了事件处理器。
  3. 当所有的Concrete Event Handler都注册完毕后,应用会调用handle_events方法来启动Initiation Dispatcher的事件循环。这是,Initiation Dispatcher会将每个注册的Concrete Event Handler的Handle合并起来,并使用Synchronous Event Demultiplexer(同步事件分离器)同步阻塞的等待事件的发生。比如说,TCP协议层会使用select同步事件分离器操作来等待客户端发送的数据到达连接的socket handler上。比如,在Java中通过Selector的select()方法来实现这个同步阻塞等待事件发生的操作。在Linux操作系统下,select()的实现中 a)会将已经注册到Initiation Dispatcher的事件调用epollCtl(epfd, opcode, fd, events)注册到linux系统中,这里fd表示Handle,events表示我们所感兴趣的Handle的事件;b)通过调用epollWait方法同步阻塞的等待已经注册的事件的发生。不同事件源上的事件可能同时发生,一旦有事件被触发了,epollWait方法就会返回;c)最后通过发生的事件找到相关联的SelectorKeyImpl对象,并设置其发生的事件为就绪状态,然后将SelectorKeyImpl放入selectedSet中。这样一来我们就可以通过Selector.selectedKeys()方法得到事件就绪的SelectorKeyImpl集合了。
  4. 当与某个事件源对应的Handle变为ready状态时(比如说,TCP socket变为等待读状态时),Synchronous Event Demultiplexer就会通知Initiation Dispatcher。
  5. Initiation Dispatcher会触发事件处理器的回调方法,从而响应这个处于ready状态的Handle。当事件发生时,Initiation Dispatcher会将被事件源激活的Handle作为『key』来寻找并分发恰当的事件处理器回调方法。
  6. Initiation Dispatcher会回调事件处理器的handle_event(type)回调方法来执行特定于应用的功能(开发者自己所编写的功能),从而相应这个事件。所发生的事件类型可以作为该方法参数并被该方法内部使用来执行额外的特定于服务的分离与分发。

猜你喜欢

转载自blog.csdn.net/qq_25805331/article/details/109273982