Netty源碼愫讀(六)ServerBootstrap相關源碼學習

BootStrap在netty的應用程序中負責引導服務器和客戶端。netty包含了兩種不同類型的引導:

  • 使用服務器的ServerBootStrap,用于接受客戶端的連接以及為已接受的連接創建子通道。
  • 用于客戶端的BootStrap,不接受新的連接,并且是在父通道類完成一些操作。

本文主要分析ServerBootstarp相關源碼。

Bootstrap類繼承圖:

Bootstrap類繼承圖.png

1、ServerBootstarap啟動流程分析

1.1、ServerBootstarap簡單啟動代碼

EventLoopGroup bossGroup = new NioEventLoopGroup(1);//用于接受accept事件的group serverSocketChannel 
EventLoopGroup group = new NioEventLoopGroup();  //用于真正讀寫事件的group socketChannel
try {
    //create ServerBootstrap instance  
    ServerBootstrap b = new ServerBootstrap();  //啟動裝載器,用于轉載配置
    //Specifies NIO transport, local socket address  
    //Adds handler to channel pipeline  
    //使用NIO通道                                   
    //保持連接,如果不設置則一次通訊后自動斷開
    b.group(bossGroup, group).channel(NioServerSocketChannel.class).option(ChannelOption.SO_KEEPALIVE, true).localAddress(port)
            .handler(new SimpleServerHandler())//服務端handler
            .childHandler(new ChannelInitializer<Channel>() {  //channel使用的handler
                @Override
                protected void initChannel(Channel ch) throws Exception {
                    ch.pipeline().addLast(new EchoServerHandler2(), new EchoServerHandler());
                }
            });
    //Binds server, waits for server to close, and releases resources  
    ChannelFuture f = b.bind().sync();
    System.out.println(EchoServer.class.getName() + "started and listen on ?" + f.channel().localAddress());
    f.channel().closeFuture().sync();
} finally {
    group.shutdownGracefully().sync();
}

主要處理說明:

(1)、新建NioEventLoopGroup類型的bossGroup和group。bossGroup主要處理服務端接收客戶端連接處理,group主要處理讀寫等I/O事件及任務等;
(2)、創建ServerBootstrap,其主要對一些處理進行代理,如bind()等操作,其是其他類的一個簡單門面;
(3)、channel()方法設置服務端的ServerSocketChannel實現類,本處實現類為NioServerSocketChannel。
(4)、option()方法設置Channel的相關選項,具體查看ChannelOption中的定義;
(5)、localAddress()設置服務端綁定的本地地址及端口;
(6)、handler()設置服務端的對應Channel的Handler;
(7)、childHandler()設置子連接的Channel的Handler;
(8)、bind()及sync()綁定本地地址并同步返回綁定結果;

1.2、bind()調用流程

bind()調用流程圖:

bind()調用流程圖.png

(1)、調用ServerBootstrap.bind():應用調用ServerBootstrap的bind()操作;
(2)、調用AbstractBootstrap.bind():調用doBind()對進行bind操作;
(3)、調用AbstractBootstrap.initAndRegister():利用ChannelFactory.newChannel()實例化NioServerSocketChannel;
(4)、調用ServerBootstrap.init():對NioServerSocketChannel進行初始化,主要操作如設置Channel相關的選項及屬性、設置ChannelHandler為ServerBootstrapAcceptor等,ServerBootstrapAcceptor為inbound類型的ChannelHandler,其為ServerBootstrap的內部類,其主要實現ChannelRead()操作,將客戶端的連接注冊到EventLoopGroup的EventLoop中。
(5)、調用NioEventLoop.register():將NioServerSocketChannel注冊到bossGroup中。
(6)、調用AbstractBootstrap.doBind0:將實際的bind操作以任務的形式添加到bossGroup的EventLoop中。
(7)、調用NioServerSocketChannel.bind():在EventLoop中以任務的形式調用此方法進行實際的bind()操作。

2、主要方法源碼分析

2.1、doBind()源碼分析

doBind()源碼:

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

主要流程處理:

  • 調用initAndRegister()初始化Channel并將其注冊到bossGroup中的NioEventLoop中;
  • 若注冊成功,則調用doBind0()進行實際的bind操作;
  • 若還未注冊,則創建注冊結果的監聽器及doBind0()的異步結果,若Channel注冊成功,則在結果監聽器中進行doBind0()操作,并將bind()異步結果這種為成功;否則將在監聽器中設置異步結果為失??;

2.2、initAndRegister()源碼分析

initAndRegister()源碼:

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        channel = channelFactory.newChannel();
        init(channel);
    } catch (Throwable t) {
        if (channel != null) {
            // channel can be null if newChannel crashed (eg SocketException("too many open files"))
            channel.unsafe().closeForcibly();
            // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
            return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor
        return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
    }

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

    // If we are here and the promise is not failed, it's one of the following cases:
    // 1) If we attempted registration from the event loop, the registration has been completed at this point.
    //    i.e. It's safe to attempt bind() or connect() now because the channel has been registered.
    // 2) If we attempted registration from the other thread, the registration request has been successfully
    //    added to the event loop's task queue for later execution.
    //    i.e. It's safe to attempt bind() or connect() now:
    //         because bind() or connect() will be executed *after* the scheduled registration task is executed
    //         because register(), bind(), and connect() are all bound to the same thread.

    return regFuture;
}

主要處理流程:

  • 通過ChannelFactory新創建一個Channel;
  • 調用ServerBootstrap的init()方法對Channel進行初始化;

2.3、init()源碼分析

init()源碼:

void init(Channel channel) throws Exception {
    final Map<ChannelOption<?>, Object> options = options0();
    synchronized (options) {
        setChannelOptions(channel, options, logger);
    }

    final Map<AttributeKey<?>, Object> attrs = attrs0();
    synchronized (attrs) {
        for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
            @SuppressWarnings("unchecked")
            AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
            channel.attr(key).set(e.getValue());
        }
    }

    ChannelPipeline p = channel.pipeline();

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

    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) throws Exception {
            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));
                }
            });
        }
    });
}

主要處理流程:

  • 如果設置了Channel選項,則調用setChannelOptions()對Channel進行選項設置;
  • 如果設置了屬性,則將對應屬性設置為Channel的屬性;
  • 設置子Channel的選項及屬性;
  • 初始化NioServerSocketChannel的ChannelHandler為ServerBootstrapAcceptor,ServerBootstrapAcceptor為inbound類型的ChannelHandler,其主要功能是將已經接受連接的子Channel注冊到workerGroup的NioEventLoop中;

2.4、doBind0()源碼分析

doBind0()源碼:

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

主要處理流程:

  • 將NioServerSocketChannel.bind()操作封裝為任務,并將任務提交給其對應的EventLoop進行處理;

2.5、ServerBootstrapAcceptor源碼分析

2.5.1、ServerBootstrapAcceptor類繼承圖:

ServerBootstrapAcceptor類繼承圖.png

ServerBootstrapAcceptor為NioServerSocketChannel的ChannelHandler,其類型為Inbound類型;

2.5.2、ServerBootstrapAcceptor源碼:

private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {

    private final EventLoopGroup childGroup;
    private final ChannelHandler childHandler;
    private final Entry<ChannelOption<?>, Object>[] childOptions;
    private final Entry<AttributeKey<?>, Object>[] childAttrs;
    private final Runnable enableAutoReadTask;

    ServerBootstrapAcceptor(
            final Channel channel, EventLoopGroup childGroup, ChannelHandler childHandler,
            Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {
        this.childGroup = childGroup;
        this.childHandler = childHandler;
        this.childOptions = childOptions;
        this.childAttrs = childAttrs;

        // Task which is scheduled to re-enable auto-read.
        // It's important to create this Runnable before we try to submit it as otherwise the URLClassLoader may
        // not be able to load the class because of the file limit it already reached.
        //
        // See https://github.com/netty/netty/issues/1328
        enableAutoReadTask = new Runnable() {
            @Override
            public void run() {
                channel.config().setAutoRead(true);
            }
        };
    }

    @Override
    @SuppressWarnings("unchecked")
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        final Channel child = (Channel) msg;

        child.pipeline().addLast(childHandler);

        setChannelOptions(child, childOptions, logger);

        for (Entry<AttributeKey<?>, Object> e: childAttrs) {
            child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
        }

        try {
            childGroup.register(child).addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (!future.isSuccess()) {
                        forceClose(child, future.cause());
                    }
                }
            });
        } catch (Throwable t) {
            forceClose(child, t);
        }
    }

    private static void forceClose(Channel child, Throwable t) {
        child.unsafe().closeForcibly();
        logger.warn("Failed to register an accepted channel: {}", child, t);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        final ChannelConfig config = ctx.channel().config();
        if (config.isAutoRead()) {
            // stop accept new connections for 1 second to allow the channel to recover
            // See https://github.com/netty/netty/issues/1328
            config.setAutoRead(false);
            ctx.channel().eventLoop().schedule(enableAutoReadTask, 1, TimeUnit.SECONDS);
        }
        // still let the exceptionCaught event flow through the pipeline to give the user
        // a chance to do something with it
        ctx.fireExceptionCaught(cause);
    }
}

ServerBootstrapAcceptor主要實現了以下方法:
(1)、channelRead():設置子連接的ChannelHandler、設置子連接的Channel選項,設置子連接的Channel屬性,將子連接注冊的child對應的EventLoop中(即workerGroup的EventLoop中);
(2)、exceptionCaught():若ServerSocketChannel在accept子連接時拋出異常,若ServerSocketChannel的autoRead為true,則設置其為false,即不允許自動接收客戶端連接,并延遲1s后再設置其為true,使其允許自動接收客戶端連接;

相關閱讀:
Netty源碼愫讀(一)ByteBuf相關源碼學習 【http://www.lxweimin.com/p/016daa404957
Netty源碼愫讀(二)Channel相關源碼學習【http://www.lxweimin.com/p/02eac974258e
Netty源碼愫讀(三)ChannelPipeline、ChannelHandlerContext相關源碼學習【http://www.lxweimin.com/p/be82d0fcdbcc
Netty源碼愫讀(四)ChannelHandler相關源碼學習【http://www.lxweimin.com/p/6ee0a3b9d73a
Netty源碼愫讀(五)EventLoop與EventLoopGroup相關源碼學習【http://www.lxweimin.com/p/05096995d296

參考書籍:

《Netty權威指南》第二版

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容