美文网首页
Java NIO(四)Netty核心组件及一次事件的处理流程

Java NIO(四)Netty核心组件及一次事件的处理流程

作者: 清雨季 | 来源:发表于2019-07-23 10:10 被阅读0次

一 整个调用流程图

先放一张调用的流程图,从最下面开始往上调用。

image.png

除了最下面的Channel之外,关键的类就是EventLoop,Unsafe,ChannelPipeline,ChannelHandler。下面分别解析一下这四层。

二 EventLoop

从它的名字大概可以看出它是干嘛的:事件循环。实际上,它主要实现了启动新线程不断从Selector中获取可操作的Channel对象的流程。

EventLoop内部有一个Selector,在服务器启动时,会把ServerSocketChannel注册到这个Selector中,之后客户端连接后,会把客户端对应的SocketChannel也注册到Selector中。

EventLoop还会启动一个线程,不断应用Selector.select()方法获取已经准备就绪的Channel,然后根据就绪事件类型交给Unsafe层的不同方法处理

EventLoop是一个接口,具体使用的类是NioEventLoop这个类,它的核心字段如下:

    Selector selector;
    private final Executor executor;
    private final Runnable asRunnable = new Runnable() {
            try {
                SingleThreadEventExecutor.this.run();
            } catch (Throwable t) {
            }
        }
    };
  • selector就是实例化的Selector对象,所有的Channel包括ServerSocketChannel都会注册到这个selector上。
  • executor是一个线程池,这个线程池会不断的执行asRunnable对象,这个Runable的run方法只是简单的调用了SingleThreadEventExecutor.this.run();

在SingleThreadEventExecutor.this.run();方法中,会不断的从Selector对象中获取可操作的Channel对象,然后交给Unsafe处理(后面会说这个类),代码如下:

    protected void run() {
        processSelectedKeys();
    }
    private void processSelectedKeys() {
        if (selectedKeys != null) {
            processSelectedKeysOptimized(selectedKeys.flip());
        } else {
            processSelectedKeysPlain(selector.selectedKeys());
        }
    }
    private void processSelectedKeysOptimized(SelectionKey[] selectedKeys) {
        for (int i = 0;; i ++) {
            final SelectionKey k = selectedKeys[i];
            if (a instanceof AbstractNioChannel) {
                processSelectedKey(k, (AbstractNioChannel) a);
            } else {
                NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
                processSelectedKey(k, task);
            }
    }

经过一系列简单的调用,最终使用的是processSelectedKey方法来处理可操作的key的,而这个processSelectedKey方法实际上会调用unsafe来处理。


从图中的代码,可以看出具体的逻辑:

  • 如果是Channel就绪的是READ或者ACCEPT事件(或者说状态),则调用Unsafe的read()方法
  • 如果就绪的WRITE事件,则调用Unsafe的forceFlush方法
  • 如果就绪的是CONNECT,则调用Unsafe的finishConnect方法

注:实际上,EventLoop不会单独使用,而是会放在EventLoopGroup中使用,这里先不说,后续再详细解释。

三 Unsafe

这个Unsafe与java api中的那个Unsafe不是同一个东西,这个Unsafe是Netty中用于读取数据的,它与Channel是一一对应的,每个Channel中都会有一个Unsafe类型的字段。

Unsafe是一个接口,具体实现类会定义在Channel的内部,如AbstractNioMessageChannel中有一个Unsafe的实现类NioMessageUnsafe

EventLoop在select出Channel后,会根据不同的事件调用Unsafe不同的方法处理。不同的Channel,使用的Unsafe也不同,但是逻辑基本是一致的:

  • 处理好当前的就绪事件,例如如果是ACCEPT事件,会把连接建立好,如果是READ事件,会把数据读到缓冲区里。
  • 调用ChannelPipeline,触发各种事件。

以NioMessageUnsafe的read方法为例,我们看看他具体做了什么:


  • 首先是第一个红框,调用Channel的doReadMessages方法读取数据(Unsage是Channel的内部类,所以实际上这里调用到的是Channel中的方法),然后把数据保存在readBuf中,readBuf的类型如下:
private final List<Object> readBuf = new ArrayList<Object>();
  • 然后是第二个红框,针对读取到的每一条消息,都调用pipeline .fireChannelReadComplete方法处理,这里就是在触发ChannelPipeline的事件了。
  • 第三个红框,所有的消息都处理完后调用pipeline.fireChannelReadComplete()方法处理
  • 第四个红框,如果出现了异常,则调用pipeline.fireExceptionCaught()方法处理

所以实际上Unsafe就做了两件事:调用Channel中的特定方法处理ACCEPT,READ,WRITE,CONNECT事件,并且在特定时刻然后调用ChannelPipeline的特定事件。

不同的Unsafe有不同的逻辑,这篇文章只是讲一下整体的流程,后续再说每个Unsafe具体的逻辑。

四 ChannelPipeline和ChannelHandler

ChannelPipeline是netty的一层抽象,无论是向Channel中写数据还是从Channel中读数据,都要经过ChannelPipeine的处理。

ChannelPipeline使用了类似于Servlet中Filter的方式,在它内部有一个ChannelHandler类型的双向链表。

ChannelHandler是用于具体处理数据用的,这个就是我们可以自己定义的处理数据的对象,可以说,使用Netty的时候,使用者就是通过ChannelHandler来操作数据的。

当Unsafe从Channel读取到数据后,会交给ChannlPipeline处理,ChannelPipeline会从队头节点开始,今次调用所有ChannelHandler处理数据,任何一个ChannelHandler都可以中断当前的流程。

当我们向ChannelPipeline写数据后,ChannelPipeline会从队尾开始,依次调用所有的ChannelHandler处理数据,同样的,任何ChannelHandler也可以中断流程,如果数据经过所有的ChannelHandler处理完毕,就会Channel.write()写出去。


ChannelPipeline工作流程-图片来源于《Netty权威指南》

ChannelPipeline中的方法与ChannelHandler中的方法是一一对应的,例如Unsafe处理READ事件时会调用ChannelPipeline中的fireChannelRead方法,而在fireChannelRead方法中会依次调用各个ChannelHandler中的channelRead方法。

接下来再看看ChannelHandler中有哪些方法,以及它们具体会在什么时候被调用。

4.1 ChannelHandler中读相关的方法

ACCEPT和READ相关的事件被称为inbound事件

  • channelRegistered(): Channel注册事件
    ...待补充

五 Bootstrap

实际上,有了上面的几个类,我们就已经可以自己写一个服务器了,但是还比较麻烦,Bootstrap这一层帮我们封装了一些比较麻烦的细节,调用上面的四个类来组装一个服务器,可以说是Netty的facade了

相关文章

网友评论

      本文标题:Java NIO(四)Netty核心组件及一次事件的处理流程

      本文链接:https://www.haomeiwen.com/subject/vfrmlctx.html