《Java原始碼解析》之NIO的Selector機制(Part2:SelectableChannel.register(Selector sel, int ops))
通過上一篇部落格,我們知道了Selector機制中的open()函式做了什麼,其實也就是建立了一個管道,並把pipe的讀寫檔案描述符放入pollArray中,這個pollArray是Selector的樞紐。下面我們抓取原始碼看一下channel在selector中註冊時做了什麼?
SelectableChannel.register(Selector sel, int ops)
SelectableChannel.register(Selector sel, int ops))函式分析
register()函式是在SelectableChannel這個類中實現的,所以只有繼承自這個類的channel才能通過selector管理,這個要點在前面部落格中已經解釋過了。那麼問題在於這個register()函式做了什麼呢?我們先看看JavaDoc上面的解釋:
public final SelectionKey register(Selector sel, int ops)throws ClosedChannelException
{
return register(sel, ops, null);
}
javaDoc上面解釋:向給定的選擇器註冊此通道,返回一個選擇鍵。該方法呼叫一個多型的register(sel, ops, null);
我們進入這個多型的函式如下:
public abstract SelectionKey register(Selector sel, int ops, Object att)
throws ClosedChannelException;
發現這是一個抽象方法,進入實現方法:在進入實現方法之前,我先給出ServerSocketServer的類繼承圖:
所以我們可以知道AbstractSelectableChannel是繼承自SelectableChannel類的,並且在AbstractSelectableChannel中實現了register(Selector sel, int ops, Object att)方法。原始碼如下:
public final SelectionKey register(Selector sel, int ops, Object att)throws ClosedChannelException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
}
}
return k;
}
}
首先還是看JavaDoc,看這個函式主要是幹嘛的:該方法實現體內部就是一個同步程式碼段。
JavaDoc:向給定的選擇器註冊此通道,返回一個選擇鍵。
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
從上面的程式碼可知:該方法首先判斷該通道是否是開啟的,以及給定的初始相關操作集是否有效。此外該通道也必須設定成非阻塞的通道。如果不滿要求均會丟擲異常。
再看後面的程式碼:
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
首先分析findKey(sel);這個方法,傳入selector引數。這是個類私有的方法,原始碼如下:
private SelectionKey findKey(Selector sel) {
synchronized (keyLock) {
if (keys == null)
return null;
for (int i = 0; i < keys.length; i++)
if ((keys[i] != null) && (keys[i].selector() == sel))
return keys[i];
return null;
}
}
該方法傳參當前的selector。函式中用到了一個十分重要的變數keys,我們先看看這個變數的定義:
private SelectionKey[] keys = null;
原始碼中國給的註釋是:這個變數儲存的是已經註冊在某個selector中的通道的selectionKey,如果某個通道被closed了,那麼相應的key也必須被登出。這個屬性被keyLock這個物件鎖保護。
這個函式內部首先判斷keys是否為空,如果為空,說明當前通道肯定是沒有被註冊的,所以返回null.如果keys非空,就遍歷keys,獲取每個selectionKey所註冊的selector的selector,如果和給定的selector相同,就說明此通道已經註冊在該selector上了,
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
這段程式碼說明該通道已經註冊在了該selector上了,在將其相關操作集設定為給定值後,返回表示該註冊的選擇鍵。
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
}
}
return k;
這裡的判斷當k為空時,說明該通道沒有註冊在該selector上,所以我們需要執行註冊操作。在保持適當鎖定的同時呼叫選擇器的 register 方法。返回前將得到的鍵新增到此通道的鍵集中。這裡註冊又呼叫的函式是:
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
這兩行程式碼註冊當前通道channel到selector中,並且獲得相應的SelectionKey,然後將SelectionKey新增到SelectionKey[] keys陣列中。
上面的k = ((AbstractSelector)sel).register(this, ops, att);
呼叫的實際上是SelectorImpl的register()函式。這個SelectorImpl是AbstractSelector的子類。我們看一下這個SelectorImpl.register()的實現;
protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
if(!(var1 instanceof SelChImpl)) {
throw new IllegalSelectorException();
} else {
SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
var4.attach(var3);
Set var5 = this.publicKeys;
synchronized(this.publicKeys) {
this.implRegister(var4);
}
var4.interestOps(var2);
return var4;
}
}
這裡的var1就是一個channel,var2是一個“interest集合”,意思是在通過Selector監聽Channel時對什麼事件感興趣。var3是一個附加物件。
這裡核心的就是做了兩件事:1.把var1也就是當前channel強轉成SelectionKeyImpl,也就是var4;2.呼叫 this.implRegister(var4);實現註冊。
繼續往下面分析,implRegister()是一個abstract方法,有兩個實現類,
由於我是在Mac下,所以選擇的是KQueueSelectorImpl這個類裡面的實現:這裡就是register最核心的實現地方:
protected void implRegister(SelectionKeyImpl var1) {
if(this.closed) {
throw new ClosedSelectorException();
} else {
//1. 獲取當前通道的檔案描述符var2
int var2 = IOUtil.fdVal(var1.channel.getFD());
//2. 將檔案描述符和對應的SelectionKey新增到fdMap裡面
this.fdMap.put(Integer.valueOf(var2), new KQueueSelectorImpl.MapEntry(var1));
++this.totalChannels;
//把selectionKey也新增到keys中。
this.keys.add(var1);
}
}
這裡var1是當前channel的selectionKey。這裡面最重要的變數也就是:
fdMap這個變數和keys變數。
這裡我懷疑,當我們執行select()函式的時候,可能就是遍歷裡面的內容。當然僅僅是懷疑,具體的還要分析select()函式的原始碼。