当前位置: 首页 > 工具软件 > open-selector > 使用案例 >

NIO源码解析-Selector

宿建本
2023-12-01

前言:

    有关于Selector的使用,我们在之前的示例中就已经用到过了。当然,基本都是最简单的使用。而有关于Selector的其他API、SelectionKey的API,我们都没有详细介绍过。故单独列一篇,来说明下其使用。

1.Selector的创建与使用

// 创建,创建比较简单,就是一句话,实际后面做了很多工作。
Selector selector = Selector.open();

// Selector.open
public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();
}

// SelectorProvider.provider()
public static SelectorProvider provider() {
    synchronized (lock) {
        if (provider != null)
            return provider;
        return AccessController.doPrivileged(
            new PrivilegedAction<SelectorProvider>() {
                public SelectorProvider run() {
                    // 加载java.nio.channels.spi.SelectorProvider系统参数配置对应的Provider
                    if (loadProviderFromProperty())
                        return provider;
                    // SPI方式加载SelectorProvider实现类
                    if (loadProviderAsService())
                        return provider;
                    // 以上两种都没有,则返回默认provider。
                    // 笔者Windows系统下直接返回WindowsSelectorProvider
                    // 最终Selector为WindowsSelectorImpl
                    provider = sun.nio.ch.DefaultSelectorProvider.create();
                    return provider;
                }
            });
    }
}
可以看到,Selector还是提供了很多方式来供我们选择Provider的。

1.1 select

    之前的文章中,展示了select方法的使用,Selector还有其他select类型方法
// 获取哪些已经准备好的channel数量。非阻塞,方法会立即返回
public abstract int selectNow() throws IOException;

// 同selectNow,会一直阻塞到有准备好的channel事件为止
public abstract int select() throws IOException;

// 同select(),会阻塞最多timeout毫秒后返回
public abstract int select(long timeout)

    我们可以在使用中选择合适的select方法,避免长时间的线程阻塞。

1.2 wakeup

    Selector提供wakeup方法来唤醒阻塞在1.1中select方法中的线程。

/**
     * Causes the first selection operation that has not yet returned to return
     * immediately.
     *
     * <p> If another thread is currently blocked in an invocation of the
     * {@link #select()} or {@link #select(long)} methods then that invocation
     * will return immediately.  If no selection operation is currently in
     * progress then the next invocation of one of these methods will return
     * immediately unless the {@link #selectNow()} method is invoked in the
     * meantime.  In any case the value returned by that invocation may be
     * non-zero.  Subsequent invocations of the {@link #select()} or {@link
     * #select(long)} methods will block as usual unless this method is invoked
     * again in the meantime.
     *
     * <p> Invoking this method more than once between two successive selection
     * operations has the same effect as invoking it just once.  </p>
     *
     * @return  This selector
     */
public abstract Selector wakeup();

注解真的很全了。

如果当前线程阻塞在select方法上,则立即返回;

如果当前Selector没有阻塞在select方法上,则本次wakeup调用会在下一次select方法阻塞时生效;

1.3 close

public abstract void close() throws IOException;
public abstract boolean isOpen();

    当我们不再使用Selector时,需要调用close方法来释放掉其它占用的资源,并将所有相关的选择键设置为无效。

    被close后的Selector则不能再使用。

    同时提供了isOpen方法来检测Selector是否开启。

1.4 keys & selectedKeys

// Returns this selector's key set.
public abstract Set<SelectionKey> keys();

// Returns this selector's selected-key set.
public abstract Set<SelectionKey> selectedKeys();

keys方法返回的是目前注册到Selector上的所有channel以及对应事件;

selectedKeys方法返回的是目前注册到Selector上的所有channel活跃的事件。返回的结果集的每个成员都被判定是已经准备好了。

故,我们之前一直使用的就是selectedKeys方法。

那么SelectionKey是什么呢?接着看。

2.SelectionKey

// A token representing the registration of 
// a {@link SelectableChannel} with a {@link Selector}.
public abstract class SelectionKey {
    
    // 代表我们关注的4种事件
    public static final int OP_READ = 1 << 0;
    public static final int OP_WRITE = 1 << 2;
    public static final int OP_CONNECT = 1 << 3;
    public static final int OP_ACCEPT = 1 << 4;
    
    // SelectionKey本质上就是channel与selector的关联关系
    public abstract SelectableChannel channel();
    public abstract Selector selector();
    
    // channel注册到selector上时,关注的事件
    public abstract int interestOps();
    // 当前channel已经准备好的事件
    public abstract int readyOps();
    
    // 附加信息。我们在channel.register(selector,ops,att)方法时,最后一个参数
    // 即指定了本次注册的附加信息
    public final Object attach(Object ob);
    public final Object attachment();
}

通过上面的注释,我们可以了解到,SelectionKey本质上就是channel注册到selector上后,用于绑定两者关系的一个类。对于channel关注的事件,添加的附件信息在这个类均有所体现。

3.SelectableChannel

    最后再来说下SelectableChannel,之前提到的SocketChannel和ServerSocketChannel都是SelectableChannel的实现类。

public abstract class SelectableChannel
    extends AbstractInterruptibleChannel
    implements Channel
{

    // 最重要的两个方法,用于注册channel到selector上,区别就是有没有attachment
    public abstract SelectionKey register(Selector sel, int ops, Object att)
            throws ClosedChannelException;
    public final SelectionKey register(Selector sel, int ops)
            throws ClosedChannelException;
    
    // 检查当前channel是否注册到任何一个selector上
    public abstract boolean isRegistered();
    
    // 当前channel可注册的有效的事件
    public abstract int validOps();
    
    // 阻塞状态,之前的SocketChannel文章中已经详细说过
    public abstract SelectableChannel configureBlocking(boolean block)
        throws IOException;
    // 检查channel阻塞状态
    public abstract boolean isBlocking();
}

注意:

1. 一个channel可以注册到多个Selector上

2.每个channel可注册的有效事件不同,如下

// SocketChannel
public final int validOps() {
    return (SelectionKey.OP_READ
            | SelectionKey.OP_WRITE
            | SelectionKey.OP_CONNECT);
}

//ServerSocketChannel
public final int validOps() {
    return SelectionKey.OP_ACCEPT;
}

心路旅程:

    笔者对于在学习NIO这一阶段时间的感受就是:多去尝试,多看注释。

    NIO属于笔者阶段性总结博客的第一站,后续还有更多系列博客要出来。

    为什么选择NIO作为第一站呢?因为众多高精尖技术的底层就是NIO。所以弄明白NIO的一系列使用是很有必要的。

    之前也知道BIO和NIO的大致写法,基本都是从网上看示例,直接拷贝。但是真让自己来写的话,又是漏洞百出,究其原因,就是对NIO的使用和其原理不甚了解。

    通过这一次的总结性输出,对NIO的理解也算是上了一个层次。

    与诸君共勉之!

 类似资料: