Netty原始碼分析(二):服務端啟動

貳級天災發表於2019-03-24

上一篇粗略的介紹了一下netty,本篇將詳細介紹Netty的伺服器的啟動過程。

ServerBootstrap

看過上篇事例的人,可以知道ServerBootstrap是Netty服務端啟動中扮演著一個重要的角色。 它是Netty提供的一個服務端引導類,繼承自AbstractBootstrap
ServerBootstrap主要包括兩部分:bossGroupworkerGroup。其中bossGroup主要用於繫結埠,接收來自客戶端的請求,接收到請求之後,就會把這些請求交給workGroup去處理。就像現實中的老闆和員工一樣,自己開個公司(繫結埠),到外面接活(接收請求),使喚員工幹活(讓worker去處理)。

埠繫結

埠繫結之前,會先check引導類(ServerBootstrap)的bossGroup和workerGroup有沒有設定,之後再呼叫doBind。

    private ChannelFuture doBind(final SocketAddress localAddress) {
        // 初始化並註冊一個channel,並將chanelFuture返回
        final ChannelFuture regFuture = initAndRegister();
        // 得到實際的channel(初始化和註冊的動作可能尚未完成)
        final Channel channel = regFuture.channel();
        // 發生異常時,直接返回
        if (regFuture.cause() != null) {
            return regFuture;
        }
        // 當到這chanel相關處理已經完成時
        if (regFuture.isDone()) {
            // 到這可以確定channel已經註冊成功
            ChannelPromise promise = channel.newPromise();
            // 進行相關的繫結操作
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            // 註冊一般到這就已經完成,到以防萬一
            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) {
                        promise.setFailure(cause);
                    } else {
                        // 修改註冊狀態為成功(當註冊成功時不在使用全域性的executor,使用channel自己的,詳見 https://github.com/netty/netty/issues/2586)
                        promise.registered();
                        // 進行相關的繫結操作
                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }
複製程式碼

上面的程式碼主要有兩部分:初始化並註冊一個channel和繫結埠。

初始化並註冊一個channel

    final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            // 生產各新channel
            channel = channelFactory.newChannel();
            // 初始化channel
            init(channel);
        } catch (Throwable t) {
            if (channel != null) {
                // 註冊失敗時強制關閉
                channel.unsafe().closeForcibly();
                // 由於channel尚未註冊好,強制使用GlobalEventExecutor
                return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
            }
            return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
        }
        // 註冊channel
        ChannelFuture regFuture = config().group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }
        return regFuture;
    }
複製程式碼

channel的初始化方法:

    void init(Channel channel) throws Exception {
        // 獲取bossChannel的可選項Map
        final Map<ChannelOption<?>, Object> options = options0();
        synchronized (options) {
            setChannelOptions(channel, options, logger);
        }
        // 獲取bossChannel的屬性Map
        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();
        // 設定worker的相關屬性
        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(childOptions.size()));
        }
        synchronized (childAttrs) {
            currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
        }
        p.addLast(new ChannelInitializer<Channel>() {
            @Override
            public void initChannel(final Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                // 新增handler到pipeline
                ChannelHandler handler = config.handler();
                if (handler != null) {
                    pipeline.addLast(handler);
                }
                // 通過EventLoop將ServerBootstrapAcceptor到pipeline中,保證它是最後一個handler
                ch.eventLoop().execute(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.addLast(new ServerBootstrapAcceptor(
                                ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                    }
                });
            }
        });
    }
複製程式碼

channel的註冊方法,最終是呼叫doRegister,不同的channel有所不同,下面以Nio為例:

    protected void doRegister() throws Exception {
        boolean selected = false;
        for (; ; ) {
            try {
                // 直接呼叫java的提供的Channel的註冊方法
                selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
                return;
            } catch (CancelledKeyException e) {
                if (!selected) {
                    eventLoop().selectNow();
                    selected = true;
                } else {
                    throw e;
                }
            }
        }
    }
複製程式碼

繫結埠

最終呼叫的是NioServerSocketChannel的doBind方法。

    protected void doBind(SocketAddress localAddress) throws Exception {
        if (PlatformDependent.javaVersion() >= 7) {
            javaChannel().bind(localAddress, config.getBacklog());
        } else {
            javaChannel().socket().bind(localAddress, config.getBacklog());
        }
    }
複製程式碼

到這就完成了netty服務端的整個啟動過程。

文中帖的程式碼註釋全在:github.com/KAMIJYOUDOU… , 有興趣的童鞋可以關注一下。


本篇到此結束,如果讀完覺得有收穫的話,歡迎點贊、關注、加公眾號【貳級天災】,查閱更多精彩歷史!!!

Netty原始碼分析(二):服務端啟動

相關文章