java nio---WindowsSelectorImpl类详解

  WindowsSelectorImpl是Java nio 基于windows操作系统的基础类,这篇文章主要讲解两个方法:doSelect(long var1)和implRegister(SelectionKeyImpl var1)

WindowsSelectorImpl属性介绍:

private final int INIT_CAP = 8;
	//每个线程处理channel的数量
    private static final int MAX_SELECTABLE_FDS = 1024;
    //注册的selectionKey数组
    private SelectionKeyImpl[] channelArray = new SelectionKeyImpl[8];
    //底层的本机轮询数组包装对象,用于存放Socket文件描述符和事件掩码
    private PollArrayWrapper pollWrapper = new PollArrayWrapper(8);
    private int totalChannels = 1;
    private int threadsCount = 0;
    //辅助线程数组
    private final List<WindowsSelectorImpl.SelectThread> threads = new ArrayList();
    //用于唤醒辅助线程
    private final Pipe wakeupPipe = Pipe.open();
    //唤醒线程的文件描述符
    private final int wakeupSourceFd;
    private final int wakeupSinkFd;
    private Object closeLock = new Object();
    //保存文件描述符和SelectionKey的映射关系
    private final WindowsSelectorImpl.FdMap fdMap = new WindowsSelectorImpl.FdMap();
    //调用JNI的poll和处理就绪的SelectionKey
    private final WindowsSelectorImpl.SubSelector subSelector = new WindowsSelectorImpl.SubSelector();
    private long timeout;
    private final Object interruptLock = new Object();
    private volatile boolean interruptTriggered = false;
    //新增的辅助线程使用该锁等待主线程的开始信号
    private final WindowsSelectorImpl.StartLock startLock = new WindowsSelectorImpl.StartLock();
    //主线程用该锁等待所有辅助线程执行完毕
    private final WindowsSelectorImpl.FinishLock finishLock = new WindowsSelectorImpl.FinishLock();
    private long updateCount = 0L;

SelectionKeyImpl类介绍

  SelectionKeyImpl封装了channel、selector和channel注册时的事件掩码interestOps等

	final SelChImpl channel;
    final SelectorImpl selector;
    private int index;
    private volatile int interestOps;
    private int readyOps;

注册过程

注册channel时,首先生成SelectionKeyImpl 对象
将SelectionKeyImpl 对象储存到channelArray数组中
将文件句柄和SelectionKeyImpl的对应关系加入到fdMap集合中
将selectionKey的文件描述符保存到pollWrapper对象中

protected void implRegister(SelectionKeyImpl var1) {
        Object var2 = this.closeLock;
        synchronized(this.closeLock) {
            if (this.pollWrapper == null) {
                throw new ClosedSelectorException();
            } else {
            	//如果容量不够则扩容(后面会讲)
                this.growIfNeeded();
                //将key保存到channelArray数组中
                this.channelArray[this.totalChannels] = var1;
                //设置key在数组中的位置下标
                var1.setIndex(this.totalChannels);
                //在fdMap中保存句柄与key对象的映射
                this.fdMap.put(var1);
                //将key保存到keys集合中
                this.keys.add(var1);
                //将channel的文件描述符添加到pollWrapper对象中
                this.pollWrapper.addEntry(this.totalChannels, var1);
                ++this.totalChannels;
            }
        }
    }

pollWrapper对象介绍

class PollArrayWrapper {
	//用户存储文件描述符数组的native空间
    private AllocatedNativeObject pollArray;
    //上述内存空间的地址
    long pollArrayAddress;
    //每条记录所占空间大小为8个字节
    static short SIZE_POLLFD = 8;
    //每条记录中文件描述符的相对起始偏移量
    private static final short FD_OFFSET = 0;
    //每条记录中事件掩码的相对起始偏移量
    private static final short EVENT_OFFSET = 4;
    //数组大小
    private int size;
	...
	}

  pollArrayWrapper对象使用使用native数组存放文件描述符和事件掩码,并将这块内存的地址保存到pollArrayAddress中,在调用poll时将该地址传递给JNI。
  对象对外提供了有关FD、Event方法。

//添加文件描述符
void putDescriptor(int i, int fd) {
    pollArray.putInt(SIZE_POLLFD * i + FD_OFFSET, fd);
}

//添加事件掩码
void putEventOps(int i, int event) {
    pollArray.putShort(SIZE_POLLFD * i + EVENT_OFFSET, (short)event);
}
//获取事件掩码
int getEventOps(int i) {
    return pollArray.getShort(SIZE_POLLFD * i + EVENT_OFFSET);
}
//获取文件描述符
int getDescriptor(int i) {
    return pollArray.getInt(SIZE_POLLFD * i + FD_OFFSET);
}

  在pollArray中,每条记录占8个字节,前四个字节存放文件描述符,接着使用两个字节存放等待事件的掩码,最后两个字节存放事件发生事件的掩码(目前未使用)

growIfNeeded()

在注册通道前,首先判断是否需要扩容
每个线程最多只能管理1024个channel,每当channel超出1024则需要新增一个辅助线程,并将唤醒线程套接字文件描述符添加到辅助线程所管理的数组区域的第一个位置

private void growIfNeeded() {
        if (this.channelArray.length == this.totalChannels) {
        	//如果channelArray数组容量不够则扩容两倍
            int var1 = this.totalChannels * 2;
            SelectionKeyImpl[] var2 = new SelectionKeyImpl[var1];
            System.arraycopy(this.channelArray, 1, var2, 1, this.totalChannels - 1);
            this.channelArray = var2;
            //将pollWrapper对象的数组也扩容两倍
            this.pollWrapper.grow(var1);
        }
		
        if (this.totalChannels % 1024 == 0) {
        	//如果数组中大小为每个线程能够处理的最大通道数量的整数倍,则将唤醒线程的文件描述符添加到totalChannels位置
            this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, this.totalChannels);
            ++this.totalChannels;
            //增加一个辅助线程
            ++this.threadsCount;
        }
    }

SubSelector对象

  SubSelector对象封装了poll调用的逻辑和获取就绪selectionKey的方法。主线程和每个辅助线程都有自己的subSelector对象

readFds[0]存放该数组中元素个数
writeFds[0]存放该数组中元素个数
exceptFds[0]存放该数组中元素个数

private final class SubSelector {
		//该线程管理的selectionKey数组区域的起始地址
        private final int pollArrayIndex;
        //可读文件描述符数组
        private final int[] readFds;
        //可写文件描述符数组
        private final int[] writeFds;
        //异常文件描述符数组
        private final int[] exceptFds;
		...
		//主线poll调用所使用的方法
		private int poll() throws IOException {
            return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress, Math.min(WindowsSelectorImpl.this.totalChannels, 1024), this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
        }
		//辅助线程poll调用所使用的方法
        private int poll(int var1) throws IOException {
            return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress + (long)(this.pollArrayIndex * PollArrayWrapper.SIZE_POLLFD), Math.min(1024, WindowsSelectorImpl.this.totalChannels - (var1 + 1) * 1024), this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);
        }
    
	private native int poll0(long var1, int var3, int[] var4, int[] var5, int[] var6, long var7);
		}

  private native int poll0(long var1, int var3, int[] var4, int[] var5, int[] var6, long var7);参数说明:
var1:数组起始偏移量
var3: 要处理的文件描述符数量
var4:readFds
var5:writeFds
var6:exceptFds
var7:处理超时时间

  遍历数组,将就绪key添加到selectKeys集合中

private int processFDSet(long var1, int[] var3, int var4, boolean var5) {
            int var6 = 0;
			//遍历
            for(int var7 = 1; var7 <= var3[0]; ++var7) {
            	//获取句柄
                int var8 = var3[var7];
                //判断文件描述符是否我用于唤醒的
                if (var8 == WindowsSelectorImpl.this.wakeupSourceFd) {
                    synchronized(WindowsSelectorImpl.this.interruptLock) {
                        WindowsSelectorImpl.this.interruptTriggered = true;
                    }
                } else {
                	//获取selector
                    WindowsSelectorImpl.MapEntry var9 = WindowsSelectorImpl.this.fdMap.get(var8);
                    if (var9 != null) {
                    	//获取selectionKey
                        SelectionKeyImpl var10 = var9.ski;
                        if (!var5 || !(var10.channel() instanceof SocketChannelImpl) || !WindowsSelectorImpl.this.discardUrgentData(var8)) {
                        //如果key已经就绪则把事件累加到key
                            if (WindowsSelectorImpl.this.selectedKeys.contains(var10)) {
                                if (var9.clearedCount != var1) {
                                    if (var10.channel.translateAndSetReadyOps(var4, var10) && var9.updateCount != var1) {
                                        var9.updateCount = var1;
                                        ++var6;
                                    }
                                } else if (var10.channel.translateAndUpdateReadyOps(var4, var10) && var9.updateCount != var1) {
                                    var9.updateCount = var1;
                                    ++var6;
                                }

                                var9.clearedCount = var1;
                            } else {
                            	//如果key为就绪,则将key添加到selectKeys中
                                if (var9.clearedCount != var1) {
                                    var10.channel.translateAndSetReadyOps(var4, var10);
                                    if ((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {
                                        WindowsSelectorImpl.this.selectedKeys.add(var10);
                                        var9.updateCount = var1;
                                        ++var6;
                                    }
                                } else {
                                    var10.channel.translateAndUpdateReadyOps(var4, var10);
                                    if ((var10.nioReadyOps() & var10.nioInterestOps()) != 0) {
                                        WindowsSelectorImpl.this.selectedKeys.add(var10);
                                        var9.updateCount = var1;
                                        ++var6;
                                    }
                                }

                                var9.clearedCount = var1;
                            }
                        }
                    }
                }
            }

            return var6;
        }

首先忽略wakeupSourceFd,前面说了该文件描述符用于唤醒。
过滤fdMap不存在的文件描述符,这些文件描述符已经被取消了。
忽略OOB(紧急)数据,这些数据需要调用discardUrgentData读取并忽略。
根据key是否在SelectorKeys中决定是设置事件掩码还是更新事件掩码。

多线程轮询

  为了加快轮询速度,引入了多线程技术。每个线程负责最多管理1024个channel(实际上真正管理的只有1023个,其中有一个是用于唤醒的socket),前面讲过,在注册channel的时候讲过,每增加1023个channel的时候,创建一个辅助线程来处理这部分channel,每个辅助线程就是一个SelectThread对象

private final class SelectThread extends Thread {
		//辅助线程序号
        private final int index;
        //每个线程都有一个SubSelector对象
        final WindowsSelectorImpl.SubSelector subSelector;
        private long lastRun;
        private volatile boolean zombie;

        private SelectThread(int var2) {
            this.lastRun = 0L;
            this.index = var2;
            this.subSelector = WindowsSelectorImpl.this.new SubSelector(var2);
            this.lastRun = WindowsSelectorImpl.this.startLock.runsCounter;
        }

        void makeZombie() {
            this.zombie = true;
        }

        boolean isZombie() {
            return this.zombie;
        }

		//1.等待主线程执行通知
		//2.执行完后通知主线程执行完毕
        public void run() {
            for(; !WindowsSelectorImpl.this.startLock.waitForStart(this); WindowsSelectorImpl.this.finishLock.threadFinished()) {
                try {
                    this.subSelector.poll(this.index);
                } catch (IOException var2) {
                    WindowsSelectorImpl.this.finishLock.setException(var2);
                }
            }

        }
    }

 &emsp;等到主线程通知执行。每次线程执行完poll调用之后都需要等待主线程的下一次执行通知

private synchronized boolean waitForStart(SelectThread thread) {
        while (true) {
        	//当runsCounter == thread.lastRun时等待主线程的执行通知
        	//新建SelectThread对象时 runsCounter = thread.lastRun
            while (runsCounter == thread.lastRun) {
                try {
                    startLock.wait();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
            //如果isZombie()为真则线程退出runsCounter == thread.lastRun才成立
            if (thread.isZombie()) { // redundant thread
                return true; // will cause run() to exit.
            } else {
            	//更新lastRun ,这样下一次执行时才满足runsCounter == thread.lastRun为真
                thread.lastRun = runsCounter; // update lastRun
                return false; //   will cause run() to poll.
            }
        }
    }
}

doSelect()运行流程详解

protected int doSelect(long var1) throws IOException {
        if (this.channelArray == null) {
            throw new ClosedSelectorException();
        } else {
            this.timeout = var1;
            //移除已取消的key
            this.processDeregisterQueue();
            if (this.interruptTriggered) {
                this.resetWakeupSocket();
                return 0;
            } else {
            	//调整辅助线程
                this.adjustThreadsCount();
                //重置finishLock
                this.finishLock.reset();
                //开始执行
                this.startLock.startThreads();

                try {
                    this.begin();

                    try {
                    	//主线程poll
                        this.subSelector.poll();
                    } catch (IOException var7) {
                        this.finishLock.setException(var7);
                    }
				
                    if (this.threads.size() > 0) {
                    	//等待辅助线程完成
                        this.finishLock.waitForHelperThreads();
                    }
                } finally {
                    this.end();
                }
				//检测异常
                this.finishLock.checkForException();
                //删除已取消的key
                this.processDeregisterQueue();
                //更新selectKeys集合
                int var3 = this.updateSelectedKeys();
                //重置唤醒标志
                this.resetWakeupSocket();
                return var3;
            }
        }
    }

移除已取消的key

void processDeregisterQueue() throws IOException {
        Set var1 = this.cancelledKeys();
        synchronized(var1) {
            if (!var1.isEmpty()) {
                Iterator var3 = var1.iterator();

                while(var3.hasNext()) {
                    SelectionKeyImpl var4 = (SelectionKeyImpl)var3.next();

                    try {
                    	//移除操作
                        this.implDereg(var4);
                    } catch (SocketException var12) {
                        IOException var6 = new IOException("Error deregistering key");
                        var6.initCause(var12);
                        throw var6;
                    } finally {
                        var3.remove();
                    }
                }
            }

        }
    }

protected void implDereg(SelectionKeyImpl var1) throws IOException {
		//获取在数组中的下标
        int var2 = var1.getIndex();

        assert var2 >= 0;

        Object var3 = this.closeLock;
        synchronized(this.closeLock) {
            if (var2 != this.totalChannels - 1) {
            	//如果不是数组中的最后一项,则用数组中的最后一项替换要移除的那一项
                SelectionKeyImpl var4 = this.channelArray[this.totalChannels - 1];
                this.channelArray[var2] = var4;
                var4.setIndex(var2);
                //移除内核中的文件描述符
                this.pollWrapper.replaceEntry(this.pollWrapper, this.totalChannels - 1, this.pollWrapper, var2);
            }

            var1.setIndex(-1);
        }
		//清空channelArray数组最后一项
        this.channelArray[this.totalChannels - 1] = null;
        --this.totalChannels;
        if (this.totalChannels != 1 && this.totalChannels % 1024 == 1) {
        //当最后一位为唤醒线程的文件描述符时,移除该记录,并减少一个辅助线程
            --this.totalChannels;
            --this.threadsCount;
        }
		//移除其他地方存储的key
        this.fdMap.remove(var1);
        this.keys.remove(var1);
        this.selectedKeys.remove(var1);
        this.deregister(var1);
        SelectableChannel var7 = var1.channel();
        if (!var7.isOpen() && !var7.isRegistered()) {
        //关闭文件描述符
            ((SelChImpl)var7).kill();
        }

    }

调整辅助线程

private void adjustThreadsCount() {
        int var1;
        if (this.threadsCount > this.threads.size()) {
        	//如果辅助线程不够则添加
            for(var1 = this.threads.size(); var1 < this.threadsCount; ++var1) {
                WindowsSelectorImpl.SelectThread var2 = new WindowsSelectorImpl.SelectThread(var1);
                this.threads.add(var2);
                var2.setDaemon(true);
                var2.start();
            }
        } else if (this.threadsCount < this.threads.size()) {
        	//如果辅助线程多余则将多余的辅助线程的zombie标志置为true(相当于移除该辅助线程)
            for(var1 = this.threads.size() - 1; var1 >= this.threadsCount; --var1) {
                ((WindowsSelectorImpl.SelectThread)this.threads.remove(var1)).makeZombie();
            }
        }
    }

启动所有辅助线程

private synchronized void startThreads() {
            ++this.runsCounter;
            //唤醒所有在waitForStart(this)方法中等到的辅助线程
            this.notifyAll();
        }

###等待所有辅助线程完成

private synchronized void waitForHelperThreads() {
            if (this.threadsToFinish == WindowsSelectorImpl.this.threads.size()) {
            	//如果辅助线程还没执行则唤醒所有辅助线程
                WindowsSelectorImpl.this.wakeup();
            }
			//循环等待所有辅助线程完成
            while(this.threadsToFinish != 0) {
                try {
                    WindowsSelectorImpl.this.finishLock.wait();
                } catch (InterruptedException var2) {
                    Thread.currentThread().interrupt();
                }
            }

        }

参照博文

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: nio-multipart 是一个用于处理 Multipart 请求的依赖。Multipart 请求是一种 HTTP 请求格式,允许在单个请求中传输多种类型的数据,包括文本、文件、图像等。nio-multipart 提供了处理这种请求格式的工具和功能。 使用 nio-multipart,开发人员可以轻松地解析和处理 Multipart 请求。它提供了一种简单而灵活的方式,可以从请求中提取不同的数据部分,并以可读的格式进行操作。无论是处理上传的文件、解析数据字段还是获取请求的属性,nio-multipart 都提供了便捷的方法。 该依赖还支持文件上传,并提供了一套完整的 API 来处理上传的文件。使用 nio-multipart,开发人员可以轻松地接收上传的文件,并对其进行验证、存储和处理。它还支持上传文件的大小限制、文件类型验证等常见的文件处理需求。 nio-multipart 还提供了处理编码类型的支持。开发人员可以使用 nio-multipart 来处理不同的编码类型,例如表单数据的 URL 编码或二进制数据的 base64 编码。它还提供了编码类型之间的转换,以便开发人员可以方便地在不同的编码类型之间进行转换。 总而言之,nio-multipart 是一个用于处理 Multipart 请求的依赖,它提供了解析、处理和操作 Multipart 请求的功能,包括数据字段的提取、文件上传的支持和编码类型的处理等。使用 nio-multipart,开发人员可以更加便捷地处理复杂的请求格式,提高开发效率。 ### 回答2: NIO-Multipart是一个Java库工具,它提供了一种使用非阻塞I/O(NIO)实现的提供文件上传和处理多部分表单数据的方法。在Web应用程序开发中,常常需要处理用户提交的文件和表单数据,而NIO-Multipart可以帮助开发人员轻松地实现这些功能。 具体来说,NIO-Multipart提供了以下功能和依赖: 1. 文件上传:NIO-Multipart可以将客户端上载的文件保存到服务器的指定位置。它提供了一种简便的方法来访问上传的文件和相关的表单数据。 2. 处理多部分表单数据:NIO-Multipart可以解析提交的多部分表单数据,包括普通的表单字段和文件字段。它可以将这些数据提取出来,并以易于使用的方式提交给开发人员进行处理。 3. 依赖:NIO-Multipart依赖于Java的NIO库,使用其提供的非阻塞I/O(NIO)功能来处理文件上传和表单数据。 使用NIO-Multipart库,开发人员可以轻松处理和管理文件上传和多部分表单数据。通过利用非阻塞I/O(NIO)的优势,可以实现高效的文件传输和数据处理,提升了Web应用程序的性能和用户体验。 总之,NIO-Multipart是一种用于处理文件上传和多部分表单数据的Java库工具,它依赖于Java的NIO库,提供了一种使用非阻塞I/O实现的方法。 ### 回答3: nio-multipart是Java中的一个依赖库,用于处理基于NIO的多部分请求(multipart requests)。 首先,多部分请求是一种HTTP请求类型,允许在单个请求中传输多个文件和文本数据。它通常用于文件上传功能,在Web应用程序中非常常见。 nio-multipart提供了一种简单和高效的方式来解析和处理多部分请求。它基于Java的NIO(New I/O)库开发,该库提供了更快和更强大的I/O操作功能。 使用nio-multipart,我们可以轻松地从多部分请求中提取文件和文本数据,并对它们进行处理。它提供了易于使用的API,用于读取和解析请求的各个部分。我们可以访问每个部分的内容、类型、大小等信息,以便根据需要进行处理。 另外,nio-multipart还可以处理大文件上传,并在处理过程中节省内存。它使用NIO的特性,将请求数据分成一系列块,逐个处理而不是将整个文件加载到内存中。这种方式可以减少内存使用量,并提高性能和吞吐量。 综上所述,nio-multipart是一个用于处理基于NIO的多部分请求的依赖库。它简化了多部分请求的解析和处理过程,并提供了高效处理大文件上传的方法。它可以在Web应用程序中广泛使用,提高开发效率和性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值