Netty 源碼(ChannelHandler 死磕)

精進篇:netty源碼死磕5  - 揭開 ChannelHandler 的神祕面紗html

目錄

1. 前言
2. Handler在經典Reactor中的角色
3. Handler在Netty中的座標位置
4. Netty中Handler的類型

1.1. ChannelInboundHandler入站處理器
1.2. ChannelOutboundHandler出站處理器
5. 揭開Pipeline的神祕面紗
6. Handler的上下文環境

7. Handler的註冊
7.1. 第一步:包裹
7.2. 加入鏈表並註冊完成回調事件
7.3. 回調添加完成事件
8. 小結
編程


1. 前言

Reactor模式是Netty的基礎和靈魂,掌握了經典的Reactor模式實現,完全掌握Netty就事半功倍了。《Reactor模式(netty源碼死磕3)》對Reactor模式的經典實現,進行了詳細介紹。做爲本文的閱讀準備,能夠去溫習一下。設計模式

Reactor模式的兩個重要的組件,一個是Reactor反應器,在Netty中的對應的實現是EventLoop,在文章《EventLoop(netty源碼死磕4)》中,已經有了很是詳細的介紹。緩存

此文聚焦於Reactor模式的另外一個重要的組成部分Handler。網絡

2. Handler在經典Reactor中的角色

在Reactor經典模型中,Reactor查詢到NIO就緒的事件後,分發到Handler,由Handler完成NIO操做和計算的操做。數據結構

wps131D.tmp

Handler主要的操做爲Channel緩存讀、數據解碼、業務處理、寫Channel緩存,而後由Channel(表明client)發送到最終的鏈接終端。ide

3. Handler在Netty中的座標

經典的Reactor模式,更多在於演示和說明,僅僅是有一種濃縮和抽象。函數

因爲Netty更多用於生產,在實際開發中的業務處理這塊,主要經過Handler來實現,因此Netty中在Handler的組織設計這塊,遠遠比經典的Reactor模式實現,要紛繁複雜得多。oop

wps133D.tmp


在分析Handler以前,首先回顧一下Netty中的Channel。在《EventLoop(netty源碼死磕4)》中,已經有詳細的說明。一個Netty Channel對應於一個Client鏈接,內部封裝了一個Java NIO SelectableChannel 可查詢通道。學習

再回到Handler。

Hander的根本使命,就是處理Channel的就緒事件,根據就緒事件,完成NIO處理和業務操做。比方Channel讀就緒時,Hander就開始讀;Channel寫就緒時,Hander就開始寫。

4. Netty中Handler的類型

從應用程序開發人員的角度來看,Netty的主要組件是ChannelHandler,因此,對ChannelHandler的分類,也是從應用開發的角度來的。

wps134E.tmp

從應用程序開發人員的角度來看,數據有入站和出站兩種類型。

這裏的出站和入站,不是網絡通訊方面的入站和出站。而是相對於Netty Channel與Java NIO Channel而言的。

數據入站,指的是數據從底層的Java NIO channel到Netty的Channel。數據出站,指的是經過Netty的Channel來操做底層的 Java NIO chanel。

從入站和出戰的角度出發,Netty中的ChannelHandler主要由兩種類型,ChannelInboundHandler和ChannelOutboundHandler。

1.1. ChannelInboundHandler入站處理器

當Java NIO事件進站到Channel時,產生一的一系列事件將由ChannelHandler所對應的API處理。

當查詢到Java NIO底層Channel的就緒事件時,經過一系列的ChannelInboundHandler處理器,完成底層就緒事件的處理。比方說底層鏈接創建事件、底層鏈接斷開事件、從底層讀寫就緒事件等等。

wps136E.tmp


囉嗦一下,入站(inbound)處理一般由底層Java NIO channel觸發,主要事件以下:

1. 註冊事件 fireChannelRegistered。

2. 鏈接創建事件 fireChannelActive。

3. 讀事件和讀完成事件 fireChannelRead、fireChannelReadComplete。

4. 異常通知事件 fireExceptionCaught。

5. 用戶自定義事件 fireUserEventTriggered。

6. Channel 可寫狀態變化事件 fireChannelWritabilityChanged。

7. 鏈接關閉事件 fireChannelInactive。


1.2. ChannelOutboundHandler出站處理器


當須要Netty Channel須要操做Java NIO底層Channel時,經過一系列的ChannelOutboundHandler處理器,完成底層操做。比方說創建底層鏈接、斷開底層鏈接、從底層Java NIO通道讀入、寫入底層Java NIO通道等。ChannelOutboundHandler是一個接口,主要操做以下圖所示:

wps137F.tmp



囉嗦一下,出站(inbound) Handler一般是Netty channel操做底層Java NIO channel,主要操做以下:

1. 端口綁定 bind。

2. 鏈接服務端 connect。

3. 寫事件 write。

4. 刷新時間 flush。

5. 讀事件 read。

6. 主動斷開鏈接 disconnect。

7. 關閉 channel 事件 close。

至此,Netty中的兩大處理器的類型,就已經說得很清楚了。

再說說Handler和Channel的關係。

打個比方,若是Hander是太陽系的行星,那麼Channel就是太陽系的恆星。Hander的服務對象和公轉的軸心,就是Channel。

這多是最爲不恰當的一個比方,可是說的是事實。


5. 揭開Pipeline的神祕面紗

一個Channel在數量上,確定不止擁有一個Handler。 如何將雜亂無章的Handler,有序的組織起來呢?

來了一個Handler的裝配器——Pipeline。

Pipeline是何方神聖呢?

先揭一下神祕面紗:

Netty中, 使用一個雙向鏈表,將屬於一個Channel的全部Handler組織起來,而且給這個雙向鏈表封裝在一個類中,再給這個類取了一個很是牛逼的名字,叫作ChannelPipeline。

爲何這個名字很牛逼呢?

實際上這裏用了Java中一種很是重要的設計模式,Pipeline設計模式。後面將用專門的文章,來介紹這種牛逼模式。

回到主題:

一個Channel,僅僅一個ChannelPipeline。該pipeline在Channel被建立的時候建立。ChannelPipeline至關因而ChannelHandler的容器,它包含了一個ChannelHander造成的列表,且全部ChannelHandler都會註冊到ChannelPipeline中。


6. Handler的上下文環境

在Netty的設計中,Handler是無狀態的,不保存和Channel有關的信息。打個不恰當的比方,Handler就像國際僱傭軍同樣,誰給錢,給誰打仗。Handler的目標,是將本身的處理邏輯作得很完成,能夠給不一樣的Channel使用。

與之不一樣的是,Pipeline是有狀態的,保存了Channel的關係。

因而乎,Handler和Pipeline之間,須要一箇中間角色,把他們聯繫起來。這個中間角色是誰呢?

它就是——ChannelHandlerContext 。

因此,ChannelPipeline 中維護的,是一個由 ChannelHandlerContext 組成的雙向鏈表。這個鏈表的頭是 HeadContext, 鏈表的尾是 TailContext。而無狀態的Handler,做爲Context的成員,關聯在ChannelHandlerContext 中。在對應關係上,每一個 ChannelHandlerContext 中僅僅關聯着一個 ChannelHandler。

wps138F.tmpwps13B0.tmp



咱們繼續用源碼說話。

Context的雙向鏈表的主要代碼,在 AbstractChannelHandlerContext類中。該類主要包含一個雙向鏈表節點的前置和後置節點引用 prev、next,以及數據引用 handler,至關於鏈表數據結構中的 Node 節點。

部分關鍵源碼節選以下:

// ChannelHandler 首位指針
    final AbstractChannelHandlerContext head;
    final AbstractChannelHandlerContext tail;
    // pipeline 所屬 channel
    private final Channel channel;
    private final ChannelFuture succeededFuture;
    private final VoidChannelPromise voidPromise;
    private final boolean touch = ResourceLeakDetector.isEnabled();

protected DefaultChannelPipeline(Channel channel) {
this.channel = ObjectUtil.checkNotNull(channel, "channel");
 succeededFuture = new SucceededChannelFuture(channel, null);
voidPromise =  new VoidChannelPromise(channel, true);

        tail = new TailContext(this);
head = new HeadContext(this);

        head.next = tail;
tail.prev = head;
    }

7. Handler的註冊

Handler是如何註冊到Pipeline中的呢?

1.3. 第一步:包裹

加入到Pipeline以前,在Pipeline的基類DefaultChannelPipeline中,首先對Handler進行包裹。

代碼以下:

// 使用 AbstractChannelHandlerContext 包裹 ChannelHandler

private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
    return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}

1.4. 加入鏈表並註冊完成回調事件

1. 構建了 AbstractChannelHandlerContext 節點,並加入到了鏈表尾部。

2. 若是 channel 還沒有註冊到 EventLoop,就添加一個任務到 PendingHandlerCallback 上,後續channel 註冊完畢,再調用 ChannelHandler.handlerAdded。

3. 若是已經註冊,立刻調用 callHandlerAdded0 方法來執行 ChannelHandler.handlerAdded 註冊完成的回調函數。

代碼以下:

@Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            // 檢查,若不是 Sharable,並且已經被添加到其餘 pipeline,則拋出異常
            checkMultiplicity(handler);
            // 構建 AbstractChannelHandlerContext 節點
            newCtx = newContext(group, filterName(name, handler), handler);
            // 添加到鏈表尾部
            addLast0(newCtx);

            // registered 爲 false 表示 channel 還沒有註冊到 EventLoop 上。
            // 添加一個任務到 PendingHandlerCallback 上,後續註冊完畢,再調用 ChannelHandler.handlerAdded
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            // registered 爲 true,則當即調用 ChannelHandler.handlerAdded
            EventExecutor executor = newCtx.executor();
            // inEvent 用於判斷當前線程是不是 EventLoop 線程。執行 ChannelHandler 時,必須在對應的 EventLoop 線程池中執行。
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

 @Override
    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            // 檢查,若不是 Sharable,並且已經被添加到其餘 pipeline,則拋出異常
            checkMultiplicity(handler);
            // 構建 AbstractChannelHandlerContext 節點
            newCtx = newContext(group, filterName(name, handler), handler);
            // 添加到鏈表尾部
            addLast0(newCtx);

            // registered 爲 false 表示 channel 還沒有註冊到 EventLoop 上。
            // 添加一個任務到 PendingHandlerCallback 上,後續註冊完畢,再調用 ChannelHandler.handlerAdded
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

     // registered 爲 true,則當即調用 ChannelHandler.handlerAdded
            EventExecutor executor = newCtx.executor();
            // inEvent 用於判斷當前線程是不是 EventLoop 線程。執行 ChannelHandler 時,必須在對應的 EventLoop 線程池中執行。
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

1.5. 回調添加完成事件

添加完成後,執行回調方法以下:

private void callHandlerAdded0(final AbstractChannelHandlerContext ctx) {
        try {
ctx.handler().handlerAdded(ctx);
            ctx.setAddComplete();
        } catch (Throwable t) {
           …….
    }

會執行handler的handlerAdded 方法,這是一個回調方法。添加完成後的回調代碼,基本上寫在這裏。



8. 小結


至此,牛逼的Netty Handler和Netty Reactor 介紹完了。

對於Pipeline模式和基於Pipeline的Netty 入站和出站的事件傳輸機制,【瘋狂創客圈】在後面的系列死磕文章,會作一個很是精彩的介紹。




無編程不創客,無案例不學習。瘋狂創客圈,一大波高手正在交流、學習中!

瘋狂創客圈 Netty 死磕系列 10多篇深度文章博客園 總入口】  QQ羣:104131248

相關文章
相關標籤/搜索