1. 程式人生 > >《Java原始碼解析》之NIO的Selector機制(Part2:SelectableChannel.register(Selector sel, int ops))

《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()函式的原始碼。