1. 程式人生 > >Netty核心概念(5)之Channel

Netty核心概念(5)之Channel

比較 打開 hat 而且 斷開 系列 address begin OS

1.前言

 上一節講了Netty的第一個關鍵啟動類,啟動類所做的一些操作,和服務端的channel固定的handler執行過程,談到了不管是connect還是bind方法最終都是調用了channel的相關方法,此節開始對channel進行說明。channel設置的概念非常多,而且都很重要,先放個NIO的客戶端Channel的類結構圖。

技術分享圖片

2.主要概念

2.1 channel

  channel就是直接與操作系統層到交道的數據通道了,可能是java提供的,也可能是通過native方法自己擴展了C++功能的渠道,但是不管哪類,都有一個基礎的定義。下面是channel中主要定義的接口方法:

技術分享圖片

  id():獲取該channel的標識

  eventloop():獲取該channel註冊的線程池

  parent():獲取該channel的父channel,NIO沒有父channel,一般為null

  config():獲取該channel的配置

  isOpen():該channel是否打開狀態

  isRegistered():該channel是否註冊到線程池中

  isActive():該channel是否可用

  metadata():該channel的元數據

  localAddress():該channel的本地綁定地址端口

  remoteAddress():該channel連接的對端的地址端口

  closeFuture():該channel關閉時觸發的future

  isWritable():該channel當前是否可寫,只有IO線程會處理可寫狀態

  bytesBeforeUnwritable():該channel還能寫多少字節

  unsafe():獲取該channel的unsafe操作對象,對於channel的讀寫,一般不直接操作channel,而是轉交給unsafe對象處理,channel本身通常只做查詢狀態,獲取相關字段內容的操作。

  alloc():獲取分配的緩沖區

  read():進行read操作

  write():進行write操作

 上面的方法我們看見了一個不熟悉的unsafe對象,這個也是一個比較重要的概念,理解該類在整個結構所處的位置作用,對於理解框架有較大的幫助。Unsafe被直接定義在Channel接口內部,意味著該接口是與Channel綁定的,上述方法的時候也解釋過該類作用。channel本身不直接做相應的工作,交給unsafe方法調用。下圖是unsafe的接口定義:

技術分享圖片

  recvBufAllocHandle():獲取處理讀取channel數據之後處理的handler

  localAddress():本地地址端口

  remoteAddress():遠程地址端口

  register():將channel註冊到線程池中

  bind():服務端綁定本地端口

  connect():客戶端連接遠程端口

  disconnect():斷開連接

  close():關閉channel

  closeForcibly():強制關閉

  deregister():移除線程池的註冊

  beginRead():準備讀取數據

  write():寫入數據

  flush():強制刷新

  voidPromise():特殊的promise

  outboundBuffer():獲取輸出數據的buffer操作類

 看到上面的一系列接口就能夠明白,實際操作channel的是unsafe類,但是是直接操作unsafe類嗎?比如綁定端口的時候確實調用的是channel.bind方法啊,實際上這裏還涉及其它概念,繞了一圈進行操作的。unsafe看名稱也應該明白,這個對channel進行操作的類是個線程非安全的類,所以一般通過Netty本身的結構設計,保證線程隔離,才能放心使用。當然如果不自己定義一種IO方式,基本上使用現在Netty封裝好的不會有什麽問題,如果自己造輪子,這個就要額外註意了。

2.2 ChannelPipeline

 在前面陸續都提到了這個pipeline,本小節就好好聊聊這個類的作用。先不看接口定義,先關註該類在整個結構所處的位置。打開AbstractChannel,仔細研究一下這個類抽象的channel類,你就會有所收獲。channel的主要操作方法大部分都是通過pipeline來完成的,如:bind,connect,close,deregister,flush,read,write等。奇怪嗎?並不是我們上面說的由unsafe處理。但是這並不矛盾,unsafe是對最底層最基礎的處理,我們會有一系列的業務層需要處理,比如bind時對socket的參數設置交由handler處理,所以channel會將相關操作委托給pipeline處理,pipeline經過一系列操作,最後調用unsafe的相關動作,最終回到channel。pipeline翻譯是管道,這裏感覺更像流水線操作。

 現在再來看pipeline的基本定義就不會覺得突兀了。

技術分享圖片

 pipline的方法很多,但是分成兩大類:1.註冊handler;2.使用handler;截圖是使用handler的方法,註冊handler的方法很多,這裏不進行介紹。ChannelPipeline沒有那麽多的實現類,基礎的就是DefaultChannelPipeline,所以直接對該類的部分方法解析。

 1.先是註冊handler的方法究竟幹了什麽,拿例子中使用的addLast()方法為例:

    public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
            checkMultiplicity(handler);

            newCtx = newContext(group, filterName(name, handler), handler);

            addLast0(newCtx);

            // If the registered is false it means that the channel was not registered on an eventloop yet.
            // In this case we add the context to the pipeline and add a task that will call
            // ChannelHandler.handlerAdded(...) once the channel is registered.
            if (!registered) {
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
                newCtx.setAddPending();
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

 要看懂這段代碼還有個內容要註意,tail和head,以及handlerContext(這個是handler相關的概念,這裏不進行介紹)。tail和head是pipeline持有的handler頭結點和尾結點,看見addLast的時候就應該明白,handler是以鏈式結構串起來的,在前面也說過,handler是職責鏈模式,是有先後順序的。這個方法就是將handler放在職責鏈尾。中間有個過程,將handler用context包裝了,這個不是本節的重點,之後handler章再介紹。

 2.接下來就是pipeline的重點,其是通過什麽方式操作channel的,或者說是操作channel的過程是怎樣的。

    public final ChannelFuture bind(SocketAddress localAddress) {
        return tail.bind(localAddress);
    }

 基本的channel操作都是通過默認的tail完成的,這些操作有bind,connect,close,deregister,flush,read,write。tail是一個handlerContext,這裏會涉及一些handlerContext的內容,簡略說下吧:在之前pipeline添加handler的時候,生成了context,context構成了鏈結構,其知道自己的前後handler是哪個。其他的不細說,最終是通過tail的這個handler不斷的早它前一個out類型的handler,最終找到head,看HeadContext類你就會明白了,該類獲取了channel的unsafe對象,所有操作都由該對象完成,這樣整個環節就連上了。由channel生成channelpipeline和unsafe對象,所有操作交給pipeline,pipeline從tail一直搜索到head,最後由head獲取channel的unsafe方法,最終進行相關操作。

 還有一類方法這裏也進行介紹一下,牽扯到handlerContext職責鏈的運行過程,不細講。

    public final ChannelPipeline fireChannelActive() {
        AbstractChannelHandlerContext.invokeChannelActive(head);
        return this;
    }

 這個從head開始調用

     public void channelActive(ChannelHandlerContext ctx) throws Exception {
            ctx.fireChannelActive();

            readIfIsAutoRead();
        }

 然後又調用了自己的fireChannelActive方法,從head往後找,next的in類型的方法,推動了整個職責鏈的操作了。所以pipeline的fireChannelActive()方法是起始方法,推動職責鏈的調用。

3.後記

  channel一共有三個重要的概念:

    1.Channel本身不做事情,將事件都交給ChannelPipeline。

    2.ChannelPipeline本身也不做什麽,其主要是控制handler鏈,由tail查詢到head持有unsafe對象,控制channel的連接,讀取,寫入,提供了由head到tail的直接觸發事件鏈方法fireXXX。

    3.Unsafe是操作channel的最終位置。

  最後附上一個關系圖:

  技術分享圖片

Netty核心概念(5)之Channel