剖析netty启动过程

依然范特西╮ 提交于 2020-01-10 05:23:32

 1、总体看一下源代码

启动类源码

// Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        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();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(new EchoServerHandler());
                 }
             });

            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

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();
    }
}

 

2、追踪两个线程组创建的过程

2.1看看追踪过程

最后追踪到MultithreadEventLoopGroup这个类,DEFAULT_EVENT_LOOP_THREADS在静态代码块中加载默认值是核数的两倍。

我们继续追踪,可以看到真正的构造器调用父类中的构造方法,下面会详细讲这个方法,顺便看看这些类的继承关系

2.2 详细查看真正创建线程组的方法在MultithreadEventExecutorGroup中的构造方法。

protected MultithreadEventExecutorGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, Object... args) {
        this.terminatedChildren = new AtomicInteger();
        this.terminationFuture = new DefaultPromise(GlobalEventExecutor.INSTANCE);
        if (nThreads <= 0) {
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        } else {
            if (executor == null) {
                executor = new ThreadPerTaskExecutor(this.newDefaultThreadFactory());
            }

            this.children = new EventExecutor[nThreads];

            int j;
            for(int i = 0; i < nThreads; ++i) {
                boolean success = false;
                boolean var18 = false;

                try {
                    var18 = true;
                    this.children[i] = this.newChild((Executor)executor, args);
                    success = true;
                    var18 = false;
                } catch (Exception var19) {
                    throw new IllegalStateException("failed to create a child event loop", var19);
                } finally {
                    if (var18) {
                        if (!success) {
                            int j;
                            for(j = 0; j < i; ++j) {
                                this.children[j].shutdownGracefully();
                            }

                            for(j = 0; j < i; ++j) {
                                EventExecutor e = this.children[j];

                                try {
                                    while(!e.isTerminated()) {
                                        e.awaitTermination(2147483647L, TimeUnit.SECONDS);
                                    }
                                } catch (InterruptedException var20) {
                                    Thread.currentThread().interrupt();
                                    break;
                                }
                            }
                        }

                    }
                }

                if (!success) {
                    for(j = 0; j < i; ++j) {
                        this.children[j].shutdownGracefully();
                    }

                    for(j = 0; j < i; ++j) {
                        EventExecutor e = this.children[j];

                        try {
                            while(!e.isTerminated()) {
                                e.awaitTermination(2147483647L, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException var22) {
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }

            this.chooser = chooserFactory.newChooser(this.children);
            FutureListener<Object> terminationListener = new FutureListener<Object>() {
                public void operationComplete(Future<Object> future) throws Exception {
                    if (MultithreadEventExecutorGroup.this.terminatedChildren.incrementAndGet() == MultithreadEventExecutorGroup.this.children.length) {
                        MultithreadEventExecutorGroup.this.terminationFuture.setSuccess((Object)null);
                    }

                }
            };
            EventExecutor[] var24 = this.children;
            j = var24.length;

            for(int var26 = 0; var26 < j; ++var26) {
                EventExecutor e = var24[var26];
                e.terminationFuture().addListener(terminationListener);
            }

            Set<EventExecutor> childrenSet = new LinkedHashSet(this.children.length);
            Collections.addAll(childrenSet, this.children);
            this.readonlyChildren = Collections.unmodifiableSet(childrenSet);
        }
    }

(1)首先会做个判断,要开辟的线程数若小于等于0则抛异常,若传入的线程执行器为null,则调用netty提供的线程工厂

(2)把利用this.children[i] = this.newChild((Executor)executor, args)创建NioEventLoop对象放在EventExecutor数组中,newChild()这个方法在MultithreadEventExecutorGroup类中没有实现,而在NioEventLoopGroup被实现了。

(3)根据我们默认DefaultEventExecutorChooserFactory选择器工厂,绑定NioEventLoop数组对象。在前面的构造方法中,我们指定了chooserFactory为DefaultEventExecutorChooserFactory,在此工厂内部,会根据children数组的长度来动态选择选择器对象,用于选择下一个可执行的EventExecutor,也就是NioEventLoop。

(4)通过e.terminationFuture().addListener(terminationListener)为每个NioEventLoop添加一个监听器

(5)通过Set<EventExecutor> childrenSet = new LinkedHashSet(this.children.length); Collections.addAll(childrenSet, this.children);把NioEventLoop对象放进set集合中。

3、追踪NioEventLoop的创建(此处参考Netty进阶:Netty核心NioEventLoop原理解析这位大神的思想)

先看看NioEventLoop类的继承关系

实现线程定时

在上一节中NioEventLoop对象主要通过 this.newChild((Executor)executor, args)方法来创建,其创建代码如下。

 protected EventLoop newChild(Executor executor, Object... args) throws Exception {
        return new NioEventLoop(this, executor, (SelectorProvider)args[0],                    ((SelectStrategyFactory)args[1]).newSelectStrategy(), (RejectedExecutionHandler)args[2]);
    }

继续追踪可以看到初始化方法,如下面的代码

 NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider, SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
        super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
        if (selectorProvider == null) {
            throw new NullPointerException("selectorProvider");
        } else if (strategy == null) {
            throw new NullPointerException("selectStrategy");
        } else {
            this.provider = selectorProvider;
            NioEventLoop.SelectorTuple selectorTuple = this.openSelector();
            this.selector = selectorTuple.selector;
            this.unwrappedSelector = selectorTuple.unwrappedSelector;
            this.selectStrategy = strategy;
        }
    }

(1)在初始化NioEventLoopGroup的时候就已经用到了SelectorProvider这个参数了,SelectorProvider是Java NIO中的抽象类,它的作用是调用Windows或者Linux底层NIO的实现,为JavaNIO提供服务,比如经常用的Selector.open()方法内部就是通过调用SelectorProvider.openSelector()来得到多路复用器selector。在这里赋值给NioEventLoop的provider属性。
(2) SelectorTuple是NioEventLoop的内部类,其实就是对Java NIO Selector的封装。

(3)selector和unwrappedSelector分别表示优化过的Selector和未优化过的Selector,selectedKeys表示优化过的SelectionKey。Netty在该类中对Java NIO的Selector做了优化,可以通过设置系统属性io.netty.noKeySetOptimization进行修改,设置为true、yes或者1关闭优化,设置为false、no或者0开启优化,默认开启优化,下面一小节介绍优化的细节。

然后继续追踪

executor即线程池,还记得上节讲的初始化NioEventLoopGroup么,在MultithreadEventExecutorGroup构造函数中执行executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()),一边保存在类属性中,一边传入了newChild方法中,最终也传入该构造函数。

并且在SingleThreadEventExecutor类中有一个属性private volatile Thread thread,它用来引用支撑该EventExecutor的线程,用来处理I/O事件和执行任务,叫支撑线程或者I/O线程均可,thread所引用的线程即来自executor。

这里也初始化了taskQueue,其中tailTasks和taskQueue均是任务队列,而优先级不同,taskQueue的优先级高于tailTasks和定时任务,定时任务优先级高于tailTasks。所谓的优先级就是线程执行任务的先后。只是tailTasksm目前在Netty中还没有用到。

newTaskQueuef方法被NioEventLoop重写,其实现是Mpsc队列(多个生产者单个消费者的意思),后面会单独的讲述它的原理,而在AbstractScheduledEventExecutor的scheduledTaskQueues是优先级队列。

 

3.1Netty 对Selecter的优化

在NioEventLoop实例化的过程中有提到,Netty对JDK Selector的优化,其实主要是对SelectKeys进行优化,JDK NIO中比如获取准备好的key通过如下代码:

 Set<SelectionKey> keys = selector.selectedKeys();

Netty用SelectedSelectionKeySet实现了AbstractSet,提供了和HashSet同样的方法,只是内部实现(HashSet内部是HashMap)用数组来实现。

  private NioEventLoop.SelectorTuple openSelector() {
        final AbstractSelector unwrappedSelector;
        try {
            unwrappedSelector = this.provider.openSelector();
        } catch (IOException var7) {
            throw new ChannelException("failed to open a new selector", var7);
        }

        if (DISABLE_KEYSET_OPTIMIZATION) {
            return new NioEventLoop.SelectorTuple(unwrappedSelector);
        } else {
            final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
            Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
                public Object run() {
                    try {
                        return Class.forName("sun.nio.ch.SelectorImpl", false, PlatformDependent.getSystemClassLoader());
                    } catch (Throwable var2) {
                        return var2;
                    }
                }
            });
            if (maybeSelectorImplClass instanceof Class && ((Class)maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
                final Class<?> selectorImplClass = (Class)maybeSelectorImplClass;
                Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        try {
                            Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
                            Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
                            Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField);
                            if (cause != null) {
                                return cause;
                            } else {
                                cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField);
                                if (cause != null) {
                                    return cause;
                                } else {
                                    selectedKeysField.set(unwrappedSelector, selectedKeySet);
                                    publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
                                    return null;
                                }
                            }
                        } catch (NoSuchFieldException var4) {
                            return var4;
                        } catch (IllegalAccessException var5) {
                            return var5;
                        }
                    }
                });
                if (maybeException instanceof Exception) {
                    this.selectedKeys = null;
                    Exception e = (Exception)maybeException;
                    logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
                    return new NioEventLoop.SelectorTuple(unwrappedSelector);
                } else {
                    this.selectedKeys = selectedKeySet;
                    logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
                    return new NioEventLoop.SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
                }
            } else {
                if (maybeSelectorImplClass instanceof Throwable) {
                    Throwable t = (Throwable)maybeSelectorImplClass;
                    logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
                }

                return new NioEventLoop.SelectorTuple(unwrappedSelector);
            }
        }
    }

先是通过相应平台的的Epoll实现,创建Selector对象,然后构造一个SelectedSelectionKeySet对象,这是Netty自己对SelectKeys的实现,然后通过反射将Selector对象中的selectedKeySet成员变量替换为自己的实现。此处应该全体起立喊666,哈哈!更绝的是Netty最新的4.x版本中加了一条:如果JDK版本大于等于9,连反射都不用了,直接通过Unsafe操作,通过成员变量的的偏移地址修改。有兴趣可以把代码拉下来参观参观。

3.2 关联EventLoop

前面说完了EventLoopGroup的实例化(包括EventLoop),也就是EventLoopGroup bossGroup = new NioEventLoopGroup(n)这行代码。

下面主要分析channel关联EventLoop。

channel关联Eventloop有三种情况:客户端SocketChannel关联EventLoop、服务端ServerSocketChannel关联EventLoop、由服务端ServerSocketChannel创建的SocketChannel关联EventLoop。Netty厉害的就是把这三种情况都都能复用Multithread EventLoopGroup中的register方法:
 

@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

 根据选择策略找到可用的EventLoop,然后调用SingleThreadEventLoop中的register方法,最终调用了 AbstractChannel#AbstractUnsafe.register 后

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // 删除条件检查.
    AbstractChannel.this.eventLoop = eventLoop;
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            eventLoop.execute(new OneTimeTask() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            ...
        }
    }
}

将一个 EventLoop 赋值给 AbstractChannel 内部的 eventLoop 字段, 到这里就完成了 EventLoop 与 Channel 的关联过程。
但是上面代码一直是bind过程,也及时说在main线程中执行,所以会跳入else分支。将注册操作包装为一个Runnable,提交给eventloop的execute方法,该方法实现是在SingleThreadEventExecutor中实现的。

4、追踪ServerBootstrap类

它和ServerChannel相关联,而ServerChannel继承Channel,有一些方法remoteAddress()可以使用

(1)channel(Class)引导类会通过反射的方式创建ChannelFactory

(2)添加TCP参数,并存放在LinkedHasMap

(3)添加服务器处理器handler

(4)添加通信的处理器handler

(5)绑定端口

(6)main线程阻塞等待关闭

4.1分析绑定函数,追踪代码到Server BootStrap的父类AbstractBootstrap中的函数ChannelFuture doBind(final SocketAddress localAddress)如下面代码

 private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = this.initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        } else if (regFuture.isDone()) {
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            final AbstractBootstrap.PendingRegistrationPromise promise = new AbstractBootstrap.PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        promise.setFailure(cause);
                    } else {
                        promise.registered();
                        AbstractBootstrap.doBind0(regFuture, channel, localAddress, promise);
                    }

                }
            });
            return promise;
        }
    }

 4.1.1上面代码中有两个核心函数initAndRegister() 和AbstractBootstrap.doBind0(regFuture, channel, localAddress, promise);

(1)initAndRegister():初始化 NioServerSocketChannel 通道并注册各个 handler,返回一个 future

final ChannelFuture initAndRegister() {
        Channel channel = null;

        try {
            channel = this.channelFactory.newChannel();
            this.init(channel);
        } catch (Throwable var3) {
            if (channel != null) {
                channel.unsafe().closeForcibly();
                return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
            }

            return (new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }

        ChannelFuture regFuture = this.config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }
channel = this.channelFactory.newChannel();通过反射拿到NioServerSocketChannel,记得上面描述ServerBootstrap中有个

.channel(NioServerSocketChannel.class),参数NioServerSocketChannel.class赋值给了channelFactory,所以通过反射拿到其NioServerSocketChannel实例,并创建了管道。

4.1.2 init()方法:

AbstractBootstrap中并没有实现这个方法而是在字类ServerBootStrap中实现了其方法
 void init(Channel channel) throws Exception {
        Map<ChannelOption<?>, Object> options = this.options0();
        synchronized(options) {
            setChannelOptions(channel, options, logger);
        }

        Map<AttributeKey<?>, Object> attrs = this.attrs0();
        synchronized(attrs) {
            Iterator var5 = attrs.entrySet().iterator();

            while(true) {
                if (!var5.hasNext()) {
                    break;
                }

                Entry<AttributeKey<?>, Object> e = (Entry)var5.next();
                AttributeKey<Object> key = (AttributeKey)e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }

        ChannelPipeline p = channel.pipeline();
        final EventLoopGroup currentChildGroup = this.childGroup;
        final ChannelHandler currentChildHandler = this.childHandler;
        Map var9 = this.childOptions;
        final Entry[] currentChildOptions;
        synchronized(this.childOptions) {
            currentChildOptions = (Entry[])this.childOptions.entrySet().toArray(newOptionArray(this.childOptions.size()));
        }

        var9 = this.childAttrs;
        final Entry[] currentChildAttrs;
        synchronized(this.childAttrs) {
            currentChildAttrs = (Entry[])this.childAttrs.entrySet().toArray(newAttrArray(this.childAttrs.size()));
        }

        p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
            public void initChannel(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                ChannelHandler handler = ServerBootstrap.this.config.handler();
                if (handler != null) {
                    pipeline.addLast(new ChannelHandler[]{handler});
                }

                ch.eventLoop().execute(new Runnable() {
                    public void run() {
                        pipeline.addLast(new ChannelHandler[]{new ServerBootstrap.ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
                    }
                });
            }
        }});
    }

(1)设置 NioServerSocketChannel 的 TCP 属性。

(2)对 NioServerSocketChannel 的 ChannelPipeline 添加 ChannelInitializer 处理器。

(3)检查handler是否符合标准

4.1.3 init方法中有个核心方法addLast,他在DefaultChannelPipeline类下

 public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized(this) {
            checkMultiplicity(handler);
            newCtx = this.newContext(group, this.filterName(name, handler), handler);
            this.addLast0(newCtx);
            if (!this.registered) {
                newCtx.setAddPending();
                this.callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    public void run() {
                        DefaultChannelPipeline.this.callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }

        this.callHandlerAdded0(newCtx);
        return this;
    }

(1)检查handler是否符合规范,如果没有 Sharable 注解且已经被使用过了,就抛出异常

(2)创建一个 AbstractChannelHandlerContext 对象,这里说一下,ChannelHandlerContext 对象是 ChannelHandler 和 ChannelPipeline 之间的关联,每当有 ChannelHandler 添加到 Pipeline 中时,都会创建 Context。Context 的主要功能是管理他所关联的 Handler 和同一个 Pipeline 中的其他 Handler 之间的交互。

(3)将Context加到链表最后一个节点之前

(4)最后,同步或者异步或者晚点异步的调用 callHandlerAdded0 方法,在该方法中,调用之前的 handler 的 handlerAdded 方法,而该方法内部调用了之前的 ChannelInitializer 匿名类的 initChannel 方法,并且参数就是 context 的 channel(通过 pipeline 获取),也就是 NioServerSocketChannel。这个 Context 的标准实现就是 DefaultChannelHandlerContext。这个 Context 内部会包含一些重要的属性,比如 pipeline,handler,属于出站类型还是入站类型等

从上面的分析我们可以看出,pipeline 的 addLast 方法,实际上创建一个 Context 对象包装了 pipeline 和 handler,然后通过同步或者异步的方式,间接执行 handler 的 自定义方法-------initChannel 方法。而这个 context 也加入到了 pipeline 的链表节点中。


 

4.1.4 initAndRegister()方法中另一个核心方法this.config().group().register(channel)方法

this.config().group()返回的这个对象bossGroup,然后用bossGroup调用了注册方法。

 继续追踪到MultithreadEventLoopGroup类下的注册方法,这个channel是NIOServerSocketChannel

继续追踪到EventExecutorChooser 的 next 方法

 

 注意,这里是著名的 Netty 对性能压榨的一个例子,Netty 对于选取数组中的线程有着2套策略。

  1. 如果数组是偶数,则使用位运算获取下一个EventLoop(单例线程池)(效率高)。
  2. 如果是奇数,使用取余(效率低)。

所以,如果是自定义数组长度的话,最好是偶数,默认的就是CPU 核心的2倍,即偶数

上面的next方法拿到一个线程后返回,调用register方法,进入SingleThreadEventLoop类下的register方法,下面代码中this指单例线程池

继续,这里创建了一个 DefaultChannelPromise ,这里需要说一下 Promise 的作用,其实类似 Future,事实上也继承了 JDK 的 Future,但增加了很多功能,比如 JDK 的 Future 虽然是异步的,但仍需要 get 方法 阻塞获取结果才能坐之后的事情,而 Promise 可以通过设置监听器的方式,在方法执行成功或者失败的情况下无需等待,就能执行监听器中的任务,效率币 Future 高很多。从某种程度上说,Future 是非阻塞,而Promise 才是正在的异步。

通过调用 promise 的 channel 方法获取了 NioServerSocketChannel ,然后再调用 NioServerSocketChannel 的 unsafe方法获取创建NioServerSocketChannel对象时同时创建的 NioMessageUnsafe 对象,最后调用 NioMessageUnsafe 的 register 方法,参数时 promise 和 NioEventLoop。最后返回了这个 promise 方法。

我们可以先思考一下,之所以使用 promise ,register 内部肯定时异步执行了某个方法,让 promise 立刻返回。执行完毕后再执行设置的监听器的方法。

      @Override
        public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            if (isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
                return;
            }
            if (!isCompatible(eventLoop)) {
                promise.setFailure(
                        new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
                return;
            }

            AbstractChannel.this.eventLoop = eventLoop;

            if (eventLoop.inEventLoop()) {
                register0(promise);
            } else {
                try {
                    eventLoop.execute(new Runnable() {// 开始真正的异步,boss 线程开始启动
                        @Override
                        public void run() {
                            register0(promise);
                        }
                    });
                } catch (Throwable t) {
                    logger.warn(
                            "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                            AbstractChannel.this, t);
                    closeForcibly();
                    closeFuture.setClosed();
                    safeSetFailure(promise, t);
                }
            }
        }

  • 先是一系列的判断。
  • 判断当前线程是否是给定的 eventLoop 线程。注意:这点很重要,Netty 线程模型的高性能取决于对于当前执行的Thread 的身份的确定。如果不在当前线程,那么就需要很多同步措施(比如加锁),上下文切换等耗费性能的操作。
  • 异步(因为我们这里直到现在还是 main 线程在执行,不属于当前线程)的执行 register0 方法。

继续追register()方法下的doRegister,doRegister 应该就是真正的执行方法,而后面的就是管道开始调用 handller 的一些注册成功之后的回调方法。先看doRegister 方法:

 protected void doRegister() throws Exception {
        boolean selected = false;

        while(true) {
            try {
                this.selectionKey = this.javaChannel().register(this.eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException var3) {
                if (selected) {
                    throw var3;
                }

                this.eventLoop().selectNow();
                selected = true;
            }
        }
    }

是不是很熟悉?该方法在一个死循环中向 JDK 中注册感兴趣的事件。如果成功,则直接结束,如果失败,则 调用 EventLoop 内部的 JDK 的 select 的 selectNow 方法立即返回,然后尝试第二次注册,如果还是报错,则抛出异常。注意,这里同时还把自己(NioServerSocketChannel)作为attach 绑定了该 selectKey 上。大家可能奇怪,为什么注册的是0,而不是16 Accpet 事件呢?楼主也不知道。但是最后还是会删除这个读事件,重新注册 accpet 事件的。netty 不知道是怎么想的。

好了,回到 register0 方法,还有2个步骤,分别是执行 pipeline 的 invokeHandlerAddedIfNeeded 方法和 fireChannelRegistered 方法,同时设置 promise 为成功,这个时候,promise就会执行监听器的方法。

invokeHandlerAddedIfNeeded 方法是做什么的呢?还记得我们之前在 pipeline 的 addLast 方法中,添加了一个 handler 吗?我们说该方法可能会晚点执行,因为这个方法被包装成了 task,这里就会执行该方法。这个方法的意思就是,如果管道中有需要执行的任务,就去执行。我们回忆一下那个方法:

callHandlerCallbackLater 方法会根据 added 属性包装成一个 task(add 任务或 removed 任务),成为任务链表上的一个节点。

而 add 任务和 removed 任务的不同在于,add 任务是pipeline 初始化之后调用的任务(通过 Channel 的handlerAdded 方法),removed 是pipeline 结束后执行(通过 Handler 的 handlerRemoved 方法)。

回到 register0 方法,在执行完 invokeHandlerAddedIfNeeded 方法后,也就是我们刚开始的init 方法里的 ChannelInitializer 匿名类的 initChannel 方法。

safeSetSuccess(promise) 方法就是通知 promise 已经成功了,你可以执行监听器的方法了,而这里的监听器则是我们的 dobind 方法中设置的:

至于内部执行,我们稍后再说,先回到我们的 register0 方法中,在 safeSetSuccess 方法执行后,执行 pipeline.fireChannelRegistered() 方法。看名字是执行 handler 的注册成功之后的回调方法。我们跟进去看看:

标题    Pipeline 中的 静态方法,并传入了 head Context

           


获取 head 的 执行器EventLoop,用于判断是否在当前线程,如果在当前线程,则立即执行 invokeChannelRegistered 方法,否则异步执行,我们这里当然是在当前线程。所以同步执行
 

 

在这里执行 Context 对应的 handler 的 channelRegistered 方法标题

 


和之前一样,会检查通道中是否有需要延迟执行的任务,如果有,就执行,然后调用 Context 的 fireChannelRegistered 方法,而不是 pipeline 的 fireChannelRegistered 方法
 
该方法调用的是 head Context 的 invokeChannelRegistered 静态方法,注意,这里的参数很重要,我们进入 findContextInbound 方法内部查看标题

 

 

标题注意,我们说 pipeline 是一个双向链表,这里链表起作用了,通过找到当前节点的下一个节点,并返回,但这判断的是:必须是入站类型的

 

回到 fireChannelRegistered 方法,看看 invokeChannelRegistered 是如何调用的?

注意到了吗,next 节点就是我设置的 LoggingHandler 对应的 Context,获取对应的 EventLoop。从这里我们总结一下 netty 的 Handler 设计:Netty 初始了一个 pipeline,pipeline 内部维护着一个 ChannelContextContext 双向链表,Context 是对 Handler 的封装,是 pipeline 和 Handler 沟通的关键,每次信息入站,从 head 节点开始,执行 context 的 handler 的对应方法,执行结束通过 findContextInbound() 方法找到下一个节点,继续执行

tail 节点的 channelRegistered 什么都不做

 

4.1.5 好,终于可以回到我们 ServerBootStrap 的 initAndRegister 方法中了。

还没有完。我们上面对于第三个步骤 对 NioServerSocketChannel 的 ChannelPipeline 添加 ChannelInitializer 处理器。 只是一笔带过。也就是说,我们从NioServerSocketChannel 的pipeline 的的addLast 方法中一直分析到现在。我们再回头看看该方法:

标ServerBootStrap init 方法题

我们分析了 addLast 方法,但下面还有一个回调方法,什么呢?想 NioServerSocketChannel 的 EventLoop 提交了一个任务,也就是 pipeline 的 addLast 方法。是一个 ServerBootstrapAcceptor 对象,而这个 ServerBootstrapAcceptor 也是一个 handler,你可以想到了吧,从该 handler 名字就可以看出来,该 handler 是用于处理 accept 事件的。我们看看他的构造方法:

上面没有上面好说的,下面有一个 task,任务内容是 设置 该 channel 的autoread 属性为 true,这里我们记一下。还要注意一点,有一个 childHandler 属性,是什么呢?就是我们 main 方法中的 ChannelInitializer 匿名内部类,聪明的你应该想到了,既然该 handler 是接受 accept 事件的,那么,肯定需要初始化管道等操作,不然我们怎么在管道中操作我们的逻辑呢?所以就需要这个 ChannelInitializer 通道初始化对象了。

好了,到这里,我们的 initAndRegister 方法终于算是结束了

 

4.2 接下来看 doBind0 方法,该方法的参数为 initAndRegister 的 future,NioServerSocketChannel,端口地址,NioServerSocketChannel 的 promise。我们进入看看:

该方法想 NioServerSocketChannel 的 eventLoop 提交了一个任务,当 future(其实就是 promise) 成功后执行
NioServerSocketChannel 的 bind 方法,并添加一个关闭监听器。我们主要关注 bind 方法。

 层层调用来到了 NioServerSocketChannel 的 pipeline 的 tail 节点的 bind 方法,该方法首先找到出站节点,然后执行出站节点的 invokeBind 方法。

标题寻找 tail 节点的上一个节点,且必须是出站类型的,根据我们的设置,tail 的上一个节点应该是 LoggingHandler 因为他既是是出站类型也是入站类型

接下来,将调用 LoggingHandler 的 invokeBind 方法。

标context 调用 handler 的 bind 方法题

 

 继续追踪,可以看到打印日志的方法

继续调用ctx.bind方法
标题继续循环,ctx 的 bind 方法就是先寻找下一个节点或者下一个节点,
然后调用节点的 invokeBind 方法,然后调用 handler 的 bind 方法

 

标来到了head 节点的bind方法,这里调用了 unsafe 的 bind方法题
这里的 unsafe 来自 NioServerSocketChannel 的 unsafe

 

 我们来看一看这个bind方法:

 public final void bind(SocketAddress localAddress, ChannelPromise promise) {
            this.assertEventLoop();
            if (promise.setUncancellable() && this.ensureOpen(promise)) {
                if (Boolean.TRUE.equals(AbstractChannel.this.config().getOption(ChannelOption.SO_BROADCAST)) && localAddress instanceof InetSocketAddress && !((InetSocketAddress)localAddress).getAddress().isAnyLocalAddress() && !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
                    AbstractChannel.logger.warn("A non-root user can't receive a broadcast packet if the socket is not bound to a wildcard address; binding to a non-wildcard address (" + localAddress + ") anyway as requested.");
                }

                boolean wasActive = AbstractChannel.this.isActive();

                try {
                    AbstractChannel.this.doBind(localAddress);
                } catch (Throwable var5) {
                    this.safeSetFailure(promise, var5);
                    this.closeIfClosed();
                    return;
                }

                if (!wasActive && AbstractChannel.this.isActive()) {
                    this.invokeLater(new Runnable() {
                        public void run() {
                            AbstractChannel.this.pipeline.fireChannelActive();
                        }
                    });
                }

                this.safeSetSuccess(promise);
            }
        }

上面这段代码,核心是  AbstractChannel.this.doBind(localAddress);进入继续跟踪

 

 将这个任务提交。而这个 fireChannelActive 和之前 pipeline 的所有方法都类似,遍历所有节点,执行 ChannelActive 方法。

最后一步:safeSetSuccess(promise),告诉 promise 任务成功了。其可以执行监听器的方法了。虽然这个 promise 没有任何监听方法。

 

 

如果到这里,楼主(莫那一鲁道)告诉你,整个启动过程已经结束了,你肯定和诧异,什么?服务器不应该是监听 Accept 事件吗,我们分析了这么多,只发现在 doRegister 方法中注册了 0 (read) 事件,竟然没有监听 Accept 事件,和我们平时写的 Nio 代码不同啊?

一切就在上面的 fireChannelActive 方法中。该方法回先调用 head 节点的 channelActive 方法,而 head 节点的 channelActive 代码如下:

 readIfIsAutoRead 默认返回 true。然后像之前的 pipeline 一样,继续在链表中调用。最后,来到了一个关键的地方:

Head 节点 的read 方法

 调用的是 NioServerSocketChannel 的 unsafe 的 beginRead 方法。继续查看:

我们看看 NioServerSocketChannel 的 doBeginRead 方法。
拿到 selectionKey ,如果 key 的监听事件是0 的话,就改为 readInterestOp ,也就是我们初始化NioServerSocketChannel 时设置的值:


 5 总结

好了,从源码层面已经分析完了,我们来总结一下启动的过程。

  1. 首先创建2个 EventLoopGroup 线程池数组。数组默认大小CPU*2,方便chooser选择线程池时提高性能。
  2. BootStrap 将 boss 设置为 group属性,将 worker 设置为 childer 属性。
  3. 通过 bind 方法启动,内部重要方法为 initAndRegister 和 dobind 方法。
  4. initAndRegister 方法会反射创建 NioServerSocketChannel 及其相关的 NIO 的对象, pipeline , unsafe,同时也为 pipeline 初始了 head 节点和 tail 节点。同时也含有 NioServerSocketChannelConfig 对象。然后向 pipeline 添加自定义的处理器和 ServerBootstrapAcceptor 处理器。这个处理器用于分配接受的 请求给 worker 线程池。每次添加处理器都会创建一个相对应的 Context 作为 pipeline 的节点并包装 handler 对象。注册过程中会调用 NioServerSocketChannel 的 doRegister 方法注册读事件。
  5. 在register0 方法成功以后调用在 dobind 方法中调用 doBind0 方法,该方法会 调用 NioServerSocketChannel 的 doBind 方法对 JDK 的 channel 和端口进行绑定,之后在调用 pipeline 的fireChannelActive 最后会调用 NioServerSocketChannel 的 doBeginRead 方法,将感兴趣的事件设置为Accept,完成 Netty 服务器的所有启动,并开始监听连接事件。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!