Netty框架核心內容
豐富的Buffer數據結構
Netty在NIO的ByteBuffer基礎上自定義一套自己的Buffer API,其實現的Buffer API具備以下特性:
- 如果需要可以自定義自己的buffer類型
- 內建composite buffer類型實現零拷貝機制(無需在內存實現數據複製)
- 可以支持動態擴容,類似於StringBuffer
- 不需要像NIO的Buffer需要每次調用flip(),Netty實現的Buffer通過readerIndex以及writeIndex避免flip()調用
- 通常會比ByteBuffer在性能上更好
零拷貝示例,通過分割與組合:
// 假設buffer1以及buffer2都存儲在堆外內存,堆內內存同理(只是在JVM中)
ByteBuf httpHeader = buffer1.silice(OFFSET_PAYLOAD, buffer1.readableBytes() - OFFSET_PAYLOAD);
ByteBuf httpBody = buffer2.silice(OFFSET_PAYLOAD, buffer2.readableBytes() - OFFSET_PAYLOAD);
ByteBuf http = ChannelBuffers.wrappedBuffer(httpHeader, httpBody);
上述的零拷貝示意圖如下:
使用異步IO的API
- 基於TCP/IP的NIO模式
- 基於TCP/IP的BIO模式
- 基於UDP/IP的BIO模式
基於事件驅動設計的責任鏈設計
支持特性組件,簡化開發
- 支持SSL/TLS安全協議
- HTTP/WebSocket協議的實現
- 支持Google的Protocol協議
- 支持編解碼器,Netty也有內置一些編解碼器實現
Netty線程模型
講述Netty線程模型之前,摘錄Netty官網的描述:
The Netty project is an effort to provide an asynchronous event-driven network application framework and tooling for the rapid development of maintainable high-performance and high-scalability protocol servers and clients.
從上述官網可得到以下的信息:
- Netty是基於EDA事件驅動架構設計實現
- Netty採用異步方式來完成事件通知,完成事件之後會進行回調喚醒Handler,可以理解爲Proactor模式,或者是多線程異步執行的Reactor模型.
- Netty是一個高性能高擴展性的web服務框架,可以在不影響性能情況快速實現web服務的開發
在Netty組件分析中,EventLoop是一個線程池執行器,同時對於長時間的耗時Handler操作,會額外分配一個線程池執行器來負責處理handler相關的業務邏輯,於是基於Reactor/Proactor模型可知,Netty的一個線程模型如下:
Netty事件流程
基於上述的Netty線程模型的理解,現摘錄官網的一個EchoServer例子來深入分析Netty事件流程,對應的EchoServer代碼示例如下:
// 僅摘錄部分核心代碼
// main方法
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
final EchoServerHandler serverHandler = new EchoServerHandler();
try {
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();
//p.addLast(new LoggingHandler(LogLevel.INFO));
// 將處理讀寫事件的handler添加到責任鏈中
p.addLast(serverHandler);
}
});
// Start the server.
ChannelFuture f = b.bind(PORT).sync();
// Wait until the server socket is closed.
f.channel().closeFuture().sync();
} finally {
// Shut down all event loops to terminate all threads.
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ctx.write(msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
// Close the connection when an exception is raised.
cause.printStackTrace();
ctx.close();
}
}
根據上述的代碼示例,其運作的核心流程如下:
- 初始化Boss以及Worker的事件輪詢NioEventLoop線程,即Reactor線程
- 創建服務啓動類ServerBootsrtap
- 將Boss以及Worker的Reactor線程添加到服務啓動類中
- 服務啓動類創建並註冊服務端的Channel
- 服務啓動類爲服務端的Channel配置可選屬性
- 服務啓動類添加一個通用共享且日誌級別爲INFO的處理器並應用在整個Netty的pipeline
- 基於之前的Channel組件分析,我們知道childChannel也就是對應客戶端的SocketChannel,這個時候是服務啓動類爲SocketChannel添加一個初始化的Handler,並在後續基於事件觸發完成之後執行責任鏈下的handler回調
- 至此,一系列的初始化操作完成,這個時候服務啓動類開始爲ServerChannel綁定端口開始對客戶端連接進行監聽
- 關閉連接的監聽以及銷燬Reactor線程釋放空間
對此,基於上述給出的完整服務端流程,現對上述流程結合源碼進行分析與總結.
EventLoopGroup與EventLoop初始化流程
EventLoopGroup初始化流程
- NioEventLoopGroup類圖結構
- EventLoopGroup初始化源碼
// NioEventLoopGroup構造器
// NioEventLoopGroup.java
public NioEventLoopGroup(int nThreads) {
this(nThreads, (Executor) null);
}
public NioEventLoopGroup(int nThreads, Executor executor) {
this(nThreads, executor, SelectorProvider.provider());
}
public NioEventLoopGroup(
int nThreads, Executor executor, final SelectorProvider selectorProvider) {
// 默認使用阻塞式輪詢策略
this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
}
public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,final SelectStrategyFactory selectStrategyFactory) {
super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject()); // channel處理不過來的時候直接丟棄
}
// MultithreadEventLoopGroup.java
protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
// 默認線程數量爲CPU*2 或者是通過 io.netty.eventLoopThreads進行配置
super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
}
// MultithreadEventExecutorGroup.java
protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
// 創建默認事件執行選擇器(從Group中選擇一個EventLoop來處理Channel的策略選擇器)
this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}
// 初始化Group的核心方法
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
}
// 上述可以看到初始化創建默認具備線程池的一些默認策略(線程大小/線程工廠/存儲任務隊列/丟棄策略)/創建默認的事件輪詢選擇器/默認的IO複用器提供者
- EventLoopGroup初始化的核心流程
根據上述可以知道,EventLoopGroup初始化的操作主要是初始化一組EventLoop的執行器,並創建選舉EventLoop的選擇器,併爲每個EventLoop在銷燬的時候添加監聽器以便於程序能夠獲取當前EventLoop銷燬情況,同時每個EventLoop對外提供服務都是隻讀模式,也就是選舉EventLoop都是處於只讀的穩定版本.
EventLoop的創建流程
EventLoop的創建流程包含在上述EventLoopGroup爲每個執行器(EventLoop)進行初始化的過程,即在源代碼中如下:
// MultithreadEventExecutorGroup.java
// 初始化執行器
children[i] = newChild(executor, args);
// newChild的實現子類NioEventLoopGroup
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
}
// NioEventLoop
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
EventLoopTaskQueueFactory queueFactory) {
super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
rejectedExecutionHandler);
this.provider = ObjectUtil.checkNotNull(selectorProvider, "selectorProvider");
this.selectStrategy = ObjectUtil.checkNotNull(strategy, "selectStrategy");
final SelectorTuple selectorTuple = openSelector();
this.selector = selectorTuple.selector;
this.unwrappedSelector = selectorTuple.unwrappedSelector;
}
EventLoop的初始化流程
基於上述的EventLoopGroup與EventLoop的認知,我們來總結下EventLoopGroup,EventLoop,EventExecutor以及Thread之間的關係,首先先從源碼開始分析如下:
// MultithreadEventExecutorGroup.java
// ThreadPerTaskExecutor看成線程池 - 對應默認的線程工廠類
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
// 有多少個線程就有多少個EventLoop
children = new EventExecutor[nThreads];
// 用線程池創建EventLoop
children[i] = newChild(executor, args);
// SingleThreadEventExecutor.java
// this爲NioEventLoop
this.executor = ThreadExecutorMap.apply(executor, this);
// ThreadExecutorMap.java
// 創建新的執行器
public static Executor apply(final Executor executor, final EventExecutor eventExecutor) {
// check not null ...
return new Executor() {
@Override
public void execute(final Runnable command) {
// ThreadPerTaskExecutor.execute -> apply
// 啓動一個線程執行任務並傳遞事件輪詢器
// FastThreadLocalThread.run()
executor.execute(apply(command, eventExecutor));
}
};
}
// 新任務Task
public static Runnable apply(final Runnable command, final EventExecutor eventExecutor) {
ObjectUtil.checkNotNull(command, "command");
ObjectUtil.checkNotNull(eventExecutor, "eventExecutor");
return new Runnable() {
@Override
public void run() {
// 將EventLoop存儲到FastThreadLocal(即保證FastThreadLocalThread獨佔持有自己的EventLoop)
setCurrentEventExecutor(eventExecutor);
try {
// 執行任務
command.run();
} finally {
// 任務執行完之後釋放獨佔EventLoop的資源
setCurrentEventExecutor(null);
}
}
};
}
private static void setCurrentEventExecutor(EventExecutor executor) {
// 使用FastThreadLocal來存儲事件輪詢器,保證每個事件輪詢器都會有對應的一個線程來處理
mappings.set(executor);
}
// 通過上述的流程可知,在每個EventLoop都含有一個新的Executor
// 而每一個Executor都通過默認的線程工廠創建一個FastThreadLocalThread線程來處理task任務
// 此時的Task任務爲一個新的任務task
通過源碼分析,可以得到以下簡要的EventLoopGroup,Group下的線程池Executor,EventLoop與EventLoop下的Executor以及Thread之間的關係如下:
通過上述示意圖可知,每個EventLoop處理任務時都會通過Group下的Executor來創建對應的線程來執行EventLoop的事件任務,並且爲了保證併發安全問題,在每次處理任務之前,將會把當前的EventLoop與Thread進行綁定,也就是當前EventLoop爲當前執行的線程Thread所獨佔持有,通過FastThreadLocal來維護兩者之間的關係,一旦EventLoop事件任務處理完成之後,將解除兩者的綁定.同時也可以看到處理一組事件任務的Thread將通過線程組的方式進行維護和管理.
Netty線程模型細化
可以看到上述一個EventLoop綁定一個專有的線程,由專有的線程負責處理EventLoop的事件,且一個channel都會對應着一個EventLoop來負責處理channel相關的事件,同時一個EventLoop/Thread能夠處理多個Channel需要依賴於AIO或者是NIO的API才能實現,AbstractBootstrap處理服務端Channel,ServerBootstrap處理客戶端Channel,而對於BIO模型而言,只能一個EventLoop/Thread處理對應一個Channel,即摘錄《Netty實戰》關於NIO/OIO(old IO,BIO)模型如下:
- 基於NIO/AIO的線程模型
- 基於BIO的線程模型(OIO爲old IO,即使用BIO的API)
- EventLoop啓動任務的執行源碼
// 調用以下的方法時執行流程
// SingleThreadEventExecutor.java
eventLoop.execute(task);
// 這裏的線程執行流程不弄清楚,後面的事件流程將很理解
// 根據類設計可知,execute爲SingleThreadEventExecutor下的方法,結合上面的EventLoop初始化流程可知,每個EventLoop都擁有一個內置的Executor,而這個Executor用於創建FastThreadLocalThread線程來保證當前eventloop與當前線程之間的綁定關聯,源碼如下:
private void execute(Runnable task, boolean immediate) {
// 判斷當前執行的線程是否與eventloop對應(EventLoop - Thread綁定一起)
boolean inEventLoop = inEventLoop();
// 將任務添加到隊列中,如果隊列滿則丟棄當前任務
addTask(task);
if (!inEventLoop) {
// 啓動一個線程,如果當前EventLoop持有的線程已經開啓過則直接跳過,如果開啓過線程,則執行doStartThread方法
startThread();
// ...
}
private void doStartThread(){
// EventLoop持有的executor來創建一個FastThreadLocalThread線程,在該線程中保證當前事件輪詢器與線程處於線程安全,通過FastThreadLocal將線程與EventLoop進行關聯
executor.execute(new Runnable() {
//....
});
}
結合EventLoop初始化對應的executor以及ThreadExecutorMap中的源碼,現將一個不在當前線程的EventLoop提交任務時創建一個完整線程執行細節流程繪製如下:
也就是說,最終處理任務task都在NioEventLoop執行的run方法中體現,或者更爲嚴格意義上來取決於我們選擇的EventLoop的IO操作模式,具體是交由EventLoop的IO操作模式的run方法通過隊列中獲取任務來進行處理,於是根據源碼中提供的任務隊列與拒絕策略,對於EventLoop處理任務的流程如下(摘錄自《Netty實戰》):
- 與線程池不一樣的是,EventLoop是與指定的線程綁定在一起,也就是一個線程處理一個EventLoop,並且在整個Web服務中EventLoop始終是由當前的專有線程負責事件的任務的處理
- 當添加任務到EventLoop執行的時候,需要校驗當前的線程是不是持有之前分配好的EventLoop,如果不是那麼就添加到任務隊列進行等待EventLoop下一次處理事件時再執行,如果隊列滿了,那麼此時就會觸發拒絕策略丟棄任務,如果是之前分配好的EventLoop那麼就會直接執行任務Task.
Netty之NIO事件輪詢流程
基於上述的線程任務流程分析之後,我們知道在EventLoop中最終會調用NioEventLoop下的run方法,對此,現該run方法執行的事件輪詢操作流程進行分析.
- 事件輪詢源碼
// NioEventLoop.run()核心代碼
for(;;){
// 檢測當前的EventLoop的隊列中是否有任務
if (!hasTasks()) {
strategy = select(curDeadlineNanos);
}
// 根據服務器配置eventloop的IO處理能力比率
if (ioRatio == 100) {
// 如果IO處理比率高,則同時處理就緒事件以及當前輪詢器隊列中的所有任務
// 不然就分開處理
try {
if (strategy > 0) {
// 處理一系列的就緒事件
processSelectedKeys();
}
} finally {
// Ensure we always run tasks.
// 執行所有的任務
ranTasks = runAllTasks();
}
} else if (strategy > 0) {
final long ioStartTime = System.nanoTime();
try {
// 處理就緒事件,處理ACCEPT/READ/WRITE事件
processSelectedKeys();
} finally {
// 在一定事件內處理隊列中的任務
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
ranTasks = runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
} else {
// 處理任務
ranTasks = runAllTasks(0); // This will run the minimum number of tasks
}
}
// NioEventLoop的unsafe爲NioMessageUnsafe
processSelectedKeys(){
int readyOps = k.readyOps();
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
}
// runAllTasks
runAllTasks(){
do {
fetchedAll = fetchFromScheduledTaskQueue();
if (runAllTasksFrom(taskQueue)) {
ranAtLeastOne = true;
}
} while (!fetchedAll);
}
protected final boolean runAllTasksFrom(Queue<Runnable> taskQueue) {
Runnable task = pollTaskFrom(taskQueue);
if (task == null) {
return false;
}
for (;;) {
// 在當前EventLoop所在的線程執行run方法
// task.run();
safeExecute(task);
task = pollTaskFrom(taskQueue);
if (task == null) {
return true;
}
}
}
- 事件輪詢流程圖
SeverBootstrap初始化流程
Netty組件初始化流程
在分析啓動類初始化之前,我們先來查看下啓動類的類圖結構設計
根據類圖結構,啓動類比較簡單,同時關於啓動類的詳細已經在先前組件分析過,現來看下啓動類初始化Netty相關組件的源碼
- EventLoopGroup添加到啓動類
// ServerBootsrtap.java
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
return this;
}
// AbstractBootstrap.java
// AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel>>
public B group(EventLoopGroup group) {
ObjectUtil.checkNotNull(group, "group");
if (this.group != null) {
throw new IllegalStateException("group set already");
}
this.group = group;
return self();
}
// 通過上述可知並結合多Reactor模式可知
// ServerBootstrap持有childGroup,用於處理socketChannel的讀寫事件
// AbstractBootstrap持有parentGroup,用於處理serverChannel的accept事件
- 將服務端的Channel類對象以及服務端配置添加到啓動類中
// 入口函數
// 創建一個服務端的ServerChannel並指定其BACKLOG大小爲100
bootstrap.channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 100);
// 源代碼
// AbstractBootstrap.java
// 通過傳遞的服務端Channel構造一個Channel創建工廠類,用於後續構建服務端的Channel
public B channel(Class<? extends C> channelClass) {
return channelFactory(new ReflectiveChannelFactory<C>(
ObjectUtil.checkNotNull(channelClass, "channelClass")
));
}
// 服務端Channel的配置存儲到容器Map中
public <T> B option(ChannelOption<T> option, T value) {
ObjectUtil.checkNotNull(option, "option");
synchronized (options) {
if (value == null) {
options.remove(option);
} else {
options.put(option, value);
}
}
return self();
}
- 將處理服務端的Handler添加到啓動類中
// 入口程序類
bootstrap.handler(new LoggingHandler(LogLevel.INFO));
// 源碼
// AbstractBootstrap.java
// 當前的服務端channelHandler存在於AbstractBootstrap
public B handler(ChannelHandler handler) {
this.handler = ObjectUtil.checkNotNull(handler, "handler");
return self();
}
- 將處理客戶端channelHandler添加到啓動類
由於服務端程序啓動服務端Channel之後會監聽客戶端SocketChannel的連接,一旦有連接進來這個時候就會註冊綁定客戶端的SocketChannel並監聽讀寫事件,對此,對於使用Netty框架的服務端程序而言只需要關注處理讀寫事件的Handler即可,由於先前在組件源碼分析中已經說明到,ServerChannel是作爲客戶端SocketChannel的語義層次上的父類,於是對於handler我們也可以理解childHandler是處理客戶端讀寫事件的handler的處理器,其對應的源碼如下:
// 入口類程序
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
// 保證每一個socket channel都會對應着一個自己的channel handler
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(serverHandler);
}
});
// 源碼
// ServerBootstrap.java
// 將上述的childHandler綁定到ServerBootstrap,爲ServerBootstrap所持有
public ServerBootstrap childHandler(ChannelHandler childHandler) {
this.childHandler = ObjectUtil.checkNotNull(childHandler, "childHandler");
return this;
}
- Bootstrap初始化
基於上述添加EventLoopGroup以及ChannelHandler,我們再來看下Bootsrtap啓動類初始化的時候做了什麼事情.
// 入口程序
new ServerBootstrap();
// 在上述執行初始化流程中,會在內部完成以下組件的初始化
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);
結合之前組件分析,我們知道Channel是存在語義上的層次關係,對此,我們關注ServerBootstrap與ServerBootstrapConfig, AbstractBootstrap與AbstractBootstrapConfig之間分別獲取channel信息的區分,其類圖組件如下:
通過上述的類圖可以知道,ServerBootsrtap與SocketChannel進行關聯,AbstractServerBootstrap與ServerSocketChannel進行關聯,對於channel,ServerSocketChannel與SocketChannel是層次上的父子關係,對於Bootsrap類抑或是Config類,均通過子類獲取與SocketChannel相關的信息,通過父類獲取與ServerSocketChannel相關信息,層次劃分明確,現將Bootstrap構造初始化操作事件流程繪製如下:
我們知道在Netty框架在處理服務端與客戶端的事件是劃分層次的,在語義層次上,服務端屬於“父類”,客戶端屬於“子類”,兩者之間的事件所依賴的組件也在語義上劃分層次,對此,結合上述對EventLoopGroup與EventLoop的源碼分析,現將啓動類Bootstrap,EventLoopGroup,EventLoop,Channel以及Thread之間的關聯示意圖繪製如下:
啓動類綁定端口事件流程
入口程序源碼分析如下:
// 入口程序
bootsrtap.bind(PORT);
// AbstractBootstrap.java
// 通過類名稱可知是創建服務端的Channel並註冊Channel事件實現對客戶端Channel連接的監聽
public ChannelFuture bind(int inetPort) {
return bind(new InetSocketAddress(inetPort));
}
// bind包括: 創建channel -> 初始化channel -> 註冊channel -> channel綁定端口操作
doBind(final SocketAddress localAddress){
// 由於註冊綁定流程複雜,這裏將綁定註冊流程劃分出來,摘錄核心方法,Netty框架中使用EventLoop來處理每個channel事件,存在多線程異步執行的情況.對於異步返回的結果ChannelFuture已在Netty組件源碼分析說明到,這裏不再詳述
// 初始化並註冊服務端的channel
initAndRegister();
//...
//如果註冊成功,執行服務端channel的綁定操作
doBind0(regFuture, channel, localAddress, promise);
}
服務端channel初始化與註冊事件
- 創建服務端channel的流程
// 源代碼
// 使用channelFactory創建NioServerSocketChannel實例
channel = channelFactory.newChannel();
NioServerSocketChannel
類圖結構設計如下:
這個時候再來看創建NioServerSocketChannel
實例都做了哪些事情
public NioServerSocketChannel() {
// 創建java的nio下的ServerSocketChannel並傳遞到當前的NioServerSocketChannel構造器中
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
public NioServerSocketChannel(ServerSocketChannel channel) {
// 服務端監聽Accept事件並保存,後續在進行註冊的時候將會使用到OP_ACCEPT
// 1.設置channel的父類,如果當前爲服務端的channel則爲null
// 2.創建channelId
// 3.創建Nio的Unsafe類
// 4.創建channel的責任鏈pipeline,同時每個pipeline都會創建一個雙端鏈表連接上下文對象
super(null, channel, SelectionKey.OP_ACCEPT);
// 1. 爲當前的channel創建接收數據的ByteBuff分配器,即AdaptiveRecvByteBufAllocator,該分配器默認從1024kb開始創建緩衝區分配數據,最小爲64kb,最多不超過65536kb
// 2. 保存java對象的ServerSocketChannel
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
根據上述源碼可知,創建Channel時會將與操作Channel相關的組件也一起完成初始化操作,即創建操作緩衝區數據的Unsafe以及對緩衝區數據進行讀寫存儲的ByteBuff分配器.
- 服務端channel的初始化流程
// 基於上述完成channel的創建,接下來對channel進行初始化操作
// 對應的源碼
// ServerBootstrap.java
// 1. 爲當前的channel設置option以及attributes
// 2. 獲取當前channel的責任鏈,爲當前的責任添加初始化handler處理器
init(channel);
// init下初始化handler核心代碼
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
// 獲取服務端channel的handler處理類
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
// 在channel所在的eventloop創建一個線程來執行任務
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
// 在任務下爲服務端的channel添加Acceptor處理器負責處理客戶端channel連接進來的事件完成處理
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
- 服務端channel的註冊流程
// 源碼
// AbstractBootstrap.java
// 獲取boss NioEventLoopGroup,將channel註冊到當前的group下
ChannelFuture regFuture = config().group().register(channel);
// 根據NioEventLoopGroup的繼承類圖,可知register方法是在MultithreadEventLoopGroup下
// MultithreadEventLoopGroup.java
public ChannelFuture register(Channel channel) {
// 選舉一個EventLoop來註冊channel
// 在初始化Group操作的時候已經完成選擇器的初始化操作,這裏調用選擇器來選擇一個EventLoop
// 這裏調用EventLoop的註冊方法,在上述入口中使用NioEventLoop可知使用的register方法爲SingleThreadEventLoop類下的方法,最終調用AbstractChannel下的register方法
// 方法調用走向如下:
// MultithreadEventLoopGroup.regitser() -> SingleThreadEventLoop.regitser() -> promise.channel().unsafe().register() -> unsafe(NioMessageUnsafe).regitser() -> AbstractNioUnsafe.regitser() -> AbstractUnsafe.register() -> AbstractUnsafe.register0()
return next().register(channel);
}
//AbstractChannel.java下的AbstractUnsafe
register0(promise);
// 上述註冊方法的核心步驟:
// 1. 將channel註冊到複用器selector上
// 2. 註冊完成之後喚醒回調責任鏈下所有先前已加入的channelHandler類下的handlerAdd方法
// 3. 註冊完成之後將結果設置在promise中
// 4. 將註冊結果傳遞到責任鏈pipeline中,並執行回調channelHandler(ChannelInboundHandler)類下的channelRegistered方法,鏈式回調執行
// 5. 如果channel爲active狀態,則繼續傳播結果事件到channelHandler(ChannelInboundHandler)類下的channelActive方法,鏈式回調執行
// 6. 5步驟是在第一次進行註冊的時候會執行(表示channel已經打開),如果已經註冊過,那麼校驗會自動開始數據讀取操作,客戶端channel註冊讀取OP_READ操作, 對應服務端的Channel而言就是監聽客戶端socket的連接ACCEPT事件
在基於上述的線程執行任務細節基礎之上,將服務端的初始化並註冊流程示意圖流程繪製如下:
執行端口綁定與監聽操作
- 端口綁定源碼分析
// 上述channel註冊成功之後,這個時候在上面流程只會觸發Active事件,這個時候沒有綁定端口沒有觸發監聽事件
// AbstractBootstrap.java
doBind0(regFuture, channel, localAddress, promise);
private static void doBind0(
// channel所在的eventloop線程執行任務
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
// 註冊成功將channel進行綁定操作
if (regFuture.isSuccess()) {
//
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}
// AbstractChannel.java
@Override
public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
return pipeline.bind(localAddress, promise);
}
// DefaultChannelPipeline.java
@Override
public final ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
// 在鏈表尾部添加綁定操作
return tail.bind(localAddress, promise);
}
// AbstractChannelHandlerContext.java
@Override
public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
ObjectUtil.checkNotNull(localAddress, "localAddress");
if (isNotValidPromise(promise, false)) {
// cancelled
return promise;
}
// 搜索outboundContext上下文
final AbstractChannelHandlerContext next = findContextOutbound(MASK_BIND);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {
// 執行責任鏈pipeline 出站事件,從鏈表尾部開始搜索,因而最後的context是headContext
// 執行headContext下的invokeBind方法,該方法還是屬於當前類,對此查看下文
next.invokeBind(localAddress, promise);
} else {
safeExecute(executor, new Runnable() {
@Override
public void run() {
next.invokeBind(localAddress, promise);
}
}, promise, null, false);
}
return promise;
}
// AbstractChannelHandlerContext.java
private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
if (invokeHandler()) {
try {
((ChannelOutboundHandler) handler()).bind(this, localAddress, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
} else {
bind(localAddress, promise);
}
}
// headContext的綁定方法
@Override
public void bind(
ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) {
unsafe.bind(localAddress, promise);
}
// unsafe爲NioMessageUnsafe,執行該類下的bind方法(AbstractUnsafe.java中定義)
// 最後再執行channel下的doBind(localAddress);方法,即NioServerSocketChannel下的方法
@SuppressJava6Requirement(reason = "Usage guarded by java version check")
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
// 可以看到實現了端口的綁定操作
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
對此,基於上述源碼的分析,我們繪製服務端channel的端口綁定流程如下:
責任鏈創建執行流程
- 基於ChannelInitial來初始化pipeline的handler
// 添加handler的入口程序
// 這裏主要是撿重點說明
ChannelPipeline p = channel.pipeline();
p.addLast(new ChannelInitializer(){
// 在源碼中有如下說明:
// 一旦channel註冊將調用當前initChannel方法,方法執行完成之後將實例會從ChannelPipeline中移除
void initChannel(C ch) throws Exception{
final ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new ServerHandler());
// 使用EventLoop添加
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
// DefaultChannelPipeline.java
@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
return addLast(null, handlers);
}
@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
ObjectUtil.checkNotNull(handlers, "handlers");
for (ChannelHandler h: handlers) {
if (h == null) {
break;
}
addLast(executor, null, h);
}
return this;
}
@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
final AbstractChannelHandlerContext newCtx;
synchronized (this) {
checkMultiplicity(handler);
// 創建一個上下文對象,默認爲DefaultChannelHandlerContext
newCtx = newContext(group, filterName(name, handler), handler);
// head -> handler1 --- handler - tail
addLast0(newCtx);
if (!registered) {
// channel還沒有註冊,設置當前handler處於等待狀態
newCtx.setAddPending();
// 將其添加到等待鏈表的尾部中
callHandlerCallbackLater(newCtx, true);
return this;
}
EventExecutor executor = newCtx.executor();
if (!executor.inEventLoop()) {
callHandlerAddedInEventLoop(newCtx, executor);
return this;
}
}
callHandlerAdded0(newCtx);
return this;
}
// 最終會當channel完成註冊的時候會調用handlerAdd方法,而ChannelInitial的handlerAdd方法如下:
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) {
// 最終會調用initChannel方法
if (initChannel(ctx)) {
// 完成channel的初始化鏈會將當前實例移除
removeState(ctx);
}
}
}
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
if (initMap.add(ctx)) { // Guard against re-entrance.
try {
initChannel((C) ctx.channel());
} catch (Throwable cause) {
exceptionCaught(ctx, cause);
} finally {
// 最後會在pipeline鏈中刪除當前實例
ChannelPipeline pipeline = ctx.pipeline();
if (pipeline.context(this) != null) {
pipeline.remove(this);
}
}
return true;
}
return false;
}
-
責任鏈添加處理器handler流程
-
channel事件與責任鏈生命週期聯繫
通過上述可知,channel在註冊前後過程中的pipeline存儲的handler結構爲:
服務端接收客戶端連接的事件流程
根據EventLoop的事件輪詢流程可知,服務端監聽的事件變化以及事件轉發處理都在EventLoop.run方法中,對此,我們詳細看下Netty的服務端是如何接收並處理客戶端的連接事件以及對應的事件流程是如何的.
- Netty框架下的核心事件輪詢run方法源代碼
// 上述已經貼有源碼,這裏我們更關注細節問題,僅列出run方法的核心源碼
// NioServerSocketChannel.java
run(){
// 服務端不斷輪詢監聽事件
for (;;) {
// 執行select操作
if (!hasTasks()) {
strategy = select(curDeadlineNanos);
}
// 處理就緒事件
processSelectedKeys();
// 處理任務隊列中的任務
ranTasks = runAllTasks();
}
}
// 既然關注ACCEPT事件,這個時候我們需要知道服務端Channel在創建註冊並綁定的時候初始化handler並將Acceptor添加到handler中,對此我們追蹤下bind方法,最後查閱代碼到init方法,其代碼如下:
void init(Channel channel){
// 在先前分析可知,這裏已經完成了channel的創建,且此時channel爲NioServerSocketChannel事件
ChannelPipeline p = channel.pipeline();
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
// 獲取服務端channel的handler處理類
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));
}
});
}
});
}
根據上述源代碼,服務端監聽連接事件的流程如下:
其中關於創建和註冊流程基本和上述服務端channel一致,這裏繪製的時候直接簡化,沒有詳細繪製出來.
服務端處理客戶端channel的讀寫事件流程
- 讀寫事件源代碼
// 在這裏關注的讀寫事件是NioSocketChannel
void processSelectedKey(){
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
ch.unsafe().forceFlush();
}
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
}
}
// NioSocketChannel使用NioByteUnsafe來實現讀寫
// 先讀後寫
// 讀取流程
// AbstractNioByteChannel.NioByteUnsafe
read(){
try{
do{
// 從socket中讀取數據
doReadBytes(byteBuff);
// 傳播數據
pipeline.fireChannelRead(byteBuf);
}while(continueReading())
pipeline.fireChannelReadComplete();
if(close){
closeOnRead(pipeline);
}
}catch (Throwable t) {
//傳播userEventTriggered
handleReadException(pipeline, byteBuf, t, close, allocHandle);
} finally {
if (!readPending && !config.isAutoRead()) {
// 取消讀取操作
removeReadOp();
}
}
}
// 寫出流程
// 寫出操作的觸發點是在某個handler下的channelRead方法下手動執行write或者writeAndFlush方法
// handler 通過addLast方法添加,默認上下文對象爲DefaultChannelHandlerContext
void handlerRead(AbstractHandlerContext ctx, Object msg){
// 執行寫操作的流程說明責任鏈執行當前入站事件handler已經是最後一個,從當前handler的上下文對象開始執行出站事件
ctx.writeAndFlush(msg);
}
// 執行鏈爲:
// AbstractChannelHandlerContext.writeAndFlush() - invokeWriteAndFlush -> handler.write() - head.write() - AbstractUnsafe.write() - filterOutboundMessage() - AbstractNioByteChannel.filterOutboundMessage()[創建一個堆外內存] - addMessage() -> incrementPendingOutboundBytes() -> [newWriteBufferSize > channel.config().getWriteBufferHighWaterMark()] -> setUnwritable -> fireChannelWritabilityChanged -> head.fireChannelWritabilityChanged -> handler.fireChannelWritabilityChanged -> tail.fireChannelWritabilityChanged
// -> invokeFlush0 -> handler.flush() -> head.flush() -> AbstractUnsafe.flush() -> addFlush() -> flush0() ->. NioSocketChannel.doWrite() -> ch.write(nioBuffers, 0, nioBufferCnt) -> incompleteWrite() -> setOpWrite()[註冊寫事件] ->
- 讀事件流程
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4MeofwI6-1587638895122)(https://raw.githubusercontent.com/xiaokunliu/xiaokunliu.github.io/feature/writing/websites/zimages/netty/event/channel_read.jpg)]
- 寫事件流程
ServerChannel/ChannelHandler/ChannelPipeline事件生命週期
結合之前組件的源碼以及上述的事件流程分析,關於channel事件與pipeline責任鏈回調執行生命週期流程總結如下: