netty5.0之server端NioServerSocketChannel的bind分析

本文详细解析了Netty 5.0中bind操作的触发机制、准备工作、handler处理流程及channel绑定过程。从源码层面深入探讨了bind作为任务的加入与执行、handler链的构建与执行次序、以及bind操作如何触发accept注册。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇我们说到dobind作为一个task加入taskQueue等待SingleThreadventExecutor执行,那么它是如何触发的呢?bind准备工作对channelpipeline和channelHandler做了哪些,handler链含有哪些元素呢?channel进行bind过程中对handler的执行次序是什么呢?handler链在fireChannelActive中handler都做了哪些?本文一一进行揭示,有不当之处欢迎拍砖。

一、bind的触发

在上一篇《netty5.0之server端NioServerSocketChannel的init和register流程》的Fig1中我们看到有dobind方法,并留下疑问doBind在何时被调用呢。下面我们看Fig1.1,该图同时说明了前面我们提到的:SingleThreadventExecutor的run方法,NioEventLoop的run方法的作用通过runAllTasks就是运行taskQueue的每个任务,一个任务就是channel的bind。Fig1的line:247所在的类方法(operationComplete)会回调用上一篇Fig 1中的dobind方法。那么是在何处触发的呢。


Fig 1.1

在上一篇Fig 2中,eventloop执行的runnable中调用了了register0,即本篇中line:426。在AbstractChannel的这个方法做了什么呢?

 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 (!ensureOpen(promise)) {
                    return;
                }
                doRegister();//tag1.1
                registered = true;
                promise.setSuccess();//tag1.2
                pipeline.fireChannelRegistered();//tag1.3
                if (isActive()) {
                    pipeline.fireChannelActive();
                }
            } catch (Throwable t) {
                // Close the channel directly to avoid FD leak.
                closeForcibly();
                closeFuture.setClosed();
                if (!promise.tryFailure(t)) {
                    logger.warn(
                            "Tried to fail the registration promise, but it is complete already. " +
                                    "Swallowing the cause of the registration failure:", t);
                }
            }
        }
tag1.1 中进行了selector注册
@Override
    protected void doRegister() throws Exception {
        boolean selected = false;
        for (;;) {
            try {
                selectionKey = javaChannel().register(eventLoop().selector, 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;
                }
            }
        }
    }

tag 1.2 

从Fig 1.2中我们可以看到setSuccess后是DefaultChannelPromise在做事,notify listeners进行调用。就是Fig1中的operationComplete。


Fig 1.2

二、bind的准备工作

Fig 3表明进入了doBind流程。


Fig 3

在AbstractBootStrap的doBind0方法需要学习两点:

1、把bind作为一个任务加入eventloop中共SingleThreadEventExecutor调用

2、run()中被执行,直到channelRegistered()被触发。

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

下面就学习tag1.3

(这部分的学习参考:http://my.oschina.net/geecoodeer/blog/193237)

DefaultChannelPipelineChannelPipeline的实现类,DefaultChannelPipeline内部维护了两个指针:final DefaultChannelHandlerContext head; final DefaultChannelHandlerContext tail;,分别指向链表的头部和尾部;而DefaultChannelHandlerContext内部是一个链表结构:volatile DefaultChannelHandlerContext next;volatile DefaultChannelHandlerContext prev;,而每个DefaultChannelHandlerContextChannelHandler实例一一对应。

从上面可以看到,这是个经典的Intercepting Filter模式实现。下面我们再接着从tag1.3代码看起,pipeline.fireChannelRegistered();依次执行如下两个方法。此时handler链是HeadHandler,ServerBootstrap$1和TailHandler。

tag1.3.0:

DefaultChannelPipeline:

 @Override
    public ChannelPipeline fireChannelRegistered() {
        head.fireChannelRegistered();
        return this;
    }
DefaultChannelHandlerContext:
@Override
    public ChannelHandlerContext fireChannelRegistered() {
        DefaultChannelHandlerContext next = findContextInbound(MASK_CHANNEL_REGISTERED);
        next.invoker.invokeChannelRegistered(next);
        return this;
    }
DefaultChannelHandlerInvoker:
@Override
    public void invokeChannelRegistered(final ChannelHandlerContext ctx) {
        if (executor.inEventLoop()) {
            invokeChannelRegisteredNow(ctx);
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    invokeChannelRegisteredNow(ctx);
                }
            });
        }
    }
    @Override
    @SuppressWarnings("unchecked")
    public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        ChannelPipeline pipeline = ctx.pipeline();
        boolean success = false;
        try {
            initChannel((C) ctx.channel());//tag1.3.1
            pipeline.remove(this);//tag1.3.2
            ctx.fireChannelRegistered();//tag1.3.3
            success = true;
        } catch (Throwable t) {
            logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), t);
        } finally {
            if (pipeline.context(this) != null) {
                pipeline.remove(this);
            }
            if (!success) {
                ctx.close();
            }
        }
    }

 tag1.3.1:由于ServerBootstrap(ChannelInitializer<C>)这个类继承了ChannelInitializer,所以会执行了ChannelInitializer.channelRegistered这个方法。下面的initChannel回调了initChannel
(回答了:上一篇文章ChannelInitializer的initChannel方式是在何处被调用。该方法把ServerBootstrapAcceptor这个Handler加入到Pipeline中;此时handler链情况如下:HeadHandler,ServerBootstrap$1, ServerBootstrap$ServerBootstrapAcceptor和TailHandler。

再回过头看看ServerBootStrap中的这块代码,其实进行了两个addlast。第一个add ServerBootstrap$1,第二个add ServerBootstrapAcceptor。

p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(Channel ch) throws Exception {
                ch.pipeline().addLast(new ServerBootstrapAcceptor(currentChildHandler, currentChildOptions,
                        currentChildAttrs));
            }
        }
对于二次add,下面代码处debug见图
@Override
    public ChannelPipeline addLast(ChannelHandlerInvoker invoker, final String name, ChannelHandler handler) {
        synchronized (this) {
            checkDuplicateName(name);

            DefaultChannelHandlerContext newCtx =
                    new DefaultChannelHandlerContext(this, invoker, name, handler);

            addLast0(name, newCtx);
        }

        return this;
    }





tag1.3.2 通过执行pipeline.remove(this);又把ServerBootstrap$1这个Handler给删除了,从而完成初始化的效果。需要提醒的是,

ServerBootstrapAcceptor的currentChildHandler属性包含了在客户端代码注册的TelnetServerInitializer类。

在tag1.3.3里,通过执行ctx.fireChannelRegistered();又找到了下一个handler。

这段逻辑和上述tag1.3.0基本一样, findContextInbound内部执行时,会跳过ServerBootstrapAcceptor这个handler,最终找到找到tailHandler,并执行channelRegistered()这个方法。就这样,最终完成了整个 pipeline.fireChannelRegistered();执行。此时满足regfuture.success=true,于是执行AbstractBootStrap的doBind0方法进行绑定。

三、bind之handler处理

netty把channel的bind作为一个task,下图的层层调用表明了bind的过程:

1、AbstractChannel使用私有成员pipeline进行bind;见line:189

2、DefaultChannelPipeline调用tailHandler进行bind;

3、DefaultChannelHandlerContext调用,期间findContextOutbound 使用pre找到HeadHandler。

4、一直到HeadHander调用bind。


AbstractChannel:

@Override
    public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
        return pipeline.bind(localAddress, promise);
    }
DefaultChannelPipeline:
@Override
    public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
        return tail.bind(localAddress, promise);
    }
DefaultChannelHandlerContext
@Override
    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        DefaultChannelHandlerContext next = findContextOutbound(MASK_BIND);
        next.invoker.invokeBind(next, localAddress, promise);
        return promise;
    }

DefaultChannelHandlerInvoker
@Override
    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        DefaultChannelHandlerContext next = findContextOutbound(MASK_BIND);
        next.invoker.invokeBind(next, localAddress, promise);
        return promise;
    }
ChannelHandlerInvokerUtil:
public static void invokeBindNow(
            final ChannelHandlerContext ctx, final SocketAddress localAddress, final ChannelPromise promise) {
        try {
            ctx.handler().bind(ctx, localAddress, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    }
HeadHandler:
@Override
        public void bind(
                ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
                throws Exception {
            unsafe.bind(localAddress, promise);
        }

四、bind之channel

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

            // See: https://github.com/netty/netty/issues/576
            if (!PlatformDependent.isWindows() && !PlatformDependent.isRoot() &&
                Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
                localAddress instanceof InetSocketAddress &&
                !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress()) {
                // Warn a user about the fact that a non-root user can't receive a
                // broadcast packet on *nix if the socket is bound on non-wildcard address.
                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 = isActive();
            try {
                doBind(localAddress);//tag4.1
            } catch (Throwable t) {
                promise.setFailure(t);
                closeIfClosed();
                return;
            }
            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {//tag4.2
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();//tag4.3
                    }
                });
            }
            promise.setSuccess();//tag4.4
        }
tag4.1执行真正的bind端口:NioSocketSeverChannel

 @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
tag4.2 执行如下方法,eventLoop().execute(task); `在后续分析。现在暂时忽略。

tag4.4

在 tag4.4.1设置了成功状态,然后该方法返回,继续执行了tag4.4.2方法。由于listeners为 null,所以直接返回。此时执行tag4.3的代码

@Override
    public ChannelPromise setSuccess() {
        return setSuccess(null);
    }

@Override
    public ChannelPromise setSuccess(Void result) {
        super.setSuccess(result);
        return this;
    }

@Override
    public Promise<V> setSuccess(V result) {
        if (setSuccess0(result)) {
            notifyListeners();//tag4.4.2
            return this;
        }
        throw new IllegalStateException("complete already: " + this);
    }

private boolean setSuccess0(V result) {
        if (isDone()) {
            return false;
        }

        synchronized (this) {
            // Allow only once.
            if (isDone()) {
                return false;
            }
            if (result == null) {
                this.result = SUCCESS;//tag4.4.1
            } else {
                this.result = result;
            }
            if (hasWaiters()) {
                notifyAll();
            }
        }
        return true;
    }
tag4.3 见五

五 bind之ACCEPT注册

                                                                   Fig 5.1

上述逻辑一样,最终执行到TailHandler这里,TailHandler没做什么,该方法没被override。HeadHandler做了acceptor的注册监听。

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

        @Override
        public void AbstractChannel.AbstractUnsafebeginRead() {
            if (!isActive()) {
                return;
            }

            try {
                doBeginRead();
            } catch (final Exception e) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireExceptionCaught(e);
                    }
                });
                close(voidPromise());
            }
        }
调用了AbstractNioChannel override的doBeginRead
@Override
    protected void doBeginRead() throws Exception {
        if (inputShutdown) {
            return;
        }

        final SelectionKey selectionKey = this.selectionKey;
        if (!selectionKey.isValid()) {
            return;
        }

        final int interestOps = selectionKey.interestOps();
        if ((interestOps & readInterestOp) == 0) {
            selectionKey.interestOps(interestOps | readInterestOp);
        }
    }
至此,整个DefaultPromise.bind方法执行完毕,下面开始执行DefaultPromise.sync()。而此时在 tag4.4.已经将值设为SUCCESS了,所以不需要等待,直接返回。
然后系统接着执行了 b.bind(port).sync().channel().closeFuture().sync();的后半截方法“channel().closeFuture().sync()”方法。而由于closeFuture这个属性的执行结果一直没有赋值,所以被wait了,从而一直处于wait状态(Fig5.1 的line:45 run方法就是)。至此,主线程处于wait状态,并通过子线程无限循环,来完成客户端请求。



评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值