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的最終位置。
最后附上一個關系圖,該圖只有一部分,並不完全,不要完全相信:

