NETTY入口--Bootstrap

netty作為客戶端從bootstrap啟動,作為服務(wù)端從ServerBootstrap,本文默認(rèn)傳輸層協(xié)議為TCP協(xié)議。

UML圖

bootstrap.png

如上圖所示,Bootstrap和ServerBoostrap都繼承自AbstractBootstrap.

Bootstrap

Bootstrap用于一個客戶端連接服務(wù)器,獲取一個channel,代碼如下

EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc(), HOST, PORT));
                     }
                     p.addLast(new DiscardClientHandler());
                 }
             });
            // Make the connection attempt.
            ChannelFuture f = b.connect(HOST, PORT).sync();
            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }

bootstrap的核心代碼是connect方法如下:

/**
     * Connect a {@link Channel} to the remote peer.
     */
    public ChannelFuture connect(SocketAddress remoteAddress) {
        if (remoteAddress == null) {
            throw new NullPointerException("remoteAddress");
        }

        validate();
        return doResolveAndConnect(remoteAddress, config.localAddress());
    }

doResolveAndConnect方法如下:

private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        // 首先創(chuàng)建并初始化并注冊一個channel
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
       // 如果注冊完成,channel已經(jīng)找到了自己的EventLoop/excutor,則直接調(diào)用
       // doResolveAndConnect0解析域名和連接服務(wù)器
        if (regFuture.isDone()) {
            if (!regFuture.isSuccess()) {
                return regFuture;
            }
            return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
        } else {
            // 如果沒有注冊完成,則為注冊future增加一個listener,但是有個問題:
            // 返回的connectFuture怎么辦?由于channel現(xiàn)在不一定有excutor,
            // 所以你不能用channel.newPromise()去新建一個promise, 所以,就誕生了
            // PendingRegistrationPromise, Future/listern模式我們后面會專門研究
            // 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 {
                    // Direclty obtain the cause and do a null check so we only need one volatile read in case of a
                    // failure.
                    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();
                        doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

PendingRegistrationPromise 到底是什么鬼?其實就干一件事:如果注冊成功,其執(zhí)行線程是channel的執(zhí)行線程,如果注冊失敗,執(zhí)行線程是一個全局的執(zhí)行線程。

static final class PendingRegistrationPromise extends DefaultChannelPromise {
        // Is set to the correct EventExecutor once the registration was successful. Otherwise it will
        // stay null and so the GlobalEventExecutor.INSTANCE will be used for notifications.
        private volatile boolean registered;

        PendingRegistrationPromise(Channel channel) {
            super(channel);
        }

        void registered() {
            registered = true;
        }

        @Override
        protected EventExecutor executor() {
           // 如果注冊完成了, 這個listener就是就是channel的enventLoop
            if (registered) {
                // If the registration was a success executor is set.
                //
                // See https://github.com/netty/netty/issues/2586
                return super.executor();
            }
            // 否則就是全局的一個線程
            // The registration failed so we can only use the GlobalEventExecutor as last resort to notify.
            return GlobalEventExecutor.INSTANCE;
        }
    }

initAndRegister()方法在AbstractBootstrap中

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            // 首先產(chǎn)生一個channel
            channel = channelFactory.newChannel();
           // 初始化, 該方法在bootstrap中,這個方法里面就是為channel設(shè)置一些可選項和屬性
            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);
        }
        // 注冊channel
        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;
    }

doResolveAndConnect0()方法解析遠(yuǎn)程域名并發(fā)起連接

private ChannelFuture doResolveAndConnect0(final Channel channel, SocketAddress remoteAddress,
                                               final SocketAddress localAddress, final ChannelPromise promise) {
        try {
            final EventLoop eventLoop = channel.eventLoop();
            final AddressResolver<SocketAddress> resolver = this.resolver.getResolver(eventLoop);
            // 如果不支持解析,或已經(jīng)解析,直接連接
            if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
                // Resolver has no idea about what to do with the specified remote address or it's resolved already.
                doConnect(remoteAddress, localAddress, promise);
                return promise;
            }
           // 如果沒有解析,則現(xiàn)在解析
            final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
          //解析完成,則馬上解析
            if (resolveFuture.isDone()) {
                final Throwable resolveFailureCause = resolveFuture.cause();
                if (resolveFailureCause != null) {
                    // Failed to resolve immediately
                    channel.close();
                    promise.setFailure(resolveFailureCause);
                } else {
                    // Succeeded to resolve immediately; cached? (or did a blocking lookup)
                    doConnect(resolveFuture.getNow(), localAddress, promise);
                }
                return promise;
            }
 
            // Wait until the name resolution is finished.
            // 解析沒有完成,則放在listener中連接
            resolveFuture.addListener(new FutureListener<SocketAddress>() {
                @Override
                public void operationComplete(Future<SocketAddress> future) throws Exception {
                    if (future.cause() != null) {
                        channel.close();
                        promise.setFailure(future.cause());
                    } else {
                        doConnect(future.getNow(), localAddress, promise);
                    }
                }
            });
        } catch (Throwable cause) {
            promise.tryFailure(cause);
        }
        return promise;
    }

doConnect方法負(fù)責(zé)連接遠(yuǎn)程服務(wù)器,調(diào)用channel的connect方法完成,channel.connect()方法負(fù)責(zé)調(diào)用java的connetc方法。

private static void doConnect(
            final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise connectPromise) {

        // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up
        // the pipeline in its channelRegistered() implementation.
        final Channel channel = connectPromise.channel();
        channel.eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                if (localAddress == null) {
                    channel.connect(remoteAddress, connectPromise);
                } else {
                    channel.connect(remoteAddress, localAddress, connectPromise);
                }
                connectPromise.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            }
        });
    }

總結(jié),Bootstrap中的方法風(fēng)味兩類:設(shè)置屬性和連接遠(yuǎn)程服務(wù)器,連接遠(yuǎn)程服務(wù)器的底層調(diào)用channel的連接,所以本質(zhì)上連接操作是在channel里面做的,bootstrap中的連接過程如下:
1: 創(chuàng)建一個channel
2: 初始化一個channel
3: 注冊一個channel到一個eventLoop中去,
4: 解析遠(yuǎn)程域名,但是未必能夠正確解析
5: 調(diào)用channel.connect連接遠(yuǎn)程服務(wù)器

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

推薦閱讀更多精彩內(nèi)容