NIO中SelectionKey和Channel、Selector的关系

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值