工作

JDK IO多路复用基础

2019-05-06  本文已影响0人  0爱上1

本文所有源码均基于JDK1.8版本

通过学习JDK提供的IO多路复用相关源码,为后续的Netty源码学习打下基础

IO多路复用底层实现

JDK nio包多路复用基于底层操作系统,linux2.6版本使用epoll实现,低于2.6版本则使用select或poll实现多路复用


JDK NIO

Channel

该接口用于操作(读,写)数据源(文件,网络socket等),并提供多种实现如FileChannel, ServerSocketChannel等,替换基于流的形式操作数据源

SelectorProvider

一个抽象类,用于selector和可选择的channel上,包括打开一个selector,打开一个 server-socket channel,或socket channel,同时该类持有自身类的一个属性provider

该类提供了一个静态方法provider(),用于获取当前操作系统范围下默认的支持的selector provider

public abstract class SelectorProvider {

    private static final Object lock = new Object();

    // 持有自身实例的引用
    private static SelectorProvider provider = null;

    // 获取可用的selectorProvider
    public static SelectorProvider provider() {
        synchronized (lock) {
            // 如果已存在,直接返回
            if (provider != null)
                return provider;
            return AccessController.doPrivileged(
                new PrivilegedAction<SelectorProvider>() {
                    public SelectorProvider run() {
                            // 1. 从属性配置中利用反射实例化selectorProvider
                            if (loadProviderFromProperty())
                                return provider;
                            // 2. 利用Java SPI加载(META-INF/services/)并反射实例化selectorProvider
                            if (loadProviderAsService())
                                return provider;
                            // 3. 以上都没有的话,就采用默认jdk环境下默认的selectorProvider,windows下为WindowsSelectorProvider
                            // Linux内核2.6及以上版本,采用EPollSelectorProvider,
                            //低版本内核使用PollSelectorProvider
                            provider = sun.nio.ch.DefaultSelectorProvider.create();
                            return provider;
                        }
                    });
        }
    }

    // 从属性配置中利用反射实例化selectorProvider
    private static boolean loadProviderFromProperty() {
        String cn = System.getProperty("java.nio.channels.spi.SelectorProvider");
        if (cn == null)
            return false;
        try {
            Class<?> c = Class.forName(cn, true,
                                    ClassLoader.getSystemClassLoader());
            provider = (SelectorProvider)c.newInstance();
            return true;
        } catch (ClassNotFoundException x) {
            throw new ServiceConfigurationError(null, x);
        } catch (IllegalAccessException x) {
            throw new ServiceConfigurationError(null, x);
        } catch (InstantiationException x) {
            throw new ServiceConfigurationError(null, x);
        } catch (SecurityException x) {
            throw new ServiceConfigurationError(null, x);
        }
    }

    // 利用ServiceLoader加载META-INF/services下全限定接口文件实例化文件内的实现类
    private static boolean loadProviderAsService() {

        ServiceLoader<SelectorProvider> sl =
            ServiceLoader.load(SelectorProvider.class,
                            ClassLoader.getSystemClassLoader());
        Iterator<SelectorProvider> i = sl.iterator();
        for (;;) {
            try {
                if (!i.hasNext())
                    return false;
                provider = i.next();
                return true;
            } catch (ServiceConfigurationError sce) {
                if (sce.getCause() instanceof SecurityException) {
                    // Ignore the security exception, try the next provider
                    continue;
                }
                throw sce;
            }
        }
    }

    // 打开一个selector(也是一个文件)
    public abstract AbstractSelector openSelector() throws IOException;

    // 打开一个 ServerSocketChannel
    public abstract ServerSocketChannel openServerSocketChannel() throws IOException;

    // 打开一个 SocketChannel
    public abstract SocketChannel openSocketChannel() throws IOException;

    // ...
}
WindowsSelectorProvider UML 类图
WindowsSelectorProvider

抽象类 Selector

jdk.nio中的selector对应操作系统底层的select结构(select/poll/epoll),在linux下也是一个文件

windows下的具体的Selector实现类是WindowsSelectorImpl

WindowsSelectorImpl UML类图
WindowsSelectorImpl
Selector抽象类

看下Selector中定义了哪些方法

public abstract class Selector implements Closeable {


    // 1. 通过open方法,内部还是调用了SelectorProvider的provider方法得到选择器供应商,再调用其openSelector方法获取selector
    public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
    }

    // 2. 返回是否该selector是打开状态
    public abstract boolean isOpen();

    // 3. 返回创建了该selector的selectorProvider
    public abstract SelectorProvider provider();

    // 返回该selector选择器的注册上来的key 集合 
    public abstract Set<SelectionKey> keys();

    // 返回该selector选择器的注册上来并且被选择的key 集合 
    public abstract Set<SelectionKey> selectedKeys();

    // 选择准备好IO操作的相应通道的keys集合,即发起系统调用,该方法会被阻塞,直到至少一个通道被选择
    // 或该selector的wakeup方法被调用,或当前线程被打断,或给定的事件timeout超时
    // 参数timeout若为正,则阻塞指定毫秒后返回0
    // 若为0,则无限阻塞
    // 不可为负数
    // 返回值代表了准备好IO操作的keys数量,可能返回0
    public abstract int select(long timeout)
        throws IOException;


    public abstract int select() throws IOException;

    // 调用该方法,会导致还未阻塞返回的selector立即返回
    public abstract Selector wakeup();

}

主要包括如下:

记住一点就是selector会维护三种key的集合

AbstractSelector 类

该类继承了Selector类,主要定义了抽象方法register方法以及一个
cancelledKeys的集合和provider属性持有

重点看register方法

protected abstract SelectionKey register(AbstractSelectableChannel ch,
                                         int ops, Object att);

该方法有三个参数

  1. 可被选择的通道ch

  2. 感兴趣的事件ops

  3. 附加属性

返回值SelectionKey对象,代表了一个注册到当前selector上的给定通道,并指定了感兴趣的事件

注册方法的具体实现由SelectorImpl类实现

SelectorImpl

protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {
    if (!(var1 instanceof SelChImpl)) {
        throw new IllegalSelectorException();
    } else {
        SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);
        var4.attach(var3);
        synchronized(this.publicKeys) {
            this.implRegister(var4);
        }

        var4.interestOps(var2);
        return var4;
    }
}

可以看到这里就是new了一个SelectionKeyImpl,并将其注册到selector上(即加入到keys set集合中)同时设置感兴趣的事件到该SelectionKey实例的interestOps属性中

注册操作:

  1. 添加SelectionKey至Java Selector对象的keys集合

  2. 记录Java Selector对象的fdMap属性,key为该SelectionKey注册的channel的fd值,value为该SelectionKey

  3. 调用操作系统底层epoll_ctl系统调用中段注册epollfd需要监听的channel指定的事件

SelectionKeyImpl 选择键

一个SelectionKey即代表了一个channel和其注册的selector之间的关系,即通过这个SelectionKey就可以知道是哪个channel注册到了哪个selector上,并且其希望Selector监听该Channel的哪些事件

UML类图
image.png

该类有几个主要属性

SelChImpl channel

注册的通道

SelectorImpl selector

通道注册到哪个选择器

 int interestOps

注册时感兴趣的操作事件

readyOps

已准备好的操作事件

该类同时定义了一些列的操作常量如:OP_READ, OP_WRITE, OP_CONNECT, OP_ACCEPT

以及一些判断是否该key的channel准备好了相应的事件如:isReadable(),isWritable(), isConnectable(), isAcceptable(),其判断逻辑就是用key的内部属性readyOps中是否含有指定的事件

select方法详解

Java Selector有两个重载的select方法以及一个selectNow()方法

public abstract int select() throws IOException;

public abstract int select(long timeout) throws IOException;

public abstract int selectNow() throws IOException;

该方法是非阻塞的,如果没有channels变为可选择的,会立即返回0

该方法时阻塞的,返回的前提是至少有一个channel是可选择的或该selector的wakeup方法被调用,或者运行select方法的当前线程被打断

该方法基本上同select(),只是另外提供了timeout参数用于当没有channel可选择时,阻塞等待的毫秒数,注意该参数为0即代表一直阻塞,需要大于0才生效

以上三个方法最终的调用都会转到doSelect方法

这里以windows环境为例,WindowsSelectorImpl

定义了最大可选择的fd数量为1024

private static final int MAX_SELECTABLE_FDS = 1024;

doSelect伪代码

protected int doSelect(long var1) throws IOException {

    // 1. 处理取消注册channel
    this.processDeregisterQueue();

    // 2. 调用内部SubSelector的poll方法,SubSelector类为WindowsSelectorImpl的内部类,封装了系统调用poll的相关native方法
    this.subSelector.poll();

    // 3. 再次处理取消注册的channel
    this.processDeregisterQueue();

    // 4. 更新可选择channel的keys到selector的selectedKeys属性中
    int var3 = this.updateSelectedKeys();

    // 5. 返回可选择key的数量
    return var3;
}

其中1. 中主要处理已取消注册通道,即若cancelledKeys集合不为空,则遍历依次取消该Key

主要包括

  1. 调用内核取消该fd的指定事件的监听

  2. fdMap属性的清除

  3. keys属性清除

  4. selectedKeys属性清除

  5. 失效该SelectionKey,设置valid属性为false

而poll()的调用即是发起内核系统调用,阻塞获取可选择的fd

最后一个的updateSelectedKeys()方法就是根据内核返回的可选择的fd去fdMap中找到对应的SelectionKey,再将SelectionKey加入到该selector的selectedKeys集合中,这样我们应用程序的select方法返回后就可以从selectedKeys集合中获取可选择的key,进而进行我们自己的处理

总结

最后以一张图表示整个IO多路复用流程

image.png
上一篇 下一篇

猜你喜欢

热点阅读