【Netty】服务端启动Netty是都做了哪些事情?(三):注册selector

【Netty】服务端启动Netty是都做了哪些事情?(三):注册selector

Scroll Down

回顾

Netty version:4.1.6

前两节的代码都是集中在initAndRegister()函数中的这节也不例外,只是我们继续往下看罢了。

Selector是什么

在了解Netty注册selector之前,我们得要先知道Selector是个啥,关于这点,我们不妨看看java 8文档中的selector,从官方的一些简介和它的函数我们不难发现,selector其实就是一个负责管理Channel的东西,网上也有人称它为多路复用器

它还可以检查一个或多个Channel(通道)的状态是否处于可读、可写。

为了更好的理解selector是个什么东西,我拿来之前的一张图:
selector.png


注册Selector大致流程

selector注册大致流程.png

开始追踪源码

让我们视角再次回到initAndRegister()函数中,如果忘记怎么进到这里面的,可以回到第一节中再看看。

现在我们先看看initAndRegister,在里面我们可以找到一个register的函数:

initAndRegister.png

上面的register函数最终会调用到

io.netty.channel.AbstractChannel.AbstractUnsafe#register,

由于这个调用过程比较繁琐,我这里只列出一些关键步骤,详情还请去源码打断点:

  • 进入io.netty.channel.MultithreadEventLoopGroup#register(io.netty.channel.Channel)。注意,这一步会遇到一个next方法,这个方法是分配NioEventLoop的,并且这个NioEventLoop是属于childGroup(workerGroup),如感兴趣,请看【创建选择器】
  • 进入io.netty.channel.SingleThreadEventLoop#register(io.netty.channel.Channel)
  • 进入io.netty.channel.SingleThreadEventLoop#register(io.netty.channel.ChannelPromise),在这里可以看到以下代码:
promise.channel().unsafe().register(this, promise);
  • 上面的register进去以后,就是:
    io.netty.channel.AbstractChannel.AbstractUnsafe#register
    register函数.png
  • 此处还注册了NioEventLoop

现在将视角切换到io.netty.channel.AbstractChannel.AbstractUnsafe#register,看看函数部分源码:

register函数.png

可以看到上图的源码中出现了流程图中的两个步骤:eventLoop和resgiter0。并且可以看到断点中eventLoop就是我们自定义的NioEventLoop(Group)。

至于eventLoop具体干什么,先在这留个印象,以后再继续补充。

纠正,这里的register0方法不一定是调用if代码块中的register0方法,因为上面也说了next方法负责分配NioEventLoop,是不是if代码块取决于next方法的分配,以前截图的时候截错了,后期博客可能对这一块还有补充。


之后,我们将视角转移到上图的register0函数中,就可以发现流程图中剩下的三个函数:
io.netty.channel.AbstractChannel.AbstractUnsafe#register0

register0函数.png

上图中还有个要注意的点,因为此时Netty还没绑定端口,所以此时isActive()还是false,而pipeline.fireChannelActive();没有被执行,也即ChannelInboundHandlerAdapter的channelActive函数其实还没被调用。

关于channelActive是哪里执行的,这里就需要补上前面章节没有补上的ServerHandler了:
ServerHander.java

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.concurrent.TimeUnit;

public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("channelActive");
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) {
        System.out.println("channelRegistered");
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        System.out.println("handlerAdded");
    }

    @Override
    public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
        super.channelRead(ctx, msg);

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 耗时的操作
                String result = loadFromDB();

                ctx.channel().writeAndFlush(result);
                ctx.executor().schedule(new Runnable() {
                    @Override
                    public void run() {
                        // ...do something
                    }
                }, 1, TimeUnit.SECONDS);

            }
        }).start();
    }

    private String loadFromDB() {
        return "hello world!";
    }
}

然后执行Server.java(代码上一节有,这里就不重复贴了),执行结果如下:

Server.java执行结果.png

上上面说的channelActive,其实就是指上面ServerHandler.java的channelActive函数,而其中handlerAdded和channelRegistered事件对应的就是invokeHandlerAddedIfNeeded()和fireChannelRegistered()。




好了,现在将视角重新转回:io.netty.channel.AbstractChannel.AbstractUnsafe#register0

这里面有三个流程图中的函数:

  • doRegister();
  • invokeHandlerAddedIfNeeded();
  • fireChannelRegistered();

doRegister

我们进到doRegister函数源码:
io.netty.channel.nio.AbstractNioChannel#doRegister

    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().selector, 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    throw e;
                }
            }
        }
    }

可以发现这里底层是用jdk的api注册并绑定selector的,参数ops=0表示现在不关注任何事件(绑定端口后会关注accept事件)。

javaChannel().register(eventLoop().selector, 0, this);中的this,最终会被包装成一个叫Attachment的东西,以后可能用到,就在这里记一记,看到Attachment可以当做是Channel。

如果我们再点进上面的javaChannel()函数,可以看见如下代码:
io.netty.channel.nio.AbstractNioChannel#javaChannel

    protected SelectableChannel javaChannel() {
        return ch;
    }

上面的ch类型为SelectableChannel,其实就是在【创建服务端Channel】那一节创建的,具体从下面这段代码开始:
io.netty.channel.socket.nio.NioServerSocketChannel#NioServerSocketChannel(java.nio.channels.ServerSocketChannel)

new NioServerSocketChannelConfig(this, javaChannel().socket());

最终到AbstractNioChannel中的this.ch=ch;

至于流程图中最后两个函数:invokeHandlerAddedIfNeeded和fireChannelRegistered这里就先不解析源码了,目前只需要知道它们操作最终体现就是上面Server.java执行结果中的前两行,或者先按照函数名或源码注释理解它们大概做了什么也行。