今天给自己的项目测出了个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实现注册。