學不懂Netty?看不懂原始碼?不存在的,這篇文章手把手帶你閱讀Netty原始碼!

跟著Mic學架構發表於2021-11-20

file

閱讀這篇文章之前,建議先閱讀和這篇文章關聯的內容。

1. 詳細剖析分散式微服務架構下網路通訊的底層實現原理(圖解)

2. (年薪60W的技巧)工作了5年,你真的理解Netty以及為什麼要用嗎?(深度乾貨)

3. 深度解析Netty中的核心元件(圖解+例項)

4. BAT面試必問細節:關於Netty中的ByteBuf詳解

5. 通過大量實戰案例分解Netty中是如何解決拆包黏包問題的?

6. 基於Netty實現自定義訊息通訊協議(協議設計及解析應用實戰)

7. 全網最詳細最齊全的序列化技術及深度解析與應用實戰

8. 手把手教你基於Netty實現一個基礎的RPC框架(通俗易懂)

9. (年薪60W分水嶺)基於Netty手寫實現RPC框架進階篇(帶註冊中心和註解)

提前準備好如下程式碼, 從服務端構建著手,深入分析Netty服務端的啟動過程。

public class NettyBasicServerExample {

    public void bind(int port){
        //netty的服務端程式設計要從EventLoopGroup開始,
        // 我們要建立兩個EventLoopGroup,
        // 一個是boss專門用來接收連線,可以理解為處理accept事件,
        // 另一個是worker,可以關注除了accept之外的其它事件,處理子任務。
        //上面注意,boss執行緒一般設定一個執行緒,設定多個也只會用到一個,而且多個目前沒有應用場景,
        // worker執行緒通常要根據伺服器調優,如果不寫預設就是cpu的兩倍。
        EventLoopGroup bossGroup=new NioEventLoopGroup();

        EventLoopGroup workerGroup=new NioEventLoopGroup();
        try {
            //服務端要啟動,需要建立ServerBootStrap,
            // 在這裡面netty把nio的模板式的程式碼都給封裝好了
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                //配置Server的通道,相當於NIO中的ServerSocketChannel
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO)) //設定ServerSocketChannel對應的Handler
                //childHandler表示給worker那些執行緒配置了一個處理器,
                // 這個就是上面NIO中說的,把處理業務的具體邏輯抽象出來,放到Handler裡面
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline()
                            .addLast(new NormalInBoundHandler("NormalInBoundA",false))
                            .addLast(new NormalInBoundHandler("NormalInBoundB",false))
                            .addLast(new NormalInBoundHandler("NormalInBoundC",true));
                        socketChannel.pipeline()
                            .addLast(new NormalOutBoundHandler("NormalOutBoundA"))
                            .addLast(new NormalOutBoundHandler("NormalOutBoundB"))
                            .addLast(new NormalOutBoundHandler("NormalOutBoundC"))
                            .addLast(new ExceptionHandler());
                    }
                });
            //繫結埠並同步等待客戶端連線
            ChannelFuture channelFuture=bootstrap.bind(port).sync();
            System.out.println("Netty Server Started,Listening on :"+port);
            //等待服務端監聽埠關閉
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //釋放執行緒資源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new NettyBasicServerExample().bind(8080);
    }
}
public class NormalInBoundHandler extends ChannelInboundHandlerAdapter {
    private final String name;
    private final boolean flush;

    public NormalInBoundHandler(String name, boolean flush) {
        this.name = name;
        this.flush = flush;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("InboundHandler:"+name);
        if(flush){
            ctx.channel().writeAndFlush(msg);
        }else {
            throw new RuntimeException("InBoundHandler:"+name);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("InboundHandlerException:"+name);
        super.exceptionCaught(ctx, cause);
    }
}
public class NormalOutBoundHandler extends ChannelOutboundHandlerAdapter {
    private final String name;

    public NormalOutBoundHandler(String name) {
        this.name = name;
    }

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("OutBoundHandler:"+name);
        super.write(ctx, msg, promise);
    }
}

在服務端啟動之前,需要配置ServerBootstrap的相關引數,這一步大概分為以下幾個步驟

  • 配置EventLoopGroup執行緒組
  • 配置Channel型別
  • 設定ServerSocketChannel對應的Handler
  • 設定網路監聽的埠
  • 設定SocketChannel對應的Handler
  • 配置Channel引數

Netty會把我們配置的這些資訊組裝,釋出服務監聽。

ServerBootstrap引數配置過程

下面這段程式碼是我們配置ServerBootStrap相關引數,這個過程比較簡單,就是把配置的引數值儲存到ServerBootstrap定義的成員變數中就可以了。

bootstrap.group(bossGroup, workerGroup)
    //配置Server的通道,相當於NIO中的ServerSocketChannel
    .channel(NioServerSocketChannel.class)
    .handler(new LoggingHandler(LogLevel.INFO)) //設定ServerSocketChannel對應的Handler
    //childHandler表示給worker那些執行緒配置了一個處理器,
    // 這個就是上面NIO中說的,把處理業務的具體邏輯抽象出來,放到Handler裡面
    .childHandler(new ChannelInitializer<SocketChannel>() {
    });

我們來看一下ServerBootstrap的類關係圖以及屬性定義

ServerBootstrap類關係圖

如圖8-1所示,表示ServerBootstrap的類關係圖。

  • AbstractBootstrap,定義了一個抽象類,作為抽象類,一定是抽離了Bootstrap相關的抽象邏輯,所以很顯然可以推斷出Bootstrap應該也繼承了AbstractBootstrap
  • ServerBootstrap,服務端的啟動類,
  • ServerBootstrapAcceptor,繼承了ChannelInboundHandlerAdapter,所以本身就是一個Handler,當服務端啟動後,客戶端連線上來時,會先進入到ServerBootstrapAccepter。
image-20210910154646643
圖8-1 ServerBootstrap類關係圖

AbstractBootstrap屬性定義

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
    @SuppressWarnings("unchecked")
    private static final Map.Entry<ChannelOption<?>, Object>[] EMPTY_OPTION_ARRAY = new Map.Entry[0];
    @SuppressWarnings("unchecked")
    private static final Map.Entry<AttributeKey<?>, Object>[] EMPTY_ATTRIBUTE_ARRAY = new Map.Entry[0];
    /**
     * 這裡的EventLoopGroup 作為服務端 Acceptor 執行緒,負責處理客戶端的請求接入
     * 作為客戶端 Connector 執行緒,負責註冊監聽連線操作位,用於判斷非同步連線結果。
     */
    volatile EventLoopGroup group; //
    @SuppressWarnings("deprecation")
    private volatile ChannelFactory<? extends C> channelFactory;  //channel工廠,很明顯應該是用來製造對應Channel的
    private volatile SocketAddress localAddress;  //SocketAddress用來繫結一個服務端地址

    // The order in which ChannelOptions are applied is important they may depend on each other for validation
    // purposes.
    /**
     * ChannelOption 可以新增Channer 新增一些配置資訊
     */
    private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
    private final Map<AttributeKey<?>, Object> attrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
    /**
     *  ChannelHandler 是具體怎麼處理Channer 的IO事件。
     */
    private volatile ChannelHandler handler;
}

對於上述屬性定義,整體總結如下:

  1. 提供了一個ChannelFactory物件用來建立Channel,一個Channel會對應一個EventLoop用於IO的事件處理,在一個Channel的整個生命週期中 只會繫結一個EventLoop,這裡可理解給Channel分配一個執行緒進行IO事件處理,結束後回收該執行緒。

  2. AbstractBootstrap沒有提供EventLoop而是提供了一個EventLoopGroup,其實我認為這裡只用一個EventLoop就行了。

  3. 不管是伺服器還是客戶端的Channel都需要繫結一個本地埠這就有了SocketAddress類的物件localAddress。

  4. Channel有很多選項所有有了options物件LinkedHashMap<channeloption<?>, Object>

  5. 怎麼處理Channel的IO事件呢,我們新增一個事件處理器ChannelHandler物件。

ServerBootstrap屬性定義

ServerBootstrap可以理解為伺服器啟動的工廠類,我們可以通過它來完成伺服器端的 Netty 初始化。主要職責:|

  • EventLoop初始化
  • channel的註冊
  • pipeline的初始化
  • handler的新增過程
  • 服務端連線處理。
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(ServerBootstrap.class);

    // The order in which child ChannelOptions are applied is important they may depend on each other for validation
    // purposes.
    //SocketChannel相關的屬性配置
    private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
    private final Map<AttributeKey<?>, Object> childAttrs = new ConcurrentHashMap<AttributeKey<?>, Object>();
    private final ServerBootstrapConfig config = new ServerBootstrapConfig(this); //配置類
    private volatile EventLoopGroup childGroup;  //工作執行緒組
    private volatile ChannelHandler childHandler; //負責SocketChannel的IO處理相關的Handler

    public ServerBootstrap() { }
}

服務端啟動過程分析

瞭解了ServerBootstrap相關屬性的配置之後,我們繼續來看服務的啟動過程,在開始往下分析的時候,先不妨來思考以下這些問題

  • Netty自己實現的Channel與底層JDK提供的Channel是如何聯絡並且構建實現的
  • ChannelInitializer這個特殊的Handler處理器的作用以及實現原理
  • Pipeline是如何初始化以的

ServerBootstrap.bind

先來看ServerBootstrap.bind()方法的定義,這裡主要用來繫結一個埠並且釋出服務端監聽。

根據我們使用NIO相關API的理解,無非就是使用JDK底層的API來開啟一個服務端監聽並繫結一個埠。

 ChannelFuture channelFuture=bootstrap.bind(port).sync();
public ChannelFuture bind(SocketAddress localAddress) {
    validate();
    return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
}
  • validate(), 驗證ServerBootstrap核心成員屬性的配置是否正確,比如group、channelFactory、childHandler、childGroup等,這些屬性如果沒配置,那麼服務端啟動會報錯

  • localAddress,繫結一個本地埠地址

doBind

doBind方法比較長,從大的程式碼結構,可以分為三個部分

  • initAndRegister 初始化並註冊Channel,並返回一個ChannelFuture,說明初始化註冊Channel是非同步實現
  • regFuture.cause() 用來判斷initAndRegister()是否發生異常,如果發生異常,則直接返回
  • regFuture.isDone(), 判斷initAndRegister()方法是否執行完成。
    • 如果執行完成,則呼叫doBind0()方法。
    • 如果未執行完成,regFuture新增一個監聽回撥,在監聽回撥中再次判斷執行結果進行相關處理。
    • PendingRegistrationPromise 用來儲存非同步執行結果的狀態

從整體程式碼邏輯來看,邏輯結構還是非常清晰的, initAndRegister()方法負責Channel的初始化和註冊、doBind0()方法用來繫結埠。這個無非就是我們使用NIO相關API釋出服務所做的事情。

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

這個方法顧名思義,就是初始化和註冊,基於我們整個流程的分析可以猜測到

  • 初始化,應該就是構建服務端的Handler處理鏈
  • register,應該就是把當前服務端的連線註冊到selector上

下面我們通過原始碼印證我們的猜想。

final ChannelFuture initAndRegister() {
    Channel channel = null;
    try {
        //通過ChannelFactory建立一個具體的Channel實現
        channel = channelFactory.newChannel();
        init(channel); //初始化
    } catch (Throwable t) {
        //省略....
    }
    //這個程式碼應該和我們猜想是一致的,就是將當前初始化的channel註冊到selector上,這個過程同樣也是非同步的
    ChannelFuture regFuture = config().group().register(channel);
    if (regFuture.cause() != null) { //獲取regFuture的執行結果
        if (channel.isRegistered()) { 
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }
    return regFuture;
}

channelFactory.newChannel()

這個方法在分析之前,我們可以繼續推測它的邏輯。

在最開始構建服務端的程式碼中,我們通過channel設定了一個NioServerSocketChannel.class類物件,這個物件表示當前channel的構建使用哪種具體的API

bootstrap.group(bossGroup, workerGroup)
    //配置Server的通道,相當於NIO中的ServerSocketChannel
    .channel(NioServerSocketChannel.class)

而在initAndRegister方法中,又用到了channelFactory.newChannel()來生成一個具體的Channel例項,因此不難想到,這兩者必然有一定的聯絡,我們也可以武斷的認為,這個工廠會根據我們配置的channel來動態構建一個指定的channel例項。

channelFactory有多個實現類,所以我們可以從配置方法中找到channelFactory的具體定義,程式碼如下。

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

channelFactory對應的具體實現是:ReflectiveChannelFactory,因此我們定位到newChannel()方法的實現。

ReflectiveChannelFactory.newChannel

在該方法中,使用constructor構建了一個例項。

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

construtor的初始化程式碼如下, 用到了傳遞進來的clazz類,獲得該類的構造器,該構造器後續可以通過newInstance建立一個例項物件

而此時的clazz其實就是:NioServerSocketChannel

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {

    private final Constructor<? extends T> constructor;

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
        ObjectUtil.checkNotNull(clazz, "clazz");
        try {
            this.constructor = clazz.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("Class " + StringUtil.simpleClassName(clazz) +
                    " does not have a public non-arg constructor", e);
        }
    }
}

NioServerSocketChannel

NioServerSocketChannel的構造方法定義如下。

public class NioServerSocketChannel extends AbstractNioMessageChannel
                             implements io.netty.channel.socket.ServerSocketChannel {
    private static ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openServerSocketChannel();
        } catch (IOException e) {
            throw new ChannelException(
                    "Failed to open a server socket.", e);
        }
    }
    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }
}

當NioServerSocketChannel例項化後,呼叫newSocket方法建立了一個服務端例項。

newSocket方法中呼叫了provider.openServerSocketChannel(),來完成ServerSocketChannel的建立,ServerSocketChannel就是Java中NIO中的服務端API。

public ServerSocketChannel openServerSocketChannel() throws IOException {
    return new ServerSocketChannelImpl(this);
}

通過層層推演,最終看到了Netty是如何一步步封裝,完成ServerSocketChannel的建立。

設定非阻塞

在NioServerSocketChannel中的構造方法中,先通過super呼叫父類做一些配置操作

public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

最終,super會呼叫AbstractNioChannel中的構造方法,

protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;
    this.readInterestOp = readInterestOp; //設定關心事件,此時是一個連線事件,所以是OP_ACCEPT
    try {
        ch.configureBlocking(false); //設定非阻塞
    } catch (IOException e) {
        try {
            ch.close();
        } catch (IOException e2) {
            logger.warn(
                "Failed to close a partially initialized socket.", e2);
        }

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

繼續分析initAndRegister

分析完成channel的初始化後,接下來就是要將當前channel註冊到Selector上,所以繼續回到initAndRegister方法。

final ChannelFuture initAndRegister() {
//省略....
    //這個程式碼應該和我們猜想是一致的,就是將當前初始化的channel註冊到selector上,這個過程同樣也是非同步的
    ChannelFuture regFuture = config().group().register(channel);
    if (regFuture.cause() != null) { //獲取regFuture的執行結果
        if (channel.isRegistered()) { 
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }
    return regFuture;
}

註冊到某個Selector上,其實就是註冊到某個EventLoopGroup中,如果大家能有這個猜想,說明前面的內容是聽懂了的。

config().group().register(channel)這段程式碼,其實就是獲取在ServerBootstrap中配置的bossEventLoopGroup,然後把當前的服務端channel註冊到該group中。

此時,我們通過快捷鍵想去看一下register的實現時,發現EventLoopGroup又有多個實現,我們來看一下類關係圖如圖8-2所示。

image-20210910170502364
圖8-3 EventLoopGroup類關係圖

而我們在前面配置的EventLoopGroup的實現類是NioEventLoopGroup,而NioEventLoopGroup繼承自MultithreadEventLoopGroup,所以在register()方法中,我們直接找到父類的實現方法即可。

MultithreadEventLoopGroup.register

這段程式碼大家都熟了,從NioEventLoopGroup中選擇一個NioEventLoop,將當前channel註冊上去

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

next()方法返回的是NioEventLoop,而NioEventLoop又有多個實現類,我們來看圖8-4所示的類關係圖。

image-20210910171415854
圖8-4 NioEventLoop類關係圖

從類關係圖中發現,發現NioEventLoop派生自SingleThreadEventLoop,所以next().register(channel);方法,執行的是SingleThreadEventLoop中的register

SingleThreadEventLoop.register

@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, 派生自Future,用來實現非同步任務處理回撥功能。簡單來說就是把註冊的動作非同步化,當非同步執行結束後會把執行結果回填到ChannelPromise中

AbstractChannel.register

抽象類一般就是公共邏輯的處理,而這裡的處理主要就是針對一些引數的判斷,判斷完了之後再呼叫register0()方法。

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    ObjectUtil.checkNotNull(eventLoop, "eventLoop");
    if (isRegistered()) { //判斷是否已經註冊過
        promise.setFailure(new IllegalStateException("registered to an event loop already"));
        return;
    }
    if (!isCompatible(eventLoop)) { //判斷eventLoop型別是否是EventLoop物件型別,如果不是則丟擲異常
        promise.setFailure(
            new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
        return;
    }

    AbstractChannel.this.eventLoop = eventLoop;
	//Reactor內部執行緒呼叫,也就是說當前register方法是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);
        }
    }
}

AbstractChannel.register0

Netty從EventLoopGroup執行緒組中選擇一個EventLoop和當前的Channel繫結,之後該Channel生命週期中的所有I/O事件都由這個EventLoop負責。

register0方法主要做四件事:

  • 呼叫JDK層面的API對當前Channel進行註冊
  • 觸發HandlerAdded事件
  • 觸發channelRegistered事件
  • Channel狀態為活躍時,觸發channelActive事件

在當前的ServerSocketChannel連線註冊的邏輯中,我們只需要關注下面的doRegister方法即可。

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();  //呼叫JDK層面的register()方法進行註冊
        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(); //觸發Handler,如果有必要的情況下

        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()) { //此時是ServerSocketChannel的註冊,所以連線還處於非活躍狀態
            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);
    }
}

AbstractNioChannel.doRegister

進入到AbstractNioChannel.doRegister方法。

javaChannel().register()負責呼叫JDK層面的方法,把channel註冊到eventLoop().unwrappedSelector()上,其中第三個引數傳入的是Netty自己實現的Channel物件,也就是把該物件繫結到attachment中。

這樣做的目的是,後續每次調Selector物件進行事件輪詢時,當觸發事件時,Netty都可以獲取自己的Channe物件。

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

服務註冊總結

上述程式碼比較繞,但是整體總結下來並不難理解

  • 初始化指定的Channel例項
  • 把該Channel分配給某一個EventLoop
  • 然後把Channel註冊到該EventLoop的Selector中

AbstractBootstrap.doBind0

分析完了註冊的邏輯後,再回到AbstractBootstrap類中的doBind0方法,這個方法不用看也能知道,ServerSocketChannel初始化了之後,接下來要做的就是繫結一個ip和埠地址。

private static void doBind0(
    final ChannelFuture regFuture, final Channel channel,
    final SocketAddress localAddress, final ChannelPromise promise) {

    //獲取當前channel中的eventLoop例項,執行一個非同步任務。
    //需要注意,以前我們在課程中講過,eventLoop在輪詢中一方面要執行select遍歷,另一方面要執行阻塞佇列中的任務,而這裡就是把任務新增到佇列中非同步執行。
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            //如果ServerSocketChannel註冊成功,則呼叫該channel的bind方法
            if (regFuture.isSuccess()) {
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }
        }
    });
}

channel.bind方法,會根據ServerSocketChannel中的handler鏈配置,逐個進行呼叫,由於在本次案例中,我們給ServerSocketChannel配置了一個 LoggingHandler的處理器,所以bind方法會先呼叫LoggingHandler,然後再呼叫DefaultChannelPipeline中的bind方法,呼叫鏈路

-> DefaultChannelPipeline.ind

​ -> AbstractChannel.bind

​ -> NioServerSocketChannel.doBind

最終就是呼叫前面初始化好的ServerSocketChannel中的bind方法繫結本地地址和埠。

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

構建SocketChannel的Pipeline

在ServerBootstrap的配置中,我們針對SocketChannel,配置了入站和出站的Handler,也就是當某個SocketChannel的IO事件就緒時,就會按照我們配置的處理器連結串列進行逐一處理,那麼這個連結串列是什麼時候構建的,又是什麼樣的結構呢?下面我們來分析這部分的內容

.childHandler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        socketChannel.pipeline()
            .addLast(new NormalInBoundHandler("NormalInBoundA",false))
            .addLast(new NormalInBoundHandler("NormalInBoundB",false))
            .addLast(new NormalInBoundHandler("NormalInBoundC",true));
        socketChannel.pipeline()
            .addLast(new NormalOutBoundHandler("NormalOutBoundA"))
            .addLast(new NormalOutBoundHandler("NormalOutBoundB"))
            .addLast(new NormalOutBoundHandler("NormalOutBoundC"))
            .addLast(new ExceptionHandler());
    }
});

childHandler的構建

childHandler的構建過程,在AbstractChannel.register0方法中實現

final ChannelFuture initAndRegister() {
        Channel channel = null;
        try {
            channel = channelFactory.newChannel(); //這是是建立channel
            init(channel); //這裡是初始化
        } catch (Throwable t) {
            //省略....
        }
        ChannelFuture regFuture = config().group().register(channel); //這是是註冊
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

ServerBootstrap.init

init方法,呼叫的是ServerBootstrap中的init(),程式碼如下。

@Override
void init(Channel channel) {
    setChannelOptions(channel, newOptionsArray(), logger);
    setAttributes(channel, newAttributesArray());

    ChannelPipeline p = channel.pipeline();

    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;  //childHandler就是在服務端配置時新增的ChannelInitializer
    final Entry<ChannelOption<?>, Object>[] currentChildOptions = newOptionsArray(childOptions);
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs = newAttributesArray(childAttrs);
    // 此時的Channel是NioServerSocketChannel,這裡是為NioServerSocketChannel新增處理器鏈。
    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(final Channel ch) {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = config.handler(); //如果在ServerBootstrap構建時,通過.handler新增了處理器,則會把相關處理器新增到NioServerSocketChannel中的pipeline中。
            if (handler != null) {
                pipeline.addLast(handler);
            }

            ch.eventLoop().execute(new Runnable() { //非同步天劍一個ServerBootstrapAcceptor處理器,從名字來看,
                @Override
                public void run() {
                    pipeline.addLast(new ServerBootstrapAcceptor(
                        //currentChildHandler,表示SocketChannel的pipeline,當收到客戶端連線時,就會把該handler新增到當前SocketChannel的pipeline中
                        ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
                }
            });
        }
    });
}

其中,對於上述程式碼的核心部分說明如下

  • ChannelPipeline 是在AbstractChannel中的構造方法中初始化的一個DefaultChannelPipeline

    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        id = newId();
        unsafe = newUnsafe();
        pipeline = newChannelPipeline();
    }
    
  • p.addLast是為NioServerSocketChannel新增handler處理器鏈,這裡新增了一個ChannelInitializer回撥函式,該回撥是非同步觸發的,在回撥方法中做了兩件事

    • 如果ServerBootstrap.handler新增了處理器,則會把相關處理器新增到該pipeline中,在本次演示的案例中,我們新增了LoggerHandler
    • 非同步執行新增了ServerBootstrapAcceptor,從名字來看,它是專門用來接收新的連線處理的。

我們在這裡思考一個問題,為什麼NioServerSocketChannel需要通過ChannelInitializer回撥處理器呢? ServerBootstrapAcceptor為什麼通過非同步任務新增到pipeline中呢?

原因是,NioServerSocketChannel在初始化的時候,還沒有開始將該Channel註冊到Selector物件上,也就是沒辦法把ACCEPT事件註冊到Selector上,所以事先新增了ChannelInitializer處理器,等待Channel註冊完成後,再向Pipeline中新增ServerBootstrapAcceptor。

ServerBootstrapAcceptor

按照下面的方法演示一下SocketChannel中的Pipeline的構建過程

  1. 啟動服務端監聽
  2. 在ServerBootstrapAcceptor的channelRead方法中打上斷點
  3. 通過telnet 連線,此時會觸發debug。
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;

    child.pipeline().addLast(childHandler);  //在這裡,將handler新增到SocketChannel的pipeline中

    setChannelOptions(child, childOptions, logger);
    setAttributes(child, childAttrs);

    try {
        //把當前客戶端的連結SocketChannel註冊到某個EventLoop中。
        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);
    }
}

ServerBootstrapAcceptor是服務端NioServerSocketChannel中的一個特殊處理器,該處理器的channelRead事件只會在新連線產生時觸發,所以這裡通過 final Channel child = (Channel) msg;可以直接拿到客戶端的連結SocketChannel。

ServerBootstrapAcceptor接著通過childGroup.register()方法,把當前NioSocketChannel註冊到工作執行緒中。

事件觸發機制的流程

在ServerBootstrapAcceptor中,收到客戶端連線時,會呼叫childGroup.register(child)把當前客戶端連線註冊到指定NioEventLoop的Selector中。

這個註冊流程和前面講解的NioServerSocketChannel註冊流程完全一樣,最終都會進入到AbstractChannel.register0方法。

AbstractChannel.register0

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(); //執行pipeline中的ChannelRegistered()事件。
        // 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);
    }
}

pipeline.fireChannelRegistered()

@Override
public final ChannelPipeline fireChannelRegistered() {
    AbstractChannelHandlerContext.invokeChannelRegistered(head);
    return this;
}

下面的事件觸發,分為兩個邏輯

  • 如果當前的任務是在eventLoop中觸發的,則直接呼叫invokeChannelRegistered
  • 否則,非同步執行invokeChannelRegistered。
static void invokeChannelRegistered(final AbstractChannelHandlerContext next) {
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelRegistered();
    } else {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelRegistered();
            }
        });
    }
}

invokeChannelRegistered

觸發下一個handler的channelRegistered方法。

private void invokeChannelRegistered() {
    if (invokeHandler()) {
        try {
            ((ChannelInboundHandler) handler()).channelRegistered(this);
        } catch (Throwable t) {
            invokeExceptionCaught(t);
        }
    } else {
        fireChannelRegistered();
    }
}

Netty服務端啟動總結

到此為止,整個服務端啟動的過程,我們就已經分析完成了,主要的邏輯如下

  • 建立服務端Channel,本質上是根據使用者配置的實現,呼叫JDK原生的Channel
  • 初始化Channel的核心屬性,unsafe、pipeline
  • 初始化Channel的Pipeline,主要是新增兩個特殊的處理器,ChannelInitializer和ServerBootstrapAcceptor
  • 註冊服務端的Channel,新增OP_ACCEPT事件,這裡底層呼叫的是JDK層面的實現,講Channel註冊到BossEventLoop中的Selector上
  • 繫結埠,呼叫JDK層面的API,繫結埠。

版權宣告:本部落格所有文章除特別宣告外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 Mic帶你學架構
如果本篇文章對您有幫助,還請幫忙點個關注和贊,您的堅持是我不斷創作的動力。歡迎關注「跟著Mic學架構」公眾號公眾號獲取更多技術乾貨!

相關文章