91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Netty服務端啟動源碼是什么

發布時間:2022-01-06 15:31:02 來源:億速云 閱讀:93 作者:iii 欄目:大數據

這篇文章主要介紹“Netty服務端啟動源碼是什么”,在日常操作中,相信很多人在Netty服務端啟動源碼是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Netty服務端啟動源碼是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

一、從EchoServer示例入手

Netty服務端啟動源碼是什么

示例從哪里來?任何開源框架都會有自己的示例代碼,Netty源碼也不例外,如模塊netty-example中就包括了最常見的EchoServer示例,下面通過這個示例進入服務端啟動流程篇章。

public final class EchoServer {

    static final boolean SSL = System.getProperty("ssl") != null;
    static final int PORT = Integer.parseInt(System.getProperty("port", "8007"));

    public static void main(String[] args) throws Exception {
        // Configure SSL.
        final SslContext sslCtx;
        if (SSL) {
            SelfSignedCertificate ssc = new SelfSignedCertificate();
            sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
        } else {
            sslCtx = null;
        }

        // 1. 聲明Main-Sub Reactor模式線程池:EventLoopGroup
        // Configure the server.
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        // 創建 EchoServerHandler 對象
        final EchoServerHandler serverHandler = new EchoServerHandler();
        try {
            // 2. 聲明服務端啟動引導器,并設置相關屬性
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .option(ChannelOption.SO_BACKLOG, 100)
             .handler(new LoggingHandler(LogLevel.INFO))
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc()));
                     }
                     //p.addLast(new LoggingHandler(LogLevel.INFO));
                     p.addLast(serverHandler);
                 }
             });

            // 3. 綁定端口即啟動服務端,并同步等待
            // Start the server.
            ChannelFuture f = b.bind(PORT).sync();

            // 4. 監聽服務端關閉,并阻塞等待
            // Wait until the server socket is closed.
            f.channel().closeFuture().sync();
        } finally {
            // 5. 優雅地關閉兩個EventLoopGroup線程池 
            // Shut down all event loops to terminate all threads.
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  1. [代碼行18、19]聲明Main-Sub Reactor模式線程池:EventLoopGroup

創建兩個 EventLoopGroup 對象。其中,bossGroup用于服務端接受客戶端的連接,workerGroup用于進行客戶端的 SocketChannel 的數據讀寫。

(<u>關于EventLoopGroup不是本文重點所以在后續文章中進行分析</u>)

  1. [代碼行23-39]聲明服務端啟動引導器,并設置相關屬性

AbstractBootstrap是一個幫助類,通過方法鏈(method chaining)的方式,提供了一個簡單易用的方式來配置啟動一個Channelio.netty.bootstrap.ServerBootstrap ,實現 AbstractBootstrap 抽象類,用于 Server 的啟動器實現類。io.netty.bootstrap.Bootstrap ,實現 AbstractBootstrap 抽象類,用于 Client 的啟動器實現類。如下類圖所示:

![AbstractBootstrap類繼承.png](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d877706356d4427a9afd4b13d7177142~tplv-k3u1fbpfcp-zoom-1.image)

(<u>在EchoServer示例代碼中,我們看到 ServerBootstrap groupchanneloptionchildHandler 等屬性鏈式設置都放到關于AbstractBootstrap體系代碼中詳細介紹。</u>)

  1. [代碼行43]綁定端口即啟動服務端,并同步等待

先調用 #bind(int port) 方法,綁定端口,后調用 ChannelFuture#sync() 方法,阻塞等待成功。對于bind操作就是本文要詳細介紹的"服務端啟動流程"。

  1. [代碼行47]監聽服務端關閉,并阻塞等待

先調用 #closeFuture() 方法,監聽服務器關閉,后調用 ChannelFuture#sync() 方法,阻塞等待成功。 注意,此處不是關閉服務器,而是channel的監聽關閉。

  1. [代碼行51、52]優雅地關閉兩個EventLoopGroup線程池

finally代碼塊中執行說明服務端將最終關閉,所以調用 EventLoopGroup#shutdownGracefully() 方法,分別關閉兩個EventLoopGroup對象,終止所有線程。

二、服務啟動過程

在服務啟動過程的源碼分析之前,這里回顧一下我們在通過JDK NIO編程在服務端啟動初始的代碼:

 serverSocketChannel = ServerSocketChannel.open();
 serverSocketChannel.configureBlocking(false);
 serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
 selector = Selector.open();
 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

這5行代碼標示一個最為熟悉的過程:

  • 打開serverSocketChannel

  • 配置非阻塞模式

  • channelsocket綁定監聽端口

  • 創建Selector

  • serverSocketChannel注冊到 selector

后面等分析完Netty的啟動過程后,會對這些步驟有一個新的認識。在EchoServer示例中,進入 #bind(int port) 方法,AbstractBootstrap#bind()其實有多個方法,方便不同地址參數的傳遞,實際調用的方法是AbstractBootstrap#doBind(final SocketAddress localAddress) 方法,代碼如下:

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;
        }
}
  • [代碼行2] :調用 #initAndRegister() 方法,初始化并注冊一個 Channel 對象。因為注冊是異步的過程,所以返回一個 ChannelFuture 對象。詳細解析,見 「initAndRegister()」。

  • [代碼行4-6]]:若發生異常,直接進行返回。

  • [代碼行9-34]:因為注冊是異步的過程,有可能已完成,有可能未完成。所以實現代碼分成了【第 10 至 14 行】和【第 15 至 36 行】分別處理已完成和未完成的情況。

    • 核心在[第 11 、29行],調用 #doBind0(final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) 方法,綁定 Channel 的端口,并注冊 Channel 到 SelectionKey 中。

    • 如果異步注冊對應的 ChanelFuture 未完成,則調用 ChannelFuture#addListener(ChannelFutureListener) 方法,添加監聽器,在注冊完成后,進行回調執行 #doBind0(...) 方法的邏輯。

通過doBind方法可以知道服務端啟動流程大致如下幾個步驟:

Netty服務端啟動源碼是什么

1. 創建Channel

Netty服務端啟動源碼是什么

#doBind(final SocketAddress localAddress)進入到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();
         }
     }

     return regFuture;
}

[代碼行4]調用 ChannelFactory#newChannel() 方法,創建Channel對象。 ChannelFactory類繼承如下:

Netty服務端啟動源碼是什么

可以在ChannelFactory注釋看到@deprecated Use {@link io.netty.channel.ChannelFactory} instead.,這里只是包名的調整,對于繼承結構不變。netty默認使用ReflectiveChannelFactory,我們可以看到重載方法:

@Override
public T newChannel() {
    try {
        return constructor.newInstance();
    } catch (Throwable t) {
        throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t);
    }
}

很明顯,正如其名是通過反射機制構造Channel對象實例的。constructor是在其構造方法初始化的:this.constructor = clazz.getConstructor();這個clazz按理說應該是我們要創建的Channel的Class對象。那Class對象是什么呢?我們接著看channelFactory是怎么初始化的。

首先在AbstractBootstrap找到如下代碼:

@Deprecated
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    ObjectUtil.checkNotNull(channelFactory, "channelFactory");
    if (this.channelFactory != null) {
        throw new IllegalStateException("channelFactory set already");
    }

    this.channelFactory = channelFactory;
    return self();
}

調用這個方法的遞推向上看到:

public B channel(Class<? extends C> channelClass) {
    return channelFactory(new ReflectiveChannelFactory<C>(
        ObjectUtil.checkNotNull(channelClass, "channelClass")
    ));
}

這個方法正是在EchoServerServerBootstrap鏈式設置時調用.channel(NioServerSocketChannel.class)的方法。我們看到,channelClass就是NioServerSocketChannel.classchannelFactory也是以ReflectiveChannelFactory作為具體實例,并且將NioServerSocketChannel.class作為構造參數傳遞初始化的,所以這回答了反射機制構造的是io.netty.channel.socket.nio.NioServerSocketChannel對象。

繼續看NioServerSocketChannel構造方法邏輯做了什么事情,看之前先給出NioServerSocketChannel類繼承關系:

Netty服務端啟動源碼是什么

NioServerSocketChannelNioSocketChannel分別對應服務端和客戶端,公共父類都是AbstractNioChannelAbstractChannel,下面介紹創建過程可以參照這個Channel類繼承圖。進入NioServerSocketChannel構造方法:

/**
  * Create a new instance
  */
public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

點擊newSocket進去:

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        /**
          *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
          *  {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.
          *
          *  See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
          */
        return provider.openServerSocketChannel();
    } catch (IOException e) {
        throw new ChannelException(
            "Failed to open a server socket.", e);
    }
}

以上傳進來的providerDEFAULT_SELECTOR_PROVIDER即默認的java.nio.channels.spi.SelectorProvider,[代碼行9]就是熟悉的jdk nio創建ServerSocketChannel。這樣newSocket(DEFAULT_SELECTOR_PROVIDER)就返回了結果ServerSocketChannel,回到NioServerSocketChannel()#this()點進去:

/**
  * Create a new instance using the given {@link ServerSocketChannel}.
  */
public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

以上super代表父類AbstractNioMessageChannel構造方法,點進去看到:

 /**
   * @see AbstractNioChannel#AbstractNioChannel(Channel, SelectableChannel, int)
   */
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent, ch, readInterestOp);
}

以上super代表父類AbstractNioChannel構造方法,點進去看到:

 protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
     super(parent);
     this.ch = ch;
     this.readInterestOp = readInterestOp;
     try {
         ch.configureBlocking(false);
     } catch (IOException e) {
         try {
             ch.close();
         } catch (IOException e2) {
             if (logger.isWarnEnabled()) {
                 logger.warn("Failed to close a partially initialized socket.", e2);
             }
         }

         throw new ChannelException("Failed to enter non-blocking mode.", e);
     }
}

以上[代碼行3]將ServerSocketChannel保存到了AbstractNioChannel#ch成員變量,在上面提到的NioServerSocketChannel構造方法的[代碼行6]javaChannel()拿到的就是ch保存的ServerSocketChannel變量。

以上[代碼行6]就是熟悉的jdk nio編程設置ServerSocketChannel非阻塞方式。這里還有super父類構造方法,點擊進去看到:

 protected AbstractChannel(Channel parent) {
     this.parent = parent;
     id = newId();
     unsafe = newUnsafe();
     pipeline = newChannelPipeline();
}

以上構造方法中:

  • parent 屬性,代表父 Channel 對象。對于NioServerSocketChannelparentnull

  • id 屬性,Channel 編號對象。在構造方法中,通過調用 #newId() 方法進行創建。(<u>這里不細展開Problem-1</u>)

  • unsafe 屬性,Unsafe 對象。因為Channel 真正的具體操作,是通過調用對應的 Unsafe 對象實施。所以需要在構造方法中,通過調用 #newUnsafe() 方法進行創建。這里的 Unsafe 并不是我們常說的 jdk自帶的sun.misc.Unsafe ,而是 io.netty.channel.Channel#Unsafe。(<u>這里不細展開Problem-2</u>)

  • pipeline屬性默認是DefaultChannelPipeline對象,賦值后在后面為channel綁定端口的時候會用到

通過以上創建channel源碼過程分析,總結的流程時序圖如下:

Netty服務端啟動源碼是什么

2. 初始化Channel

Netty服務端啟動源碼是什么

回到一開始創建ChannelinitAndRegister()入口方法,在創建Channel后緊接著init(channel)進入初始化流程,因為是服務端初始化,所以是ServerBootstrap#init(Channel channel),代碼如下:

@Override
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));
                }
            });
        }
    });
}
  • [代碼 3 - 6 行]: options0()方法返回的options保存了用戶在EchoServer中設置自定義的可選項集合,這樣ServerBootstrap將配置的選項集合,設置到了 Channel 的可選項集合中。

  • [代碼 8 - 15 行]: attrs0()方法返回的attrs保存了用戶在EchoServer中設置自定義的屬性集合,這樣ServerBootstrap將配置的屬性集合,設置到了 Channel 的屬性集合中。

  • [代碼21-28行]:通過局部變量currentChildOptionscurrentChildAttrs保存了用戶自定義的childOptionschildAttrs,用于[代碼43行] ServerBootstrapAcceptor 構造方法。

  • [代碼30-47]]:創建ChannelInitializer 對象,添加到 pipeline 中,用于后續初始化 ChannelHandler pipeline 中,包括用戶在EchoServer配置的LoggingHandler和創建的創建 ServerBootstrapAcceptor 對象。

    • [代碼行34-37]:添加啟動器配置的 LoggingHandlerpipeline 中。

    • [代碼行39-45]:創建 ServerBootstrapAcceptor 對象,添加到 pipeline 中。從名字上就可以看出來,ServerBootstrapAcceptor 也是一個 ChannelHandler 實現類,專門用于接受客戶端的新連接請求,把新的請求扔給某個事件循環器,我們先不做過多分析。我們發現是使用EventLoop.execute 執行添加的過程,這是為什么呢?同樣記錄問題(<u>Problem-</u>3)

    • 需要說明的是pipeline 在之前介紹Netty核心組件的時候提到是一個包含ChannelHandlerContext的雙向鏈表,每一個context對于唯一一個ChannelHandler,這里初始化后,ChannelPipeline里就是如下一個結構:

      Netty服務端啟動源碼是什么

3. 注冊Channel

Netty服務端啟動源碼是什么

初始化Channel一些基本配置和屬性完畢后,回到一開始創建ChannelinitAndRegister()入口方法,在初始化Channel后緊接著[代碼行17] ChannelFuture regFuture = config().group().register(channel);明顯這里是通過EventLoopGroup進入注冊流程(EventLoopGroup體系將在后續文章講解)

EchoServer中啟動器同樣通過ServerBootstrap#group()設置了NioEventLoopGroup,它繼承自MultithreadEventLoopGroup,所以注冊流程會進入MultithreadEventLoopGroup重載的register(Channel channel)方法,代碼如下:

@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

這里會調用 next() 方法選擇出來一個 EventLoop 來注冊 Channel,里面實際上使用的是一個叫做 EventExecutorChooser 的東西來選擇,它實際上又有兩種實現方式 ——PowerOfTwoEventExecutorChooserGenericEventExecutorChooser,本質上就是從 EventExecutor 數組中選擇一個 EventExecutor,我們這里就是 NioEventLoop,那么,它們有什么區別呢?(<u>Problem-4:在介紹EventLoopGroup體系的后續文章中將會詳細講解,這里簡單地提一下,本質都是按數組長度取余數 ,不過,2 的 N 次方的形式更高效。</u>)

接著,來到 NioEventLoopregister(channel) 方法,你會不會問找不到該方法?提示NioEventLoop 繼承SingleThreadEventLoop,所以父類方法:

@Override
public ChannelFuture register(Channel channel) {
    return register(new DefaultChannelPromise(channel, this));
}

@Override
public ChannelFuture register(final ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    promise.channel().unsafe().register(this, promise);
    return promise;
}

可以看到,先創建了一個叫做 ChannelPromise 的東西,它是 ChannelFuture 的子類。[代碼行9]又調回了 ChannelUnsaferegister () 方法,這里第一個參數是 this,也就是 NioEventLoop,第二個參數是剛創建的 ChannelPromise

點擊 AbstractUnsafe#register(EventLoop eventLoop, final ChannelPromise promise) 方法進去,代碼如下:

 public final void register(EventLoop eventLoop, final ChannelPromise promise) {
     if (eventLoop == null) {
         throw new NullPointerException("eventLoop");
     }
     if (isRegistered()) {
         promise.setFailure(new IllegalStateException("registered to an event loop already"));
         return;
     }
     if (!isCompatible(eventLoop)) {
         promise.setFailure(
             new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
         return;
     }

     AbstractChannel.this.eventLoop = eventLoop;

     if (eventLoop.inEventLoop()) {
         register0(promise);
     } else {
         try {
             eventLoop.execute(new Runnable() {
                 @Override
                 public void run() {
                     register0(promise);
                 }
             });
         } catch (Throwable t) {
             logger.warn(
                 "Force-closing a channel whose registration task was not accepted by an event loop: {}",
                 AbstractChannel.this, t);
             closeForcibly();
             closeFuture.setClosed();
             safeSetFailure(promise, t);
         }
     }
}

[代碼行15]這行代碼是設置 ChanneleventLoop 屬性。這行前面的代碼主要是在校驗傳入的 eventLoop 參數非空,校驗是否有注冊過以及校驗 ChanneleventLoop 類型是否匹配。

[代碼18、24]接著,跟蹤到 AbstractUnsafe#register0(ChannelPromise promise) 方法中:

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

[代碼行9]進入 AbstractNioChannel#doRegister() 方法:

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

[代碼行5]關鍵一行代碼,將 Java 原生NIO Selector與 Java 原生 NIOChannel 對象(ServerSocketChannel) 綁定在一起,并將當前 Netty 的Channel通過 attachment 的形式綁定到 SelectionKey 上:

  • 調用 #unwrappedSelector() 方法,返回 Java 原生 NIO Selector 對象,而且每個NioEventLoopSelector唯一一對應。

  • 調用 SelectableChannel#register(Selector sel, int ops, Object att) 方法,注冊 Java 原生NIOChannel 對象到 NIO Selector對象上。

通過以上注冊channel源碼分析,總結流程的時序圖如下:

Netty服務端啟動源碼是什么

4. 綁定端口

Netty服務端啟動源碼是什么

注冊完Channel最后回到AbstractBootstrap#doBind() 方法,分析 Channel 的端口綁定邏輯。進入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());
            }
        }
    });
}
  • [代碼行7]:在前面Channel 注冊成功的條件下,調用 EventLoop執行 Channel 的端口綁定邏輯。但是,實際上當前線程已經是 EventLoop所在的線程了,為何還要這樣操作呢?答案在【第 5 至 6 行】的英語注釋,這里作為一個問題記著(<u>Problem-5</u>)。

  • [代碼行11]:進入AbstractChannel#bind(SocketAddress localAddress, ChannelPromise promise),同樣立即異步返回并添加ChannelFutureListener.CLOSE_ON_FAILURE監聽事件。

  • [代碼行13]:如果綁定端口之前的操作并沒有成功,自然也就不能進行端口綁定操作了,通過promise記錄異常原因。

AbstractChannel#bind(SocketAddress localAddress, ChannelPromise promise)方法如下:

 public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
        return pipeline.bind(localAddress, promise);
 }

pipeline是之前創建channel的時候創建的DefaultChannelPipeline,進入該方法:

 public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
        return tail.bind(localAddress, promise);
 }

[在分析初始化流程的時候最后畫一個DefaultChannelPipeline內部的結構,能夠便于分析后面進入DefaultChannelPipeline一系列bind方法。]

首先,tail代表TailContext,進入AbstractChannelHandlerContext# bind(final SocketAddress localAddress, final ChannelPromise promise)方法:

 public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
     //省略部分代碼
     final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND);
     EventExecutor executor = next.executor();
     if (executor.inEventLoop()) {
         next.invokeBind(localAddress, promise);
     } else {
         safeExecute(executor, new Runnable() {
             @Override
             public void run() {
                 next.invokeBind(localAddress, promise);
             }
         }, promise, null);
     }
     return promise;
}

[代碼行3]:findContextOutbound方法里主要是執行ctx = ctx.prev;那么得到的next就是綁定LoggingHandlercontext

[代碼行6]:進入invokeBind(localAddress, promise)方法并直接執行LoggingHandler#bind(this, localAddress, promise),進入后的方法如下:

 public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception {
     if (logger.isEnabled(internalLevel)) {
         logger.log(internalLevel, format(ctx, "BIND", localAddress));
     }
     ctx.bind(localAddress, promise);
 }

設置了LoggingHandler的日志基本級別為默認的INFO后,進行綁定操作的信息打印。接著,繼續循環到AbstractChannelHandlerContext# bind(final SocketAddress localAddress, final ChannelPromise promise)方法執行ctx = ctx.prev取出HeadContext進入到bind方法:

 public void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
     unsafe.bind(localAddress, promise);
 }

兜兜轉轉,最終跳出了pipeline輪回到AbstractUnsafe#bind(final SocketAddress localAddress, final ChannelPromise promise) 方法,Channel 的端口綁定邏輯。代碼如下:

public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
    //此處有省略...
    boolean wasActive = isActive();
    try {
        doBind(localAddress);
    } catch (Throwable t) {
        safeSetFailure(promise, t);
        closeIfClosed();
        return;
    }
    //此處有省略...
}

做實事方法doBind進入后如下:

@Override
protected void doBind(SocketAddress localAddress) throws Exception {
    if (PlatformDependent.javaVersion() >= 7) {
        javaChannel().bind(localAddress, config.getBacklog());
    } else {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }
}

到了此處,服務端的 Java 原生 NIO ServerSocketChannel 終于綁定上了端口。

三、問題歸納

  • Problem-1: 創建Channel流程中AbstractChannel構造函數中為channel分配ID的算法如何實現?

  • Problem-2: AbstractChannel內部類AbstractUnsafe的作用?

  • Problem-3: 初始化channel流程中pipeline 添加ServerBootstrapAcceptor 是通過EventLoop.execute 執行添加的過程,這是為什么呢?

  • Problem-4:注冊channel流程中PowerOfTwoEventExecutorChooserGenericEventExecutorChooser的區別和優化原理?

  • Problem-5:綁定端口流程中調用 EventLoop執行 Channel 的端口綁定邏輯。但是,實際上當前線程已經是 EventLoop所在的線程了,為何還要這樣操作呢?

到此,關于“Netty服務端啟動源碼是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

富锦市| 镇安县| 瓦房店市| 鸡东县| 云霄县| 武鸣县| 商南县| 铁力市| 天峨县| 黑河市| 平远县| 临城县| 永定县| 宁乡县| 榆林市| 宁城县| 青浦区| 湘乡市| 栖霞市| 平和县| 应城市| 五大连池市| 尼勒克县| 宣化县| 博兴县| 同江市| 天祝| 综艺| 深泽县| 晴隆县| 商都县| 黄龙县| 阿荣旗| 永福县| 鹰潭市| 乌什县| 故城县| 安仁县| 淳化县| 堆龙德庆县| 临安市|