Netty原始碼解析 -- 服務端啟動過程

binecy發表於2020-11-01

本文通過閱讀Netty原始碼,解析Netty服務端啟動過程。
原始碼分析基於Netty 4.1

Netty是一個高效能的網路通訊框架,支援NIO,OIO等多種IO模式。通常,我們都是使用NIO模式,該系列文章也是解析Netty下NIO模式的實現。
首先,看一個NIO網路通訊示意圖

Netty中NIO網路通訊過程在此基礎上實現,下面來看一下具體實現。

Channel

首先,看一下Netty中的通道Channel,它代表了一個能完成IO操作的通道,提供read, write, connect, bind等方法。
Channel中維護了一個Unsafe物件,用於完成資料傳輸操作(這類操作通常由IO事件觸發,而不是使用者觸發)。

SocketChannel代表Socket連線的網路通道,面向流,支援讀寫操作。
ServerChannel表示可以監聽新連線的通道,ServerSocketChannel代表實現TCP/IP協議的ServerChannel。

AbstractChannel提供基礎邏輯實現,它維護了Unsafe和ChannelPipeline物件,並委託這兩個物件完成實際工作。同時,它也提供newUnsafe,newChannelPipeline方法給子類構造他們需要的物件。
AbstractUnsafe是AbstractChannel的內部類,實現了register,bind,disconnect等方法的基礎邏輯。

ChannelPipeline可以理解為攔截器連結串列,維護了一個ChannelHandler連結串列,ChannelHandler即具體攔截器,負責邏輯處理。
DefaultChannelPipeline是ChannelPipeline介面的預設實現。Netty中Nio相關的Channel都使用它。
可以這樣理解,Unsafe負責資料傳輸,而ChannelPipeline負責邏輯處理。

AbstractNioChannel實現了NIO基礎邏輯,如維護(jvm)SelectableChannel,(jvm)SelectionKey等物件,還有一個很關鍵的selectionKey,代表關注的NIO事件。
AbstractNioUnsafe是AbstractNioChannel內部類,繼承於AbstractUnsafe,並實現Unsafe另一個子介面NioUnsafe,新增了SelectableChannel相關的方法,如finishConnect,read。

AbstractNioChannel的子類可以分成ServerChannel實現類和SocketChannel實現類。

ServerChannel實現類是AbstractNioMessageChannel,newUnsafe方法返回的NioMessageUnsafe。
NioServerSocketChannel是AbstractNioMessageChannel子類,實現TCP/IP協議。

SocketChannel實現類是AbstractNioByteChannel,newUnsafe方法返回的NioByteUnsafe。
NioSocketChannel是AbstractNioByteChannel子類,實現TCP/IP協議。

Channel各實現類關係如下

Netty中將介面劃分得很細微,最好大家可以按功能層次理解各介面代表含義以及實現類的​邏輯。​
以免後續看原始碼時混淆各介面功能。

服務端啟動

首先簡單瞭解一下EventLoop,可以理解為它負責處理網路事件和非同步任務,後面有對應文章詳細解析。
EventLoopGroup則是一組EventLoop集合,它會將操作委託給其中一個EventLoop處理。

Netty的服務端啟動引導類ServerBootstrap中維護了兩個EventLoopGroup,EventLoopGroup#childGroup和AbstractBootstrap#group。
AbstractBootstrap#group負責管理註冊於其上的ServerChannel,處理這些Channel上發生的Accept事件,並將生成的SocketChannel註冊到EventLoopGroup#childGroup。
EventLoopGroup#childGroup處理這些SocketChannel上發生的Read,Write事件。
為了方便,下文我將AbstractBootstrap#group稱為AcceptGroup,ServerBootstrap#childGroup稱為ReadGroup。

這些設計來自Reactor模式,詳細可以見java.util.concurrent包的作者Doug Lea的《Scalable IO in Java》

AbstractBootstrap#bind -> AbstractBootstrap#doBind

private ChannelFuture doBind(final SocketAddress localAddress) {
	// #1
	final ChannelFuture regFuture = initAndRegister();	
	final Channel channel = regFuture.channel();
	if (regFuture.cause() != null) {
		return regFuture;
	}

	if (regFuture.isDone()) {
		ChannelPromise promise = channel.newPromise();
		// #2
		doBind0(regFuture, channel, localAddress, promise);
		return promise;
	} else {
		...
	}
}

#1 初始化及註冊ServerChannel。
initAndRegister方法返回ChannelFuture,ChannelFuture繼承了(jvm)Future,代表IO非同步處理結果,並且可以繫結回撥函式,非同步IO處理完成Netty後會觸發這些回撥函式。
我們要有這個意識,Netty是一個非同步框架,所有的IO操作都是非同步的(充分利用cpu),IO方法不會等待實際IO操作完成,而是返回ChannelFuture。
待實際IO完成後,Netty再觸發ChannelFuture中的回撥函式處理後續邏輯。
ChannelPromise是一種特殊的ChannelFuture,提供更新操作結果的方法(setSuccess,setFailure方法),一般提供給IO方法作為引數(Unsafe中很多方法都有該引數),IO操作完成後,會呼叫這些方法更新操作結果。
#2 註冊完成後,繫結ServerChannel監聽埠。

AbstractBootstrap#initAndRegister

final ChannelFuture initAndRegister() {
	Channel channel = null;
	try {
		// #1
		channel = channelFactory.newChannel();
		// #2
		init(channel);
	} catch (Throwable t) {
		...
	}
	// #3
	ChannelFuture regFuture = config().group().register(channel);
	
	// #4
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }
	return regFuture;
}

#1 構造ServerChannel
AbstractBootstrap#channelFactory是一個ReflectiveChannelFactory物件,他通過反射生成Channel。
ServerBootstrap#channel方法負責構造ReflectiveChannelFactory,並指定具體的ServerChannel類。
(所以我們要通過該方法指定NioServerSocketChannel.class -- new ServerBootstrap().channel(NioServerSocketChannel.class)
#2 初始化ServerChannel,該方法由子類實現
#3 註冊Channel到AcceptGroup,注意,config().group()返回AcceptGroup。
#4 如果IO操作發生了異常,需要關閉Channel。

NioServerSocketChannel#構造方法 -> NioServerSocketChannel#newSocket方法

private static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        return provider.openServerSocketChannel();
    } catch (IOException e) {
        throw new ChannelException("Failed to open a server socket.", e);
    }
}

使用(jvm)SelectorProvider,構造一個(jvm)ServerSocketChannel。
這裡完成了NIO網路通訊第一步。

ServerBootstrap#init

void init(Channel channel) throws Exception {
	// #1
	...

	ChannelPipeline p = channel.pipeline();

	final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
    }
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);

	// #2
	p.addLast(new ChannelInitializer<Channel>() {
		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() {
				public void run() {
					// #3
					pipeline.addLast(new ServerBootstrapAcceptor(
							ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));	
				}
			});
		}
	});
}

#1 設定ServerChannel的Option和Attribute屬性。
#2 給ServerChannel的ChannelPipeline新增一個ChannelInitializer。
ChannelInitializer是一種特殊的ChannelHandler,initChannel方法負責完成一些Channel初始化工作,該方法的觸發可以參考下文的延遲任務。
#3 上一步驟的ChannelInitializer負責給ServerChannel的ChannelPipeline新增一個ServerBootstrapAcceptor,並將SocketChannel的相關配置(childHandler,currentChildHandler,currentChildOptions,currentChildAttrs)交給它,ServerBootstrapAcceptor用於處理Accept事件,文章後面會解析。

AbstractBootstrap#initAndRegister方法#3步驟 -> SingleThreadEventLoop#register ->(通過Channel呼叫Unsafe)AbstractUnsafe#register

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    ...

    AbstractChannel.this.eventLoop = eventLoop;
	// #1
    if (eventLoop.inEventLoop()) {
        register0(promise);	
    } else {
        try {
            eventLoop.execute(new Runnable() {	
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            ...
        }
    }
}

eventLoop.inEventLoop()判斷當前執行緒是否為EventLoop執行執行緒。
如果是,直接執行操作 -- 呼叫register0方法處理。
否則,提交一個任務給EventLoop。
這是Netty中提交非同步任務的通用格式,Netty中有大量類似程式碼。
注意,這裡是非同步的關鍵,將當前操作作為一個非同步任務,提交給EventLoop處理,而不需要阻塞當前執行緒。
EventLoop實際上是一個(jvm)EventExecutor,通過execute方法可以給它任務。

AbstractUnsafe#register0

private void register0(ChannelPromise promise) {
	try {
		if (!promise.setUncancellable() || !ensureOpen(promise)) {
			return;
		}
		boolean firstRegistration = neverRegistered;
		// #1
		doRegister();
		neverRegistered = false;
		registered = true;

		// #2
		pipeline.invokeHandlerAddedIfNeeded();
		// #3
		safeSetSuccess(promise);
		// #4
		pipeline.fireChannelRegistered();
		// #5
		if (isActive()) {
			if (firstRegistration) {
				pipeline.fireChannelActive();
			} else if (config().isAutoRead()) {
				beginRead();
			}
		}
	} catch (Throwable t) {
		// #6
		closeForcibly();
		closeFuture.setClosed();
		safeSetFailure(promise, t);
	}
}

#1 由子類實現具體註冊操作
#2 執行DefaultChannelPipeline中的延遲任務
#3 設定promise狀態為Success
#4 觸發ChannelPipeline#fireChannelRegistered
#5 如果是首次註冊,觸發ChannelPipeline#fireChannelActive
isActive()方法判斷當前Channel是否活躍
NioSocketChannel中呼叫SocketChannel#isOpen和SocketChannel#isConnected判斷
NioServerSocketChannel中呼叫SelectableChannel#isOpen和ServerSocket#isBound方法判斷
#6 異常處理,關閉Channel,設定promise狀態為Failure。

AbstractUnsafe#doRegister -> AbstractNioChannel#doRegister

protected void doRegister() throws Exception {
	boolean selected = false;
	for (;;) {
		try {
			// #1
			selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
			return;
		} catch (CancelledKeyException e) {
			...
		}
	}
}

#1 javaChannel()獲取(jvm)SelectableChannel,
eventLoop().unwrappedSelector()獲取AcceptGroup維護的Selector(jvm)
這裡將(jvm)ServerSocketChannel註冊到(jvm)Selector,但還沒有註冊關注事件Key。
從Netty層面看,將Channel註冊到EventLoop中。
注意,這裡將當前NioServerSocketChannel作為channle#attachment,後面使用它來判斷是否為IO事件。

AbstractUnsafe#register0方法#5步驟 -> DefaultChannelPipeline#fireChannelActive -> HeadContext#channelActive
這裡涉及ChannelPipeline的事件傳播,後面解析ChannelPipeline時詳細說明。

HeadContext#channelActive會呼叫readIfIsAutoRead方法,判斷是否開啟autoRead,開啟則自動觸發read事件處理方法。
HeadContext#readIfIsAutoRead -> DefaultChannelPipeline#read -> HeadContext#read -> AbstractUnsafe#beginRead -> AbstractNioChannel#doBeginRead

protected void doBeginRead() throws Exception {
	// #1
	final SelectionKey selectionKey = this.selectionKey;
	if (!selectionKey.isValid()) {
		return;
	}

	readPending = true;

	final int interestOps = selectionKey.interestOps();
	// #2
	if ((interestOps & readInterestOp) == 0) {
		selectionKey.interestOps(interestOps | readInterestOp);
	}
}

#1 selectionKey是Selector中關注事件集合(由AbstractNioChannel#doRegister方法中生成)
#2 這裡註冊了關注事件readInterestOp。
那麼readInterestOp的值是什麼呢? 它在AbstractNioChannel#構造方法中賦值,真正的值來自NioServerSocketChannel構造方法,可以看到,它在ServerChannel中固定為SelectionKey.OP_ACCEPT。
到這裡,註冊ServerChannel的關注事件OP_ACCEPT。
這裡完成NIO網路通訊第二步,註冊關注事件。

AbstractBootstrap.doBind0 -> AbstractChannel#bind -> DefaultChannelPipeline#bind -> HeadContext#bind -> AbstractUnsafe#bind -> 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());
	}
}

根據不同JDK版本,呼叫不同的bind方法。
這裡完成了NIO網路通訊第三步,分配套接字地址,開始socket監聽。

Accept事件處理

下面我們來看一下AcceptGroup中如何處理ServerChannel上監聽到的accept事件。

這裡涉及EventLoop的相關內容,後面有對應解析文章。
現在直接看Accept事件的處理方法NioMessageUnsafe#read

public void read() {
	...
	try {
		try {
			do {
				// #1
				int localRead = doReadMessages(readBuf);
				if (localRead == 0) {
					break;
				}
				if (localRead < 0) {
					closed = true;
					break;
				}

				allocHandle.incMessagesRead(localRead);
				
			} while (allocHandle.continueReading());
		} catch (Throwable t) {
			exception = t;
		}

		int size = readBuf.size();
		for (int i = 0; i < size; i ++) {
			readPending = false;
			// #2
			pipeline.fireChannelRead(readBuf.get(i));
		}
		readBuf.clear();
		allocHandle.readComplete();
		// #3
		pipeline.fireChannelReadComplete();

		...
	} ...
}

#1 呼叫NioServerSocketChannel#doReadMessages,處理Accept事件。
注意,readBuf是一個List<Object>,用於接收處理結果。

allocHandle.continueReading(),判斷是否需要繼續執行,這裡都是返回false
#2 觸發DefaultChannelPipeline#fireChannelRead
#3 觸發DefaultChannelPipeline#fireChannelReadComplete

NioServerSocketChannel#doReadMessages

protected int doReadMessages(List<Object> buf) throws Exception {
	// #1
	SocketChannel ch = SocketUtils.accept(javaChannel());

	try {
		if (ch != null) {
			// #2
			buf.add(new NioSocketChannel(this, ch));
			return 1;
		}
	} catch (Throwable t) {
		...
	}

	return 0;
}

#1 呼叫(jvm)ServerSocketChannel#accept方法,生成的(jvm)SocketChannel
#2 使用(jvm)SocketChannel構造NioSocketChannel

前面說過,ServerChannel註冊到AcceptGroup時,會給ServerChannel的ChannelPipeline新增一個ServerBootstrapAcceptor,用於處理accept事件。
NioMessageUnsafe#read方法#2步驟 -> DefaultChannelPipeline#fireChannelRead -> ServerBootstrapAcceptor#channelRead

public void channelRead(ChannelHandlerContext ctx, Object msg) {
	// #1
	final Channel child = (Channel) msg;

	child.pipeline().addLast(childHandler);

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

	try {
		// #2
		childGroup.register(child).addListener(new ChannelFutureListener() {
			public void operationComplete(ChannelFuture future) throws Exception {
				if (!future.isSuccess()) {
					forceClose(child, future.cause());
				}
			}
		});
	} catch (Throwable t) {
		forceClose(child, t);
	}
}

#1 注意msg引數,就是NioServerSocketChannel#doReadMessages方法中生成的NioSocketChannel。
上面說了,ServerBootstrap#init方法中會將ServerBootstrap中SocketChannel相關配置交給ServerBootstrapAcceptor。
這裡配置NioSocketChannel的Options,Attribute,並將childHandler新增給pipeline。
#2 將NioSocketChannel註冊到ReadGroup中,註冊過程類似於NioServerSocketChannel註冊到AcceptGroup,呼叫AbstractUnsafe#register方法實現。
但有一點不同,呼叫AbstractNioChannel#doBeginRead方法註冊關注事件時,關注事件(即AbstractNioChannel#readInterestOp),是來自子類AbstractNioByteChannel#構造方法,固定為SelectionKey.OP_READ。
到這裡,(jvm)SocketChannel已經註冊到ReadGroupo維護中(jvm)Selector,關注的事件Key為read。

延遲任務

前面說了,ServerBootstrap#init方法#2步驟中ChannelInitializer#initChannel方法由延遲任務觸發。現在看一下延遲任務的實現。

新增延遲任務
DefaultChannelPipeline#addFirst

public final ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler) {
	final AbstractChannelHandlerContext newCtx;
	synchronized (this) {
		checkMultiplicity(handler);
		name = filterName(name, handler);
		// #1
		newCtx = newContext(group, name, handler);
		addFirst0(newCtx);
		// #2
		if (!registered) {
			newCtx.setAddPending();
			callHandlerCallbackLater(newCtx, true);
			return this;
		}
		// #3
		EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
	}
	callHandlerAdded0(newCtx);
	return this;
}

#1 構造一個ChannelHandlerContext並新增到攔截連結串列首部位置
#2 當前Channel未註冊,呼叫DefaultChannelPipeline#callHandlerCallbackLater,新增一個延遲任務
#3 當前Channel已註冊,呼叫DefaultChannelPipeline#callHandlerAdded0,完成ChannelHandler新增擴充套件操作。

DefaultChannelPipeline#callHandlerCallbackLater方法,將當前ChannelHandlerContext轉化為一個延遲任務PendingHandlerAddedTask或者PendingHandlerRemovedTask,加到DefaultChannelPipeline#pendingHandlerCallbackHead列表中。
DefaultChannelPipeline#addLast/removeFirst/removeLast同樣有類似處理延遲任務的邏輯。

執行延遲任務
AbstractUnsafe#register0方法#2步驟 -> DefaultChannelPipeline#callHandlerAddedForAllHandlers,該方法會執行pendingHandlerCallbackHead列表所有任務,呼叫其execute方法。
PendingHandlerAddedTask#execute會呼叫ChannelHandler#handlerAdded,完成ChannelHandler新增擴充套件工作。
PendingHandlerRemovedTask#execute則呼叫ChannelHandler#handlerRemoved,完成ChannelHandler移除善後工作。

ServerBootstrap#init方法#2步驟給ServerChannel的ChannelPipeline新增一個ChannelInitializer,它是Netty提供的工具類,實現了ChannelHandler#handlerAdded方法,實現邏輯是如果當前Channel已註冊,則呼叫initChannel方法,否則不處理(所以我們常常利用該介面在註冊完成後新增新的ChannelHandler給ChannelHandler)。

回到ServerBootstrap#init方法,由於該方法執行時Channel未註冊,所以會生成延遲任務,由AbstractUnsafe#register0方法#2步驟觸發完成實際操作,將ServerBootstrapAcceptor新增到ServerChannel的ChannelPipeline中。

最後說一下本文提到的netty元件。
Channel,通訊通道,是Netty通訊的基礎元件。
EventLoop,ChannelPipeline是Netty中比較重要的元件,後面有對應的文章解析。
Unsafe負責實際資料傳輸工作,在解析Netty流程時會註解介紹它。
ChannelFuture,ChannelPromise代表Netty非同步IO結果,通過回撥函式執行後續操作。

如果您覺得本文不錯,歡迎關注我的微信公眾號。您的關注是我堅持的動力!

相關文章