Netty源码解析(二)之服务器启动源码

在上一篇文章中,我们对Netty启动的流程做了一个概述

Netty源码解析(一)之Netty启动流程(4.1.47.Final)_benjam1n77的博客-CSDN博客

这篇文章中,我将从源码出发,详细解析Netty服务器启动流程。(为了简洁,只贴出关键的源码部分)

一. ServerSocketChannel的初始化和注册

进入到该方法

ServerBootstrp.bind(int inetPort)

然后会进入AbstractBootstrap.doBind()方法中。

//AbstractBootstrap.java 
//line 271-305
private ChannelFuture doBind(final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }

    if (regFuture.isDone()) {
        // At this point we know that the registration was complete and successful.
        ChannelPromise promise = channel.newPromise();
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        // Registration future is almost always fulfilled already, but just in case it's not.
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
        regFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                Throwable cause = future.cause();
                if (cause != null) {
                    // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                    // IllegalStateException once we try to access the EventLoop of the Channel.
                    promise.setFailure(cause);
                } else {
                    // Registration was successful, so set the correct executor to use.
                    // See https://github.com/netty/netty/issues/2586
                    promise.registered();

                    doBind0(regFuture, channel, localAddress, promise);
                }
            }
        });
        return promise;
    }
}

这一节先讲ServerSocketChannel的初始化和注册,该逻辑在AbstractBootstrap.initAndRegister()方法中。首先channelFactory.newChannel()会通过反射创建一个NioServerSocketChannel实例,然后调用init(channel)进行初始化。初始化主要做了以下几件事情:

  1. 为ServerSocketChannel设置channelOption和attributes,这个channelOption和attributes是用户自己设置的。
  2. 为ServerSocketChannel添加一个ChannelInitializer,该Initializer会为ServerSocketChannel添加一个类型为ServerBootstrapAcceptor的Handler。
//AbstractBootstrap.java
//line 307-342
final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        channel = channelFactory.newChannel();
        init(channel);
    } 

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

    return regFuture;
}

//ServerBootstrap.java
//line 131-163
void init(Channel channel) {
    setChannelOptions(channel, newOptionsArray(), logger);
    setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));

    ChannelPipeline p = channel.pipeline();

    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Map.Entry<ChannelOption<?>, Object>[] currentChildOptions;
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
    }
    final Map.Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);

    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = config.handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }

            ch.eventLoop().execute(new Runnable() {
                @Override
                public void run() {
                    pipeline.addLast(new ServerBootstrapAcceptor(
                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

初始化完成后,进行ServerSocketChannel的注册。注册过程主要分为一下几步:

  1. 调用doRegister()方法,在该方法中终于见到了原生JDK NIO的方法调用了,将NioServerSocketChannel的java channel注册到Selector上,并将感兴趣的事件设置为0,即对任何IO事件都不感兴趣(在后续的过程中才会将interested ops设置为OP_ACCEPT)。
  2. NioServerSocketChannel的ChannelPipeline会触发一个handlerAdded事件,此时在上一步的初始化过程中所添加的ChannelInitializer的initChannel()方法会被触发,该方法会在ChannelPipeline中再添加一个名为ServerBootstrapAcceptor的Handler。
  3. ChannelPipeline再触发一个ChannelRegistered事件,这一步如果用户没有为NioServerSocketChannel添加额外的Handler的话则不会发生任何事情。
  4. 注意:在下面的代码中isActive()方法返回的是false,因为此时Channel只进行了初始化和注册,还未绑定端口,处于未激活状态,因此条件判断代码块中的pipeline.fireChannelActive()和beginRead()都不会被执行。
//AbstractChannel.java
//line 488-525
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);
    }
}

//AbstractNioChannel.java
//line 376-395
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;
            }
        }
    }
}

二. ServerSocketChannel的绑定

ServerSocketChannel完成初始化和注册后,进行端口的绑定,主要分为以下几步:

  1. 调用doBind()方法,通过原生的JDK NIO将java channel绑定到特定的端口上。
  2. ServerSocketChannel的ChannelPipeline触发一个channel active事件。
//AbstractChannel.java
//line 528-567
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    assertEventLoop();

    if (!promise.setUncancellable() || !ensureOpen(promise)) {
        return;
    }

    // See: https://github.com/netty/netty/issues/576
    if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
            localAddress instanceof InetSocketAddress &&
            !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
            !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {
        // 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);
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        closeIfClosed();
        return;
    }

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

    safeSetSuccess(promise);
}


//NioServerSocketChannel.java
//line 132-138
protected void doBind(SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        javaChannel().bind(localAddress, config.getBacklog());
    } else {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}

毫无疑问,上述过程中的第2步触发的channel active事件会传递给NioServerSocketChannel的Handler,此时NioServerSocketChannel有三个Handler:head->ServerBootstrapAcceptor->tail,ServerBootstrapAcceptor以及tail这两个handler的channelActive()方法都不会做任何事情,主要的逻辑在headHandler的channelActive()方法中,而这个方法最后是会调用到AbstractNioChannel的doBeginRead()方法,在该方法中,最终会将NioServerSocketChannel注册到Selector的感兴趣的事件设置为readInterestOp,即SelectionKey.OP_ACCEPT。

//DefaultChannelPipeline.java
//line 1397-1401
public void channelActive(ChannelHandlerContext ctx) {
    ctx.fireChannelActive();

    readIfIsAutoRead();
}


//DefaultChannelPipeline.java
//line 1420-1424
private void readIfIsAutoRead() {
    if (channel.config().isAutoRead()) {
        channel.read();
    }
}


//AbstractNioChannel.java
//line 403-416
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);
    }
}

至此,NioServerSocketChannel就可以真正的开始接收客户端连接了。有小伙伴可能会好奇,在原生的JDK NIO中,服务器接收客户端连接时,应该是要在一个死循环中调用selector.select()才能接收到客户端的连接啊,怎么在Netty中看不到这一段代码?别急,其实这段代码在Netty中是有的,还记得我们的bossEventLoopGroup吗?这个EventLoopGroup中的线程在开启后就会在一个死循环中不停的调用selector.select()来查看是否有新的客户端连接,正如在上一篇文章中的这张图所示:

 关于EventLoopGroup的详细的工作原理我将会在后续专门写一篇文章来介绍。

猜你喜欢

转载自blog.csdn.net/benjam1n77/article/details/122731774