今天給自己的專案測出了個bug,NIOServer端在讀取一次資料後我一開始是選擇將獲取的迭代器remove掉,但是發現selectNow()中獲取的SelectionKey還存在,後來將其直接SelectionKey.cancel();後就只能傳遞一次資料了。這是由於我只是初步接觸NIO沒有深入瞭解每個模組的具體實現導致的,下面我通過閱讀原始碼來了解SelectionKey和Channel、Selector這三者的關係來梳理相關知識。
複製程式碼
--------------分割線--------------
由於Channel只是介面,因此繼續獲取該方法的實現,最終定位到AbstractSelectableChannel類,為了方便閱讀以及符合主題,我省略了部分無關程式碼,相關實現如下:
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中。
--------------分割線--------------
那麼繼續檢視這兩個方法,首先是addKey
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.
那麼findKey就很好猜測它的實現了:應該是對這個keys陣列的遍歷,並將其中的SelectionKey與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;
}
}
複製程式碼
既然有find和and了那自然就會有remove
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實現註冊。