Netty核心概念(5)之Channel
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