Netty源碼分析(三):客戶端啓動

Bootstrap

Bootstrap主要包含兩個部分,一個是服務器地址的解析器組AddressResolverGroup,另外一個是用來工做的EventLoopGroupEventLoopGroup負責出人EventLoopAddressResolverGroup負責給EventLoop解析服務器地址。java

客戶端鏈接遠程服務器

鏈接遠程服務器,會先check引導類(Bootstrap)的group有沒有設置以及生成channel的工廠,以後再調用doResolveAndConnect方法。git

private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
        // 初始化並註冊一個channel,並將chanelFuture返回
        final ChannelFuture regFuture = initAndRegister();
        // 獲得實際的channel(初始化和註冊的動做可能還沒有完成)
        final Channel channel = regFuture.channel();
        // 當到這chanel相關處理已經完成時
        if (regFuture.isDone()) {
            // 鏈接失敗直接返回
            if (!regFuture.isSuccess()) {
                return regFuture;
            }
            // 解析服務器地址並完成鏈接動做
            return doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
        } else {
            // 註冊通常到這就已經完成,這裏以防萬一
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            // 添加一個監聽器
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    // Directly 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) {
                        promise.setFailure(cause);
                    } else {
                        // 修改註冊狀態爲成功(當註冊成功時不在使用全局的executor,使用channel本身的,詳見 https://github.com/netty/netty/issues/2586)
                        promise.registered();
                        // 進行相關的綁定操做
                        doResolveAndConnect0(channel, remoteAddress, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }
複製代碼

ServerBootstrap同樣,Bootstrap也要先去初始化和註冊channel,註冊的方法和ServerBootstrap相同,初始化channel的方法有所區別。github

初始化channel

void init(Channel channel) throws Exception {
        ChannelPipeline p = channel.pipeline();
        p.addLast(config.handler());
        // 獲取channel的可選項Map
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            setChannelOptions(channel, options, logger);
        }
        // 獲取channel的屬性Map
        final Map<AttributeKey<?>, Object> attrs = attrs0();
        synchronized (attrs) {
            for (Entry<AttributeKey<?>, Object> e : attrs.entrySet()) {
                channel.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }
        }
    }
複製代碼

客戶端channel的初始化和服務端相比要簡單不少,只須要設置一些相關信息便可。promise

channel處理好以後,客戶端就會去鏈接服務器。bash

鏈接服務器

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);
            // 解析器不知道怎麼處理這個服務器地址或者已經處理過了
            if (!resolver.isSupported(remoteAddress) || resolver.isResolved(remoteAddress)) {
                doConnect(remoteAddress, localAddress, promise);
                return promise;
            }
            // 解析服務器地址
            final Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
            // 解析完成時
            if (resolveFuture.isDone()) {
                final Throwable resolveFailureCause = resolveFuture.cause();
                if (resolveFailureCause != null) {
                    // 解析失敗直接關閉channel
                    channel.close();
                    promise.setFailure(resolveFailureCause);
                } else {
                    // 解析成功開始鏈接
                    doConnect(resolveFuture.getNow(), localAddress, promise);
                }
                return promise;
            }
            // 解析沒完成時等待解析完成
            resolveFuture.addListener(new FutureListener<SocketAddress>() {
                @Override
                public void operationComplete(Future<SocketAddress> future) throws Exception {
                    if (future.cause() != null) {
                        // 解析失敗直接關閉channel
                        channel.close();
                        promise.setFailure(future.cause());
                    } else {
                        // 解析成功開始鏈接
                        doConnect(future.getNow(), localAddress, promise);
                    }
                }
            });
        } catch (Throwable cause) {
            promise.tryFailure(cause);
        }
        return promise;
    }
複製代碼

當服務器地址是IP的時候,直接鏈接,若是是域名之類的,會先解析出服務器的IP地址,而後再進行鏈接。域名解析直接使用的java.net.InetAddress的getByName方法,而鏈接的方法調用的是sun.nio.ch.SocketChannelImpl的connect方法。服務器

文中帖的代碼註釋全在:github.com/KAMIJYOUDOU… , 有興趣的童鞋能夠關注一下。ide


本篇到此結束,若是讀完以爲有收穫的話,歡迎點贊、關注、加公衆號【貳級天災】,查閱更多精彩歷史!!! oop

相關文章
相關標籤/搜索