NIO中SelectionKey和Channel、Selector的關係

今天給本身的項目測出了個bug,NIOServer端在讀取一次數據後我一開始是選擇將獲取的迭代器remove掉,可是發現selectNow()中獲取的SelectionKey還存在,後來將其直接SelectionKey.cancel();後就只能傳遞一次數據了。這是因爲我只是初步接觸NIO沒有深刻了解每一個模塊的具體實現致使的,下面我經過閱讀源碼來了解SelectionKey和Channel、Selector這三者的關係來梳理相關知識。
複製代碼

--------------分割線--------------

因爲Channel只是接口,所以繼續獲取該方法的實現,最終定位到AbstractSelectableChannel類,爲了方便閱讀以及符合主題,我省略了部分無關代碼,相關實現以下:java

public final SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException {
        synchronized (regLock) {
            /* 異常狀態檢查代碼,省略 */
            SelectionKey k = findKey(sel);
            if (k != null) {
                k.interestOps(ops);
                k.attach(att);
            }
            if (k == null) {
                synchronized (keyLock) {
                     /* 異常狀態檢查代碼,省略 */
                    k = ((AbstractSelector)sel).register(this, ops, att);
                    addKey(k);
                }
            }
            return k;
        }
    }
複製代碼

那麼這裏能夠知道當Channel往Selector註冊的時候Channel會先調用本地方法findKey傳入要註冊的Selector獲取對應的SelectionKey,而參數只有Selector,沒有相關狀態,所以這裏就能夠直接知道SelectionKey對應的就是一個Selector,我前面將其直接cancel掉的話其實是將這個Channel從對應的Selector中取消註冊了,這就不免在同一個鏈接中只能傳遞一次,由於在等待下次IO的時候我已經將其從Selector中取消了讀狀態。數組

相應的,若是SelectionKey不爲null,則表示該Channel已經在目標Selector上註冊,所以只要將目標狀態添加進SelectionKey中便可。安全

若是SelectionKey爲null,則直接由Selector的註冊方法建立並添加進Channel中。this

--------------分割線--------------

那麼繼續查看這兩個方法,首先是addKeyspa

private void addKey(SelectionKey k) {
        assert Thread.holdsLock(keyLock);
        int i = 0;
        if ((keys != null) && (keyCount < keys.length)) {
            // Find empty element of key array
            for (i = 0; i < keys.length; i++)
                if (keys[i] == null)
                    break;
        } else if (keys == null) {
            keys =  new SelectionKey[3];
        } else {
            // Grow key array
            int n = keys.length * 2;
            SelectionKey[] ks =  new SelectionKey[n];
            for (i = 0; i < keys.length; i++)
                ks[i] = keys[i];
            keys = ks;
            i = keyCount;
        }
        keys[i] = k;
        keyCount++;
    }
複製代碼

這段代碼十分簡單,忽略相關安全檢查後能夠看到這個操做主要是對keys的操做,這個keys經過查看源碼能夠看到它是一個SelectionKey數組用以保存該Channel所綁定的Selector生成的SelectionKey對象。它默認長度爲3,每次進行擴容將其長度*2.rest

那麼findKey就很好猜想它的實現了:應該是對這個keys數組的遍歷,並將其中的SelectionKey與Selector對比便可.code

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;
        }
    }
複製代碼

既然有find和and了那天然就會有removecdn

void removeKey(SelectionKey k) {                    // package-private
        synchronized (keyLock) {
            for (int i = 0; i < keys.length; i++)
                if (keys[i] == k) {
                    keys[i] = null;
                    keyCount--;
                }
            ((AbstractSelectionKey)k).invalidate();
        }
    }
複製代碼

結論

Channel、Selector經過一個惟一的SelectionKey實現註冊。對象

相關文章
相關標籤/搜索