本文通過閱讀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結果,通過回撥函式執行後續操作。
如果您覺得本文不錯,歡迎關注我的微信公眾號,後續提供系列文章pdf下載。您的關注是我堅持的動力!