Selector

选择器是 Java 多路复用模型的一个实现,可以同时监控多个非阻塞套接字通道。示意图大致如下:

创建选择器

     选择器 Selector 是一个抽象类,所以不能直接创建。Selector 提供了一个 open 方法,通过 open 方法既可以创建选择器实例。示例代码如下:

Selector selector = Selector.open();

     Java 选择器是对底层多路复用接口的一个包装,这里的 open 方法也不例外。假设我们的 Java 运行在 Linux 平台下,那么 open 最终所做的事情应该是调用操作系统的epoll_create函数,用于创建 epoll 实例。真实情况是不是如此呢?答案就在冰山深处,接下来就让我们一起去求索吧。下面我们将沿着 open 方法一路走下去,如下:

public abstract class Selector implements Closeable {
    public static Selector open() throws IOException {
        // 创建 SelectorProvider,再通过其 openSelector 方法创建 Selector
        return SelectorProvider.provider().openSelector();
    }
    // 省略无关代码
}

public abstract class SelectorProvider {

  private static final Object lock = new Object();
  private static SelectorProvider provider = null;

public static SelectorProvider provider() {
        synchronized (lock) {
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
                            if (loadProviderFromProperty())
                                return provider;
                            if (loadProviderAsService())
                                return provider;
                            // 创建默认的 SelectorProvider
                            provider = sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }
}

public class DefaultSelectorProvider {
    private DefaultSelectorProvider() { }
    
    /**
     * 根据系统名称创建相应的 SelectorProvider
     */
    public static SelectorProvider create() {
        String osname = AccessController
            .doPrivileged(new GetPropertyAction("os.name"));
        if (osname.equals("SunOS"))
            return createProvider("sun.nio.ch.DevPollSelectorProvider");
        if (osname.equals("Linux"))
            return createProvider("sun.nio.ch.EPollSelectorProvider");
        
        // 
        return new sun.nio.ch.PollSelectorProvider();
    }
    
    /**
     * 加载 SelectorProvider 类,并创建实例
     */
    @SuppressWarnings("unchecked")
    private static SelectorProvider createProvider(String cn) {
        Class<SelectorProvider> c;
        try {
            c = (Class<SelectorProvider>)Class.forName(cn);
        } catch (ClassNotFoundException x) {
            throw new AssertionError(x);
        }
        try {
            return c.newInstance();
        } catch (IllegalAccessException | InstantiationException x) {
            throw new AssertionError(x);
        }

    }
}

/**
 * 创建完 SelectorProvider,接下来要调用 openSelector 方法
 * 创建 Selector 的继承类了。
 */
public class EPollSelectorProvider extends SelectorProviderImpl {
    public AbstractSelector openSelector() throws IOException {
        return new EPollSelectorImpl(this);
    }
}

class EPollSelectorImpl extends SelectorImpl {
    EPollSelectorImpl(SelectorProvider sp) throws IOException {
        // 调用父类构造方法
        super(sp);
        long pipeFds = IOUtil.makePipe(false);
        fd0 = (int) (pipeFds >>> 32);
        fd1 = (int) pipeFds;
        
        // 创建 EPollArrayWrapper,EPollArrayWrapper 是一个重要的实现
        pollWrapper = new EPollArrayWrapper();
        
        pollWrapper.initInterrupt(fd0, fd1);
        fdToKey = new HashMap<>();
    }
}

public abstract class SelectorImpl extends AbstractSelector {
    protected SelectorImpl(SelectorProvider sp) {
        super(sp);
        keys = new HashSet<SelectionKey>();
        selectedKeys = new HashSet<SelectionKey>();
        
        /* 初始化 publicKeys 和 publicSelectedKeys,
         * publicKeys 即 selector.keys() 方法所返回的集合,
         * publicSelectedKeys 则是 selector.selectedKeys() 方法返回的集合
         */
        if (Util.atBugLevel("1.4")) {
            publicKeys = keys;
            publicSelectedKeys = selectedKeys;
        } else {
            publicKeys = Collections.unmodifiableSet(keys);
            publicSelectedKeys = Util.ungrowableSet(selectedKeys);
        }
    }
}

/**
 * EPollArrayWrapper 一个重要的实现,这一层再往下就是 C 代码了
 */
class EPollArrayWrapper {
    EPollArrayWrapper() throws IOException {
        // 调用 epollCreate 方法创建 epoll 文件描述符
        epfd = epollCreate();
    
        // the epoll_event array passed to epoll_wait
        // 初始化 pollArray,该对象用于存储就绪文件描述符和事件
        int allocationSize = NUM_EPOLLEVENTS * SIZE_EPOLLEVENT;
        pollArray = new AllocatedNativeObject(allocationSize, true);
        pollArrayAddress = pollArray.address();
    
        // eventHigh needed when using file descriptors > 64k
        if (OPEN_MAX > MAX_UPDATE_ARRAY_SIZE)
            eventsHigh = new HashMap<>();
    }
    
    // epollCreate 方法是 native 类型的
    private native int epollCreate();
}

以上代码时 Java 层面的,Java 层调用栈最下面的类是 EPollArrayWrapper(源码路径可以在附录中查找)。EPollArrayWrapper 是一个重要的实现,起着承上启下的作用。上层是 Java 代码,下层是 C 代码。上层的代码看完了,接下来看看冰山深处的 C 代码:

JNIEXPORT jint JNICALL
Java_sun_nio_ch_EPollArrayWrapper_epollCreate(JNIEnv *env, jobject this)
{
    // 调用 epoll_create 函数创建 epoll 实例,并返回文件描述符 epfd
    int epfd = epoll_create(256);
    if (epfd < 0) {
       JNU_ThrowIOExceptionWithLastError(env, "epoll_create failed");
    }
    return epfd;
}

仅做了创建 epoll 实例这一件事。看到这里,答案就明了了。最后在附一张时序图帮助大家理清代码调用顺序,如下:

选择键 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;

事件之间可以通过或运算进行组合,比如:

int interestOps = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

interestOps 和 readyOps

interestOps 即感兴趣的事件集合,通道调用 register 方法注册时会设置此值,interestOps 可通过 SelectionKey interestOps() 方法获取。readyOps 是就绪事件集合,可通过 SelectionKey readyOps() 获取。

interestOps 和 readyOps 被声明在 SelectionKey 子类 SelectionKeyImpl 中,代码如下:

public class SelectionKeyImpl extends AbstractSelectionKey {
    private volatile int interestOps;
    private int readyOps;
}

接下来再来看看与 readyOps 事件集合相关的几个方法,如下:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();

接下来以 isReadable 方法为例,简单看一下这个方法是如何实现。

public final boolean isReadable() {
    return (readyOps() & OP_READ) != 0;
}

可以通过或运算组合事件,这里则是通过与运算来测试某个事件是否在事件集合中。比如

readyOps = SelectionKey.OP_READ | SelectionKey.OP_WRITE = 0101,
readyOps & OP_READ = 0101 & 0001 = 0001,
readyOps & OP_CONNECT = 0101 & 1000 = 0

readyOps & OP_READ != 0,所以 OP_READ 在事件集合中。readyOps & OP_CONNECT == 0,所以 OP_CONNECT 不在事件集合中。

attach 方法

attach 是一个好用的方法,通过这个方法,可以将对象暂存在 SelectionKey 中,待需要的时候直接取出来即可。比如本文对应的练习代码实现了一个简单的 HTTP 服务器,在读取用户请求数据后(即 selectionKey.isReadable() 为 true),会去解析请求头,然后将请求头信息通过 attach 方法放入 selectionKey 中。待通道可写后,再从 selectionKey 中取出请求头,并根据请求头回复客户端不同的消息。当然,这只是一个应用场景

selectionKey.attach(obj);
Object attachedObj = selectionKey.attachment();

通道注册

通道注册即将感兴趣的事件告知 Selector,待事件发生时,Selector 即可返回就绪事件,就可以去做后续的事情了。比如 ServerSocketChannel 通道通常对 OP_ACCEPT 事件感兴趣,那么我们就可以把这个事件注册给 Selector。待事件发生,即服务端接受客户端连接后,我们即可获取这个就绪的事件并做相应的操作。通道注册的示例代码如下:

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

选择方法

Selector 包含3种不同功能的选择方法,分别如下:

  • int select()
  • int select(long timeout)
  • int selectNow()

select() 是一个阻塞方法,仅在至少一个通道处于就绪状态时才返回。
select(long timeout) 同样也是阻塞方法,不过可对该方法设置超时时间(timeout > 0),使得线程不会被一直阻塞。如果 timeout = 0,会一直阻塞线程。
selectNow() 为非阻塞方法,调用后立即返回。

以上3个方法均返回 int 类型值,表示每次调用 select 或 selectNow 方法后,新就绪通道的数量。如果某个通道在上一次调用 select 方法时就已经处于就绪状态,但并未将该通道对应的 SelectionKey 对象从 selectedKeys 集合中移除。假设另一个的通道在本次调用 select 期间处于就绪状态,此时,select 返回1,而不是2。

 2.4.2 选择过程

选择方法用起来虽然简单,但方法之下隐藏的逻辑还是比较复杂的。大致分为下面几个步骤:

  1. 检查已取消键集合 cancelledKeys 是否为空,不为空则将 cancelledKeys 的键从 keys 和 selectedKeys 中移除,并将键和通道注销。
  2. 调用操作系统的 epoll_ctl 函数将通道感兴趣的事件注册到 epoll 实例中
  3. 调用操作系统的 epoll_wait 函数监听事件
  4. 再次执行步骤1
  5. 更新 selectedKeys 集合,并返回就绪通道数量

上面五个步骤对应于 EPollSelectorImpl 类中 doSelect 方法的逻辑,如下:

+----EPollArrayWrapper.java
int poll(long timeout) throws IOException {
    // 调用 epoll_ctl 函数注册事件,对应步骤3
    updateRegistrations();
    
    // 调用 epoll_wait 函数等待事件发生,对应步骤4
    updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd);
    for (int i=0; i<updated; i++) {
        if (getDescriptor(i) == incomingInterruptFD) {
            interruptedIndex = i;
            interrupted = true;
            break;
        }
    }
    return updated;
}

/**
 * Update the pending registrations.
 */
private void updateRegistrations() {
    synchronized (updateLock) {
        int j = 0;
        while (j < updateCount) {
            // 获取 fd 和 events,这两个值在调用 register 方法时被存储到数组中
            int fd = updateDescriptors[j];
            short events = getUpdateEvents(fd);
            boolean isRegistered = registered.get(fd);
            int opcode = 0;

            if (events != KILLED) {
                // 确定 opcode 的值
                if (isRegistered) {
                    opcode = (events != 0) ? EPOLL_CTL_MOD : EPOLL_CTL_DEL;
                } else {
                    opcode = (events != 0) ? EPOLL_CTL_ADD : 0;
                }
                if (opcode != 0) {
                    // 注册事件
                    epollCtl(epfd, opcode, fd, events);
                    // 设置 fd 的注册状态
                    if (opcode == EPOLL_CTL_ADD) {
                        registered.set(fd);
                    } else if (opcode == EPOLL_CTL_DEL) {
                        registered.clear(fd);
                    }
                }
            }
            j++;
        }
        updateCount = 0;
    }
    
    // 下面两个均是 native 方法
    private native void epollCtl(int epfd, int opcode, int fd, int events);
    private native int epollWait(long pollAddress, int numfds, long timeout, int epfd) throws IOException;
}

 

 

 

参考:

http://www.tianxiaobo.com/2018/04/03/Java-NIO%E4%B9%8B%E9%80%89%E6%8B%A9%E5%99%A8/

posted on 2018-10-22 16:47  溪水静幽  阅读(646)  评论(0)    收藏  举报