今天給本身的項目測出了個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實現註冊。對象