Netty Server端启动分析(三)register0和 doBind0分析

上篇中,整体分析了doBind方法,但是遗留了 register0doBind0 方法。

register0执行

核心代码在 AbstractChannelregister 入口:

            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中执行,即是否当前方法由reactor线程执行。
SingleThreadEventExecutor 中,判断依据为:

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

否则会将其推入任务队列中执行。

register0详解
        private void register0(ChannelPromise promise) {
            try {
                // 判断是否能注册
                if (!promise.setUncancellable() || !ensureOpen(promise)) {
                    return;
                }
                boolean firstRegistration = neverRegistered;
                // 实际注册方法,即调用nio底层channel进行注册
                doRegister();
                neverRegistered = false;  // 设置变量
                registered = true;

                // 执行handlerAdded方法
                pipeline.invokeHandlerAddedIfNeeded();
				// 告知其他监听着
                safeSetSuccess(promise);
                pipeline.fireChannelRegistered();  // 调用pipeline的channelregister方法
                // 准确来说,只有首次被注册,才能调用active方法。这里判断原因是因为一个channel可能被deregistered 再 re-registered.
                if (isActive()) {
                    if (firstRegistration) {
                        pipeline.fireChannelActive();
                    } else if (config().isAutoRead()) {
                        // 如果先前就被注册过滤,并且被设置为autoRead,那么就要开始处理读事件
                        beginRead();
                    }
                }
            } catch (Throwable t) {
                // 报错处理
                closeForcibly();
                closeFuture.setClosed();
                safeSetFailure(promise, t);
            }
        }

整个register0 代码逻辑在上面都有了注释,主要包括:

  1. 调用java nio底层进行selector与channel注册。
  2. 调用 pipeline.invokeHandlerAddedIfNeeded() 执行channelAdd方法。
  3. 调用 pipeline.fireChannelRegistered() 执行 channelRegistered 方法。
  4. 调用 pipeline.fireChannelActive() 执行 channelActive 方法。
pipeline.invokeHandlerAddedIfNeeded()

pipeline.invokeHandlerAddedIfNeeded() 目前在netty中,主要是链式调用 handlerAdded 方法。

    private void callHandlerAddedForAllHandlers() {
        final PendingHandlerCallback pendingHandlerCallbackHead;
        synchronized (this) {
            assert !registered;
            registered = true;
            pendingHandlerCallbackHead = this.pendingHandlerCallbackHead;
            // 每次调用完,会被清除
            this.pendingHandlerCallbackHead = null;
        }

        // 链式执行,并且放在synchronized外面,防止一直持有锁
        PendingHandlerCallback task = pendingHandlerCallbackHead;
        while (task != null) {
            task.execute();
            task = task.next;
        }
    }

加入时机则是在每个ChannelHandler 的增加或者删除时机:
DefaultChannelPipelinecallHandlerCallbackLater 方法:
在这里插入图片描述
这样当调用添加或者删除时,则会通过 ChannelHandlerContext 进行链式调用 handlerAdded 或者 handlerRemoved 方法。

pipeline.fireChannelRegistered() 和 pipeline.fireChannelActive()

这两个方法调用逻辑其实是类似的,通过Pipeline进行链式handler调用,以 pipeline.fireChannelRegistered 为例:
DefaultChannelPipeline

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

此处的 head 则是表示 DefaultChannelPipeline 的头结点。
AbstractChannelHandlerContext

    static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeChannelRegistered();
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRegistered();
                }
            });
        }
    }

再到 invokeChannelRegistered 方法:

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

从上面 invokeChannelRegistered 可知:

  1. 通过invokeHandler 方法, 只会对已加入,并且执行完 handlerAdded 方法的channelHandler进行操作。
  2. 会捕获所有Throwable的异常,并进行通知。
  3. 然后是否往后面传递,取决于代码中是否显示调用 fireChannelRegistered 方法。

doBind0

从doBind方法中可以了解,只有注册成功后,才能执行doBind0方法,否则加一个register的监听器进行操作:
在这里插入图片描述
AbstractBootstrapdoBind0 开始:

    private static void doBind0(
            final ChannelFuture regFuture, final Channel channel,
            final SocketAddress localAddress, final ChannelPromise promise) {
        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());
                }
            }
        });
    }

注册成功后,执行 channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE)
AbstractChannelbind 方法:

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

从上面代码可以猜出大概,就是通过DefaultChannelPipeline 将绑定时间传递出去。
DefaultChannelPipelinebind方法:

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

执行tailbind方法。
AbstractChannelHandlerContextbind方法:

    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        }
        if (isNotValidPromise(promise, false)) {
            return promise;
        }
        // 找到一个有bind事件的ChannelHandlerContext
        final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND);
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeBind(localAddress, promise);
        } else {
            safeExecute(executor, new Runnable() {
                @Override
                public void run() {
                    next.invokeBind(localAddress, promise);
                }
            }, promise, null);
        }
        return promise;
    }

最终找到了 DefaultChannelPipeline$HeadContextinvokeBind方法,实际上是父类 AbstractChannelHandlerContext 的方法:

    private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
        if (invokeHandler()) {
            try {
                ((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
            } catch (Throwable t) {
                notifyOutboundHandlerException(t, promise);
            }
        } else {
            bind(localAddress, promise);
        }
    }

同样类似的写法,最终进入到 HeadContextbind方法:

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

由于是server端,所以此处unsafeio.netty.channel.nio.AbstractNioMessageChannel$NioMessageUnsafe

NioMessageUnsafe 的 bind方法

NioMessageUnsafebind方法 整体保持和register一样的写法,前面一些判断后,直接进入了doBind方法中:

        @Override
        public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
            assertEventLoop();
            if (!promise.setUncancellable() || !ensureOpen(promise)) {
                return;
            }
			。。。 

            boolean wasActive = isActive();
            try {
                doBind(localAddress);
            } catch (Throwable t) {
                safeSetFailure(promise, t);
                closeIfClosed();
                return;
            }
            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }

            safeSetSuccess(promise);
        }

最终仍然是调用nio,将channel与地址绑定起来:

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

总结

本文主要细致了解了netty在doBind方法中两个细节:

  1. channel和selector注册逻辑,以及事件传播逻辑。
  2. channel在注册成功后,与ip地址绑定逻辑。

关注博主公众号: 六点A君。
哈哈哈,一起研究Netty:
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值