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实现注册。对象

相关文章
相关标签/搜索