Netty源码学习 - netty服务端通道的注册流程

系列文章目录

Netty源码学习 - netty服务端通道的实例化流程
Netty源码学习 - netty服务端通道的初始化流程
Netty源码学习 - netty服务端通道的注册流程



前言

在前面一章中详细介绍了netty服务端通道的初始化过程,在初始化过程中主要做了两个事情,一个就是用户设置的选项与参数的设置,另一个就是添加了系统定义的通道处理器,这些通道处理器添加完成之后,真正的执行逻辑是需要等到通道被注册之后才会真实触发的,本章我们就来探讨通道初始化的下一个流程-通道的注册流程。对应的代码在io.netty.bootstrap.AbstractBootstrap#initAndRegister当中.
在这里插入图片描述
在这里我们不难看出,注册其实是由EventLoopGroup来执行的,所以本章首先从EventLoopGroup来讲起,然后才是通道的注册流程。


提示:以下是本篇文章正文内容,下面案例可供参考

一、EventLoopGroup

在我们编写的netty服务端代码中存在如下的片段

// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
    ServerBootstrap b = new ServerBootstrap();
    b.group(bossGroup, workerGroup)

在这里创建了两个NioEventLoopGroup对象,然后设置到了ServerBootstrap当中,其中第一个作为NIO中ServerChannel的连接处理(acceptor),后一个用于ServerChannel的连接处理后创建的用于IO数据处理的Channel的处理(client)。

Set the EventLoopGroup for the parent (acceptor) and the child (client). These EventLoopGroup’s are used to handle all the events and IO for ServerChannel and Channel’s.

那么这个NioEventLoopGroup究竟是啥呢?看一下类的继承结构图。在这里插入图片描述
从这个图大致能看出,这个类与线程池有很大的关系。通常我们可以认为这是netty用于执行任务的线程池,但是其实不仅仅如此。在NioEventLoopGroup当中包含的最重要的元素就是一个称为childrenEventExecutor数组。那么这个EventExecutor又是什么呢?这里其实就是NioEventLoop。在上面两个构造方法中,传入的参数只是决定这个EventExecutor数组的大小。如下图所示
在这里插入图片描述
如果采用的默认构造参数,那么EventExecutor执行器的个数取决于当前系统的CPU的个数。比如个数为4,那么执行器的个数就是8.

private static final int DEFAULT_EVENT_LOOP_THREADS;

static {
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
            "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2));

    if (logger.isDebugEnabled()) {
        logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
    }
}

/**
 * @see MultithreadEventExecutorGroup#MultithreadEventExecutorGroup(int, Executor, Object...)
 */
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
    super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}

因为从上面可以看出,NioEventLoopGroup最重要的信息其实就是这个数组元素的信息,在当前就是NioEventLoop对象。那么这个类是干啥的呢?SingleThreadEventLoop implementation which register the Channel's to a Selector and so does the multi-plexing of these in the event loop.。按照官方的说明,就是将通道注册到选择器,并且在事件循环中对它们进行多路复用。对应的loop其实就是在run方法中执行的。
在这里插入图片描述
NioEventLoop中包含了两块重要的内容,一个就是与NIO相关的Seletor信息,另一个就是对应的线程与任务队列信息。以下为构造完成之后的NioEventLoop信息。
在这里插入图片描述
NioEventLoop在构造的过程中会初始化好与Selector相关的信息,而对应的线程需要等到后续通过executor来获取。其实就是等到这个NioEventLoop第一次执行任务的时候才会设置。
在这里插入图片描述
下面的就是设置了thread之后的信息
在这里插入图片描述
后面的循环操作都是由这个线程来执行的。所以对NioEventLoopGroup的理解就是包含了多个NioEventLoop,而NioEventLoop就是相当于一个包含了NIO中Selector信息以及各种针对通道操作的单线程池。首先看一下有关Selector信息是如何获取的。

public NioEventLoopGroup(int nThreads, Executor executor) {
    this(nThreads, executor, SelectorProvider.provider());
}

首先是在NioEventLoopGroup构造方法中获取系统的SelectorProvider,返回的是一个单例对象,在前面已经讲解过。最后在MultithreadEventExecutorGroup的构造方法中就会初始化children数组,也就是初始化和设置NioEventLoop信息。
在这里插入图片描述

/**
 * Create a new EventExecutor which will later then accessible via the {@link #next()}  method. This method will be
 * called for each thread that will serve this {@link MultithreadEventExecutorGroup}.
 *
 */
protected abstract EventExecutor newChild(Executor executor, Object... args) throws Exception;

这个方法由MultithreadEventExecutorGroup具体的实现类实现。在这里就是NioEventLoopGroup

@Override
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实例。

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");
    }
    if (strategy == null) {
        throw new NullPointerException("selectStrategy");
    }
    provider = selectorProvider;
    final SelectorTuple selectorTuple = openSelector();
    selector = selectorTuple.selector;
    unwrappedSelector = selectorTuple.unwrappedSelector;
    selectStrategy = strategy;
}

在这里首先是在父类的构造器中初始化一些公共的属性,比如队列,还有设置执行器等
在这里插入图片描述

1、打开openSelector

接下来就是属于NIO相关的openSelector逻辑了。在netty当中一个NioEventLoop会包含有一个选择器,用于获取TCP事件。其实在这里构造了一个二元组,存放NIO的Selector实例unwrappedSelector和Netty的Selector实例selector,后者其实就是针对前者的一个封装。

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

    if (DISABLE_KEYSET_OPTIMIZATION) {
        return new SelectorTuple(unwrappedSelector);
    }

    final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();

    Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
        @Override
        public Object run() {
            try {
                return Class.forName(
                        "sun.nio.ch.SelectorImpl",
                        false,
                        PlatformDependent.getSystemClassLoader());
            } catch (Throwable cause) {
                return cause;
            }
        }
    });

    if (!(maybeSelectorImplClass instanceof Class) ||
            // ensure the current selector implementation is what we can instrument.
            !((Class<?>) maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
        if (maybeSelectorImplClass instanceof Throwable) {
            Throwable t = (Throwable) maybeSelectorImplClass;
            logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
        }
        return new SelectorTuple(unwrappedSelector);
    }

    final Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;

    Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
        @Override
        public Object run() {
            try {
                Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
                Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");

                Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
                if (cause != null) {
                    return cause;
                }
                cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField, true);
                if (cause != null) {
                    return cause;
                }

                selectedKeysField.set(unwrappedSelector, selectedKeySet);
                publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
                return null;
            } catch (NoSuchFieldException e) {
                return e;
            } catch (IllegalAccessException e) {
                return e;
            }
        }
    });

    if (maybeException instanceof Exception) {
        selectedKeys = null;
        Exception e = (Exception) maybeException;
        logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
        return new SelectorTuple(unwrappedSelector);
    }
    selectedKeys = selectedKeySet;
    logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
    return new SelectorTuple(unwrappedSelector,
                             new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}

此处能够进行包装必须满足一定的条件,首先当前系统中获取的Selector对象是sun.nio.ch.SelectorImpl的实现类,并且包含有selectedKeyspublicSelectedKeys,并且可以给这两个属性赋值SelectedSelectionKeySet类型的数据,那么返回的SelectorTuple就包含有包装的Selector对象。以下为windows下返回的情况
在这里插入图片描述

2、创建独有执行线程

NioEventLoop继承自SingleThreadEventExecutor,除了包含单独的选择器,还包括了一个专有执行线程,在实例化的过程中这个线程是没有的,当第一次执行任务的时候才会创建这个线程(io.netty.util.concurrent.SingleThreadEventExecutor#execute)。

@Override
public void execute(Runnable task) {
    if (task == null) {
        throw new NullPointerException("task");
    }
    1. 判断执行线程是否是专有执行线程
    boolean inEventLoop = inEventLoop();
    2. 添加任务
    addTask(task);
    if (!inEventLoop) {
        3. 开启线程
        startThread();
        if (isShutdown() && removeTask(task)) {
            reject();
        }
    }

    if (!addTaskWakesUp && wakesUpForTask(task)) {
        wakeup(inEventLoop);
    }
}
  • 判断执行线程是不是属于这个EventLoop
@Override
public boolean inEventLoop() {
    return inEventLoop(Thread.currentThread());
}

@Override
public boolean inEventLoop(Thread thread) {
    return thread == this.thread;
}

因为在netty当中,并不禁止其他线程调用NioEventLoop的任务执行方法,但是如果这个线程不是属于这个NioEventLoop的专有线程,则会将这个任务添加到队列中,然后等待专有线程来执行这个任务,而提交任务的线程就可以返回继续其他任务了。

  • 添加任务到任务队列中
/**
 * Add a task to the task queue, or throws a {@link RejectedExecutionException} if this instance was shutdown
 * before.
 */
protected void addTask(Runnable task) {
    if (task == null) {
        throw new NullPointerException("task");
    }
    if (!offerTask(task)) {
        reject(task);
    }
}

final boolean offerTask(Runnable task) {
    if (isShutdown()) {
        reject();
    }
    return taskQueue.offer(task);
}

在这里就是将任务添加到taskQueue队列当中(这个队列在NioEventLoop构造的时候已经初始化了)。

  • 开启专有线程
    如果当前执行的线程不是专有线程,第一次的时候专有线程是不存在的,所以inEventLoop一定是返回false的,此时就会开启专有线程,此处要考虑并发问题,netty通过CAS来保证的。通过state字段记录线程的状态。
private volatile int state = ST_NOT_STARTED;

private static final AtomicIntegerFieldUpdater<SingleThreadEventExecutor> STATE_UPDATER =
           AtomicIntegerFieldUpdater.newUpdater(SingleThreadEventExecutor.class, "state");

private void startThread() {
    if (state == ST_NOT_STARTED) {
        if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
            try {
                doStartThread();
            } catch (Throwable cause) {
                STATE_UPDATER.set(this, ST_NOT_STARTED);
                PlatformDependent.throwException(cause);
            }
        }
    }
}

开启线程是通过executor来执行的,这个executor是一个线程池,在执行任务的时候将会将具体执行的线程赋值给当前NioEventLoop实例作为专有线程。
在这里插入图片描述
对于这个executor,在创建NioEventLoopGroup的时候可以通过构造方法传入,如果没有设置,则通过系统创建。在前面提到过,如下所示
在这里插入图片描述
在这里使用的线程工厂就是DefaultThreadFactory.

protected ThreadFactory newDefaultThreadFactory() {
    return new DefaultThreadFactory(getClass());
}

创建线程的时候就会进入到对应的newThread方法当中。executor执行任务方法如下io.netty.util.concurrent.ThreadPerTaskExecutor#execute,通过线程工厂创建线程并启动这个线程。

@Override
public void execute(Runnable command) {
    threadFactory.newThread(command).start();
}

在这里插入图片描述
创建完这个专有线程之后,此时就有两个线程在执行业务了,一个是开启这个专有线程的线程,另一个就是专有线程了。
在这里插入图片描述
前者提交完任务之后就返回了,而后者则执行前面doStartThread中的任务。并将执行线程赋值作为专有线程,最后进入到那个无限循环的方法了。
在这里插入图片描述
在这里插入图片描述
在这个无限循环的方法中主要有两个任务,一个就是处理选择器返回的信息,另一个就是处理任务队列taskQueue中的任务。
在这里插入图片描述
总结一下:所谓的NioEventLoopGroup无非就是创建了指定个数的NioEventLoop,而每个NioEventLoop又包含一个Selector、专有线程、任务等待队列,在无限循环使用这个专有线程处理Selector感兴趣的事件以及从等待队列当中获取任务并执行。

二、通道注册流程

ChannelFuture regFuture = config().group().register(channel);

通道注册的流程是通过NioEventLoopGroup来执行的。

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

在这里会通过next获取一个EventLoop来执行,因为NioEventLoopGroup可能包含多个EventLoop,这里其实使用的一个负载均衡的思路。

@Override
public EventLoop next() {
    return (EventLoop) super.next();
}

具体如下

@Override
public EventExecutor next() {
    return chooser.next();
}

其实就是一个简单的轮询(Round Robin)法。
在这里插入图片描述
获取到一个EventLoop实例之后,具体的注册操作是由这个EventLoop来执行的(io.netty.channel.SingleThreadEventLoop#register(io.netty.channel.Channel))。

@Override
public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}

@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}

最后还是会回到对应的通道实例的方法io.netty.channel.AbstractChannel.AbstractUnsafe#register.

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    if (eventLoop == null) {
        throw new NullPointerException("eventLoop");
    }
    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() {
                @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不能为空,而且类型必须是NioEventLoop,另外这个通道不能已经注册过。这里还会把当前的eventLoop赋值为Channel的eventLoop属性。

在netty当中,每一个channel都会对应一个eventLoop,是一对一的关系,但是一个eventLoop却不一定是只为一个channel服务的,一个eventLoop可能会作用于多个channel。毕竟channel要支持很多的链接,而eventLoop的个数通常不会太多,至于分配的原则其实就是之前说过的负载均衡(next)。

接下来会判断当前执行的线程是不是与当前NioEventLoop的专有执行线程是不是一致的(此处是由main线程发起的注册,所以这里是不一致的,如果这个NioEventLoop之前没有执行过,专有线程还没有创建,所以真正的register0操作是通过eventLoop当做任务来执行的)。
在这里插入图片描述
通常这里也是这个NioEventLoop第一次执行任务,就会执行我们上面说的专有线程创建的逻辑,并最终进入到无限循环的逻辑当中。其中的register0就会放到任务队列中等待执行。
在这里插入图片描述
在这里插入图片描述
对应源码如下

private void register0(ChannelPromise promise) {
    try {
        // check if the channel is still open as it could be closed in the mean time when the register
        // call was outside of the eventLoop
        if (!promise.setUncancellable() || !ensureOpen(promise)) {
            return;
        }
        boolean firstRegistration = neverRegistered;
        doRegister();
        neverRegistered = false;
        registered = true;

        // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
        // user may already fire events through the pipeline in the ChannelFutureListener.
        pipeline.invokeHandlerAddedIfNeeded();

        safeSetSuccess(promise);
        pipeline.fireChannelRegistered();
        // Only fire a channelActive if the channel has never been registered. This prevents firing
        // multiple channel actives if the channel is deregistered and re-registered.
        if (isActive()) {
            if (firstRegistration) {
                pipeline.fireChannelActive();
            } else if (config().isAutoRead()) {
                // This channel was registered before and autoRead() is set. This means we need to begin read
                // again so that we process inbound data.
                //
                // See https://github.com/netty/netty/issues/4805
                beginRead();
            }
        }
    } catch (Throwable t) {
        // Close the channel directly to avoid FD leak.
        closeForcibly();
        closeFuture.setClosed();
        safeSetFailure(promise, t);
    }
}

这里包含有以下几个任务,首先是进行注册(doRegister),接下来就是触发通道管理器的handlerAdded事件(invokeHandlerAddedIfNeeded),然后就是触发通道注册事件(fireChannelRegistered),触发通道激活事件(fireChannelActive).

1、doRegister

所谓注册无非就是将通道注册到选择器上面,在netty的通道实例当中包含有nio的通道实例,而eventLoop又包含有选择器。

@Override
protected void doRegister() throws Exception {
    boolean selected = false;
    for (;;) {
        try {
            selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
            return;
        } catch (CancelledKeyException e) {
            if (!selected) {
                // Force the Selector to select now as the "canceled" SelectionKey may still be
                // cached and not removed because no Select.select(..) operation was called yet.
                eventLoop().selectNow();
                selected = true;
            } else {
                // We forced a select operation on the selector before but the SelectionKey is still cached
                // for whatever reason. JDK bug ?
                throw e;
            }
        }
    }
}

2、invokeHandlerAddedIfNeeded

final void invokeHandlerAddedIfNeeded() {
    assert channel.eventLoop().inEventLoop();
    if (firstRegistration) {
        firstRegistration = false;
        // We are now registered to the EventLoop. It's time to call the callbacks for the ChannelHandlers,
        // that were added before the registration was done.
        callHandlerAddedForAllHandlers();
    }
}

在每次向管道上添加通道管理器的时候会注册一个PendingHandlerAddedTask事件,然后挂到pendingHandlerCallbackHead为头结点的一个迭代器上面。而触发HandlerAdded事件就是遍历这个迭代器,一个一个PendingHandlerAddedTask执行即可。

private void callHandlerAddedForAllHandlers() {
    final PendingHandlerCallback pendingHandlerCallbackHead;
    synchronized (this) {
        assert !registered;

        // This Channel itself was registered.
        registered = true;

        pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
        // Null out so it can be GC'ed.
        this.pendingHandlerCallbackHead = null;
    }

    // This must happen outside of the synchronized(...) block as otherwise handlerAdded(...) may be called while
    // holding the lock and so produce a deadlock if handlerAdded(...) will try to add another handler from outside
    // the EventLoop.
    PendingHandlerCallback task = pendingHandlerCallbackHead;
    while (task != null) {
        task.execute();
        task = task.next;
    }
}

在初始化的时候添加通道管理的时候就会添加PendingHandlerAddedTask并挂到pendingHandlerCallbackHead实例上面,并且通过next一个一个的链接。

在这里会将通道的registered属性修改为true

在这里插入图片描述
所以在上面的这个循环任务里就是遍历所有的PendingHandlerAddedTask并一个一个的执行。
在这里插入图片描述
这里首先会修改通道管理器对应的上下文的状态为ADD_COMPLETE,然后触发对应通道管理器的handlerAdded方法。
在这里插入图片描述
当前的这个ChannelHandler是在通道初始化(io.netty.bootstrap.ServerBootstrap#init)最后添加一个通道初始化管理器。回忆一下
在这里插入图片描述
由于这是一个ChannelInitializer类型的通道管理器,所以执行方法顺序为handlerAdded->initChannel,这个在上文已经讲解过,也就是最后会执行到上图中的方法。

final ChannelPipeline pipeline = ch.pipeline();
 ChannelHandler handler = config.handler();
 if (handler != null) {
     1. 直接使用当前线程
     pipeline.addLast(handler);
 }

 2. 获取eventLoop执行下一个任务
 ch.eventLoop().execute(new Runnable() {
     @Override
     public void run() {
         pipeline.addLast(new ServerBootstrapAcceptor(
                 ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
     }
 });

如下图所示
在这里插入图片描述
在上面添加添加的第一个handler的时候由于使用的就是当前的线程,而且在之前已经将registered属性修改为true。所以在addLast方法中会直接执行callHandlerAdded0方法而不是添加到任务回调链表头对象pendingHandlerCallbackHead中。
在这里插入图片描述
在添加完用户定义的这个通道管理器并且触发HandlerAdded事件之后,通道会通过自己的eventLoop(在前面注册的时候赋值的,每一个channel都会绑定到一个eventLoop上面)执行添加ServerBootstrapAcceptor类型的通道管理器的任务。当前是同一个EventLoop,将这个任务添加到任务列表当中。
在这里插入图片描述
判断是否需要wakesUpForTask,其实就是判断这个任务是不是一个NonWakeupRunnable实例。

@Override
protected boolean wakesUpForTask(Runnable task) {
    return !(task instanceof NonWakeupRunnable);
}

这里并不是,所以需要进入wakeup操作。

@Override
protected void wakeup(boolean inEventLoop) {
    if (!inEventLoop && wakenUp.compareAndSet(false, true)) {
        selector.wakeup();
    }
}

其实这里在前面startThread的时候已经触发过了。

wakeup -> Causes the first selection operation that has not yet returned to return immediately.
If another thread is currently blocked in an invocation of the select() or select(long) methods then that invocation will return immediately. If no selection operation is currently in progress then the next invocation of one of these methods will return immediately unless the selectNow() method is invoked in the meantime. In any case the value returned by that invocation may be non-zero. Subsequent invocations of the select() or select(long) methods will block as usual unless this method is invoked again in the meantime.
Invoking this method more than once between two successive selection operations has the same effect as invoking it just once.

由于当前触发的事件对应的通道管理器是ChannelInitializer类型的,执行完初始化操作之后就会从管道中移除了。其实就是遍历管道中的上下文对象,将上下文对象对应的通道管理器与当前通道管理器比较是不是同一个对象,如果是的话,从管道的上下文链表中删除对应的上下文对象即可、只不过移除之后,需要再次触发对应通道管理器的handlerRemoved事件。此处不再详细介绍了。

private void remove(ChannelHandlerContext ctx) {
    try {
        ChannelPipeline pipeline = ctx.pipeline();
        if (pipeline.context(this) != null) {
            pipeline.remove(this);
        }
    } finally {
        initMap.remove(ctx);
    }
}

3、safeSetSuccess

当循环执行完所有的HandlerAdded事件之后呢,就会通过传入的ChannelPromise修改状态并发布事件。

/**
 * Marks the specified {@code promise} as success.  If the {@code promise} is done already, log a message.
 */
protected final void safeSetSuccess(ChannelPromise promise) {
    if (!(promise instanceof VoidChannelPromise) && !promise.trySuccess()) {
        logger.warn("Failed to mark a promise as success because it is done already: {}", promise);
    }
}

```io.netty.channel.DefaultChannelPromise#trySuccess````通过CAS修改引用

@Override
public boolean trySuccess() {
    return trySuccess(null);
}

private static final AtomicReferenceFieldUpdater<DefaultPromise, Object> RESULT_UPDATER =
       AtomicReferenceFieldUpdater.newUpdater(DefaultPromise.class, Object.class, "result");
private static final Object SUCCESS = new Object();
private static final Object UNCANCELLABLE = new Object();
private volatile Object result;

private boolean setSuccess0(V result) {
    return setValue0(result == null ? SUCCESS : result);
}

private boolean setValue0(Object objResult) {
    if (RESULT_UPDATER.compareAndSet(this, null, objResult) ||
        RESULT_UPDATER.compareAndSet(this, UNCANCELLABLE, objResult)) {
        checkNotifyWaiters();
        return true;
    }
    return false;
}

当修改对象成功之后会通知监听器

@Override
public boolean trySuccess(V result) {
    if (setSuccess0(result)) {
        notifyListeners();
        return true;
    }
    return false;
}

在这里插入图片描述
在这里插入图片描述
此时会触发对应GenericFutureListener的操作完成事件。

private static void notifyListener0(Future future, GenericFutureListener l) {
    try {
        l.operationComplete(future);
    } catch (Throwable t) {
        logger.warn("An exception was thrown by " + l.getClass().getName() + ".operationComplete()", t);
    }
}

在这里最关键的是这个GenericFutureListener是从哪里来的,也就是属性listeners是从哪里来的,其实这里可以通过当前对象的addListeners方法来添加。

@Override
public Promise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners) {
    checkNotNull(listeners, "listeners");

    synchronized (this) {
        for (GenericFutureListener<? extends Future<? super V>> listener : listeners) {
            if (listener == null) {
                break;
            }
            addListener0(listener);
        }
    }

    if (isDone()) {
        notifyListeners();
    }

    return this;
}

可以看到在这里添加完成之后呢,也会执行notifyListeners的一个操作。但是添加完成之后,不是就能立刻执行了,因为这里同样涉及到inEventLoop的一个问题。也就是说如果不是对应的eventLoop对象添加的listeners会进入如下的流程
在这里插入图片描述

private static void safeExecute(EventExecutor executor, Runnable task) {
    try {
        executor.execute(task);
    } catch (Throwable t) {
        rejectedExecutionLogger.error("Failed to submit a listener notification task. Event loop shut down?", t);
    }
}

其实又会进入到io.netty.util.concurrent.SingleThreadEventExecutor#execute中,判断是否inEventLoop,添加任务,eventLoop在无限循环中执行了。如果单纯从这个线程来分析当前的GenericFutureListener是不够的,对于发起注册任务的主线程此时执行了什么逻辑了呢?
在这里插入图片描述
在主线程发起注册之后就会立即返回
在这里插入图片描述
此时如果注册事件已经完成就是进入doBind0操作,而如果还没有完成就会注册一个监听器,当注册事件完成之后来触发这个事件,不知道看到addListeneroperationComplete操作是不是会柳暗花明的感觉。

private static void doBind0(
        final ChannelFuture regFuture, final Channel channel,
        final SocketAddress localAddress, final ChannelPromise promise) {

    // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
    // the pipeline in its channelRegistered() implementation.
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            if (regFuture.isSuccess()) {
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}

再看一下doBind0方法,即便是直接进入也不会由当前线程来执行这个任务,还是交给当前通道对应的eventLoop来执行。在回忆一下我们Server端的代码

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

在主线程注册了事件之后就会进入等待了。

@Override
public Promise<V> sync() throws InterruptedException {
    await();
    rethrowIfFailed();
    return this;
}

这个sync方法是属于DefaultChannelPromise,需要等到这个promise对象执行notifyAll方法才会返回。
在这里插入图片描述
现在我们在回到notifyListenersNow方法中,其实这里就会进入doBind0操作了。
在这里插入图片描述
注册了一个bind操作。

4、fireChannelRegistered

接下来触发通道的注册事件。

@Override
public final ChannelPipeline fireChannelRegistered() {
    AbstractChannelHandlerContext.invokeChannelRegistered(head);
    return this;
}

无非是从通道的头节点开始执行invokeChannelRegistered方法。

private void invokeChannelRegistered() {
    if (invokeHandler()) {
        try {
            ((ChannelInboundHandler) handler()).channelRegistered(this);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        fireChannelRegistered();
    }
}

每个通道的第一个节点是HeadContext类型,因此会进入HeadContext#channelRegistered方法

@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
    invokeHandlerAddedIfNeeded();
    ctx.fireChannelRegistered();
}

在这里并不是要触发所有的通道处理器上下文,而是只触发入站处理器的上下文。

@Override
public ChannelHandlerContext fireChannelRegistered() {
    由于这里只会找到下一个触发 所以如果用户自定义的通道管理器在触发了注册事件之后不继续传递 那么后续的上下文就不会再触发了
    invokeChannelRegistered(findContextInbound());
    return this;
}

在这里插入图片描述
在这里插入图片描述

如果用户自定义的通道处理器不在channelRegistered方法中继续传递事件,后面的通道处理器上下文件就不会触发这个注册事件了。主要的原因就在于io.netty.channel.AbstractChannelHandlerContext#fireChannelRegistered方法并没有执行遍历操作,只是触发下一个入站上下文,而不是继续触发所有。

在这里插入图片描述

5、fireChannelActive

如果要触发fireChannelActive需要等待socket已经完成了注册,也就是第3步中的doBind0已经执行完毕。

@Override
public boolean isActive() {
    return javaChannel().socket().isBound();
}

由于doBind0还在等待队列中,所以这里不会真实执行fireChannelActive事件了。当前的注册事件执行完毕,继续执行队列中的其他任务,比如

pipeline.addLast(new ServerBootstrapAcceptor(
                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));

在这里插入图片描述
添加到通道中,由于这个通道管理器的handlerAdded实现为空,所以这里没有啥复杂的逻辑,添加到管道任务也就结束了。继续下一个任务,也就是doBind0中添加的绑定任务。

if (regFuture.isSuccess()) {
   channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
   promise.setFailure(regFuture.cause());
}

在这里插入图片描述
通道的绑定操作会交给管道来执行

@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return pipeline.bind(localAddress, promise);
}

而管道又会交给尾节点来执行。

@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
    return tail.bind(localAddress, promise);
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后会传递到HeadContext(既是出站又是入站处理器)并执行bind操作

@Override
public void bind(
        ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
        throws Exception {
    unsafe.bind(localAddress, promise);
}

在这里插入图片描述
终于执行绑定操作了。

@Override
protected void doBind(SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        javaChannel().bind(localAddress, config.getBacklog());
    } else {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}

Binds the channel’s socket to a local address and configures the socket to listen for connections.
This method is used to establish an association between the socket and a local address. Once an association is established then the socket remains bound until the channel is closed.

The backlog parameter is the maximum number of pending connections on the socket. Its exact semantics are implementation specific. In particular, an implementation may impose a maximum length or may choose to ignore the parameter altogther. If the backlog parameter has the value 0, or a negative value, then an implementation specific default is used.

在这里插入图片描述

private void invokeLater(Runnable task) {
    try {
        // This method is used by outbound operation implementations to trigger an inbound event later.
        // They do not trigger an inbound event immediately because an outbound operation might have been
        // triggered by another inbound event handler method.  If fired immediately, the call stack
        // will look like this for example:
        //
        //   handlerA.inboundBufferUpdated() - (1) an inbound handler method closes a connection.
        //   -> handlerA.ctx.close()
        //      -> channel.unsafe.close()
        //         -> handlerA.channelInactive() - (2) another inbound handler method called while in (1) yet
        //
        // which means the execution of two inbound handler methods of the same handler overlap undesirably.
        eventLoop().execute(task);
    } catch (RejectedExecutionException e) {
        logger.warn("Can't invoke task later as EventLoop rejected it", e);
    }
}

把上述任务添加到队列之后,又再次触发safeSetSuccess事件。trySuccess->notifyListeners->notifyListenersNow,但此时并没有listener,然后完成了绑定操作,返回。

channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);

添加一个CLOSE_ON_FAILURE的listener并继续触发notifyListeners.再继续下一个任务
在这里插入图片描述
终于轮到fireChannelActive方法了。在上一次注册方法中之所以不能fireChannelActive就是因为当时还没有完成NIO的绑定操作,此时已经弯沉了绑定操作,时机到了。

@Override
public boolean isActive() {
    return javaChannel().socket().isBound();
}

从头节点开始传递事件

@Override
public final ChannelPipeline fireChannelActive() {
    AbstractChannelHandlerContext.invokeChannelActive(head);
    return this;
}
@Override
public ChannelHandlerContext fireChannelActive() {
    invokeChannelActive(findContextInbound());
    return this;
}

可以看出这里只和入站处理器有关。以下是几个重要的逻辑。
io.netty.channel.DefaultChannelPipeline.HeadContext#channelActive

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    ctx.fireChannelActive();

    readIfIsAutoRead();
}

io.netty.channel.DefaultChannelPipeline.TailContext#channelActive

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    onUnhandledInboundChannelActive();  // 其实是一个空的逻辑
}

其中真正重要的就是HeadContextchannelActive,这里在执行了其他其他通道管理的channelActive事件之后,会触发通道的read操作。

private void readIfIsAutoRead() {
    // 默认值为true
    if (channel.config().isAutoRead()) {
        channel.read();
    }
}

执行通道的读取事件,

@Override
public Channel read() {
    pipeline.read();
    return this;
}

交给尾结点读取

@Override
public final ChannelPipeline read() {
    tail.read();
    return this;
}
@Override
public ChannelHandlerContext read() {
    1. 查找出站处理器
    final AbstractChannelHandlerContext next = findContextOutbound();
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeRead();
    } else {
        Runnable task = next.invokeReadTask;
        if (task == null) {
            next.invokeReadTask = task = new Runnable() {
                @Override
                public void run() {
                    next.invokeRead();
                }
            };
        }
        executor.execute(task);
    }

    return this;
}

private void invokeRead() {
    if (invokeHandler()) {
        try {
            ((ChannelOutboundHandler) handler()).read(this);
        } catch (Throwable t) {
            notifyHandlerException(t);
        }
    } else {
        read();
    }
}

最后就会进入到io.netty.channel.DefaultChannelPipeline.HeadContext#read

@Override
public void read(ChannelHandlerContext ctx) {
    unsafe.beginRead();
}

在这里插入图片描述
修改readInterestOp为对连接事件感兴趣。

/**
 * Operation-set bit for socket-accept operations.
 *
 * <p> Suppose that a selection key's interest set contains
 * <tt>OP_ACCEPT</tt> at the start of a <a
 * href="Selector.html#selop">selection operation</a>.  If the selector
 * detects that the corresponding server-socket channel is ready to accept
 * another connection, or has an error pending, then it will add
 * <tt>OP_ACCEPT</tt> to the key's ready set and add the key to its
 * selected-key&nbsp;set.  </p>
 */
public static final int OP_ACCEPT = 1 << 4;

注册感兴趣的事件,在前面通过以下代码

selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);

注册的时候,设置的interestOps为0,其实就是不对任何事件类型感兴趣。对于服务端,是需要对连接感兴趣,这个值在通道的构造的时候已经设置好了。

this.readInterestOp = readInterestOp;

修改感兴趣的事件

@Override
protected void doBeginRead() throws Exception {
    // Channel.read() or ChannelHandlerContext.read() was called
    final SelectionKey selectionKey = this.selectionKey;
    if (!selectionKey.isValid()) {
        return;
    }

    readPending = true;

    final int interestOps = selectionKey.interestOps();
    if ((interestOps & readInterestOp) == 0) {
        selectionKey.interestOps(interestOps | readInterestOp);
    }
}

在这里插入图片描述

interestOps ->Sets this key’s interest set to the given value.This method may be invoked at any time. Whether or not it blocks, and for how long, is implementation-dependent.

终于完成了fireChannelActive事件。

至此所有的任务runAllTasks都完成了,继续在EventLoop中执行下一个Loop
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值