概述
channel 是 netty 網絡 IO 操作抽象出來的一個接口,主要功能有:網絡IO的讀寫,客戶端發起連接、主動關閉連接,鏈路關閉,獲取通信雙方的網絡地址等。
這裏我們只簡單分析下 AbstractChannel 和 AbstractNioChannel 這兩個Channel源碼。
AbstractChannel 源碼
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
// 父 Channel(NioServerSocketChannel 是沒有父channel的)
private final Channel parent;
// Channel 唯一ID
private final ChannelId id;
// Unsafe 對象,封裝 ByteBuf 的讀寫操作
private final Unsafe unsafe;
// 關聯的 Pipeline 對象
private final DefaultChannelPipeline pipeline;
private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
private final CloseFuture closeFuture = new CloseFuture(this);
// 本地地址和遠端地址
private volatile SocketAddress localAddress;
private volatile SocketAddress remoteAddress;
// EventLoop 封裝的 Selector
private volatile EventLoop eventLoop;
// 是否註冊
private volatile boolean registered;
private boolean closeInitiated;
/** Cache for the string representation of this channel */
private boolean strValActive;
private String strVal;
AbstractChannel 構造
protected AbstractChannel(Channel parent, ChannelId id) {
this.parent = parent;
this.id = id;
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
// Unsafe 實現交給子類實現
protected abstract AbstractUnsafe newUnsafe();
// 創建 DefaultChannelPipeline 對象
protected DefaultChannelPipeline newChannelPipeline() {
return new DefaultChannelPipeline(this);
}
Unsafe類裏實現了具體的連接與寫數據。比如:網絡的讀,寫,鏈路關閉,發起連接等。之所以命名爲unsafe是不希望外部使用,並非是不安全的。
DefaultChannelPipeline 只是一個 Handler 的容器,也可以理解爲一個Handler鏈,具體的邏輯由Handler處理,而每個Handler都會分配一個EventLoop,最終的請求還是要EventLoop來執行,而EventLoop中又調用Channel中的內部類Unsafe對應的方法。
新建一個channel會自動創建一個ChannelPipeline。
這裏創建 DefaultChannelPipeline,構造中傳入當前的 Channel,而讀寫數據都是在 ChannelPipeline 中進行的,ChannelPipeline 進行讀寫數據又委託給 Channel 中的 Unsafe 進行操作。
AbstractUnsafe 類結構
從類結構中可以看出 Unsafe 實現了 網絡的讀,寫,連接關閉,發起連接等操作。
AbstractNioChannel 源碼
public abstract class AbstractNioChannel extends AbstractChannel {
// 抽象了 SocketChannel 和 ServerSocketChannel 的公共的父類
private final SelectableChannel ch;
// SelectionKey.OP_READ 讀事件
protected final int readInterestOp;
// 註冊到 selector 上返回的 selectorKey
volatile SelectionKey selectionKey;
// 是否還有未讀的數據
boolean readPending;
private final Runnable clearReadPendingRunnable = new Runnable() {
@Override
public void run() {
clearReadPending0();
}
};
/**
* The future of the current connection attempt. If not null, subsequent
* connection attempts will fail.
*/
// 連接操作的結果
private ChannelPromise connectPromise;
// 連接超時定時任務
private ScheduledFuture<?> connectTimeoutFuture;
// 客戶端地址
private SocketAddress requestedRemoteAddress;
....
SelectableChannel 抽象了 java.nio.SocketChannel 和 java.nio.ServerSocketChannel 的公共方法。netty 封裝了 NIO 的channel。
註冊 Channel
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
// 這裏註冊的事件爲0
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}
javaChannel() :是 Nio 中的 channel。
eventLoop().unwrappedSelector(): 是 Nio 中的 selector。
在java.nio.channels.SelectionKey 類中定義了 4種事件。
// 讀操作事件
public static final int OP_READ = 1 << 0;
// 寫操作事件
public static final int OP_WRITE = 1 << 2;
// 客戶端鏈接服務的操作事件
public static final int OP_CONNECT = 1 << 3;
// 服務端接受客戶端連接事件
public static final int OP_ACCEPT = 1 << 4;
這裏註冊的是0,說明對任何事件都不感興趣,僅僅完成註冊操作。把當前的 Channel當做附件進行註冊。如果註冊成功則返回 selectionKey,通過 selectionKey 可用從 Selector 中獲取 當前註冊的 Channel。
什麼情況下才拋出 CancelledKeyException 異常呢?
由於尚未調用select.select(..)操作,因此可能仍在緩存而未刪除“已取消”selectionkey,因此強制調用 selector.selectNow() 方法將已經取消的 selectionKey 從 selector 上刪除。
只有第一次拋出此異常,才調用 selector.selectNow() 進行取消。 如果調用 selector.selectNow() 還有取消的緩存,可能是jdk的一個bug。
@Override
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
該方法首先判斷 Channel 是否可用,不可用直接返回。
獲取該 selectionKey 註冊到 selector 的事件。
如果註冊的事件 位運算與 讀事件 等於0,則說明該 Channel 沒有在 selelctor 上註冊讀事件,在這裏註冊讀事件。
什麼情況下會調用 doBeginRead() 方法?
當 Channel 處於 channelActive 狀態後,就會在DefaultChannelPipeline.channelActive 方法中調用 doBeginRead() 方法,在 selector 上註冊讀事件。