NIO之Selector解读

news2024/12/23 4:25:24

目录

Selector 简介

为什么会出现Selector

Selector 和 Channel 关系

可选择通道(SelectableChannel) 

Channel 注册到 Selector

选择键(SelectionKey)

Selector 的使用方法

Selector 的创建

注册 Channel 到 Selector 

轮询查询就绪操作

停止选择的方法


Selector 简介

为什么会出现Selector

  • 阻塞模式下,相关方法都会导致线程暂停
    • ServerSocketChannel.accept 会在没有连接建立时让线程暂停
    • SocketChannel.read 会在没有数据可读时让线程暂停
    • 阻塞的表现其实就是线程暂停了,暂停期间不会占用 cpu,但线程相当于闲置
// 使用 nio 来理解非阻塞模式, 单线程
// 0. ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(16);
// 1. 创建了服务器
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false); // 非阻塞模式
// 2. 绑定监听端口
ssc.bind(new InetSocketAddress(8080));
// 3. 连接集合
List<SocketChannel> channels = new ArrayList<>();
while (true) {
    // 4. accept 建立与客户端连接, SocketChannel 用来与客户端之间通信
    SocketChannel sc = ssc.accept(); // 非阻塞,线程还会继续运行,如果没有连接建立,但sc是null
    if (sc != null) {
        log.debug("connected... {}", sc);
        sc.configureBlocking(false); // 非阻塞模式
        channels.add(sc);
    }
    for (SocketChannel channel : channels) {
        // 5. 接收客户端发送的数据
        int read = channel.read(buffer);// 非阻塞,线程仍然会继续运行,如果没有读到数据,read 返回 0
        if (read > 0) {
            buffer.flip();
            debugRead(buffer);
            buffer.clear();
            log.debug("after read...{}", channel);
        }
    }
}
  • 非阻塞模式下,相关方法都会不会让线程暂停
    • 在 ServerSocketChannel.accept 在没有连接建立时,会返回 null,继续运行
    • SocketChannel.read 在没有数据可读时,会返回 0,但线程不必阻塞,可以去执行其它 SocketChannel 的 read 或是去执行 ServerSocketChannel.accept
    • 写数据时,线程只是等待数据写入 Channel 即可,无需等 Channel 通过网络把数据发送出去
  • 但非阻塞模式下,即使没有连接建立,和可读数据,线程仍然在不断运行,白白浪费了 cpu
  • 数据复制过程中,线程实际还是阻塞的(AIO 改进的地方)

Selector 和 Channel 关系

Selector 一般称 为选择器 ,也可以翻译为 多路复用器 。它是 Java NIO 核心组件中 的一个,用于检查一个或多个 NIO Channel(通道)的状态是否处于可读、可写。如 此可以实现单线程管理多个 channels,也就是可以管理多个网络链接。

 使用 Selector 的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个 线程,避免了线程上下文切换带来的开销。

  • 多路复用仅针对网络 IO、普通文件 IO 没法利用多路复用
  • 如果不用 Selector 的非阻塞模式,线程大部分时间都在做无用功,而 Selector 能够保证
    • 有可连接事件时才去连接
    • 有可读事件才去读取
    • 有可写事件才去写入
      • 限于网络传输能力,Channel 未必时时可写,一旦 Channel 可写,会触发 Selector 的可写事件

可选择通道(SelectableChannel) 

(1)不是所有的 Channel 都可以被 Selector 复用的。比方说,FileChannel 就不能 被选择器复用。判断一个 Channel 能被 Selector 复用,有一个前提:判断他是否继承了一个抽象类 SelectableChannel。如果继承了 SelectableChannel,则可以被复 用,否则不能。

(2)SelectableChannel 类提供了实现通道的可选择性所需要的公共方法。它是所有 支持就绪检查的通道类的父类。所有 socket 通道,都继承了 SelectableChannel 类 都是可选择的,包括从管道(Pipe)对象的中获得的通道。而 FileChannel 类,没有继 承 SelectableChannel,因此是不是可选通道。

(3)一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。通道和选择器之间的关系,使用注册的方式完成。SelectableChannel 可以被注册到 Selector 对象上,在注册的时候,需要指定通道的哪些操作,是 Selector 感兴趣的。

Channel 注册到 Selector

(1)使用 Channel.register(Selector sel,int ops)方法,将一个通道注册到一个 选择器时。第一个参数,指定通道要注册的选择器。第二个参数指定选择器需要查询的通道操作。

(2)可以供选择器查询的通道操作,从类型来分,包括以下四种:

  • - 可读 : SelectionKey.OP_READ
  • - 可写 : SelectionKey.OP_WRITE
  • - 连接 : SelectionKey.OP_CONNECT
  • - 接收 : SelectionKey.OP_ACCEPT

如果 Selector 对通道的多操作类型感兴趣,可以用“位或”操作符来实现: 比如:int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE ;

(3)选择器查询的不是通道的操作,而是通道的某个操作的一种就绪状态。什么是操作的就绪状态?一旦通道具备完成某个操作的条件,表示该通道的某个操作已经就绪, 就可以被 Selector 查询到,程序可以对通道进行对应的操作。比方说,某个 SocketChannel 通道可以连接到一个服务器,则处于“连接就绪”(OP_CONNECT)。 再比方说,一个 ServerSocketChannel 服务器通道准备好接收新进入的连接,则处于 “接收就绪”(OP_ACCEPT)状态。还比方说,一个有数据可读的通道,可以说是 “读就绪”(OP_READ)。一个等待写数据的通道可以说是“写就绪”(OP_WRITE)。

选择键(SelectionKey)

(1)Channel 注册到后,并且一旦通道处于某种就绪的状态,就可以被选择器查询到。 这个工作,使用选择器 Selector 的 select()方法完成。select 方法的作用,对感兴 趣的通道操作,进行就绪状态的查询。

(2)Selector 可以不断的查询 Channel 中发生的操作的就绪状态。并且挑选感兴趣 的操作就绪状态。一旦通道有操作的就绪状态达成,并且是 Selector 感兴趣的操作, 就会被 Selector 选中,放入选择键集合中。

(3)一个选择键,首先是包含了注册在 Selector 的通道操作的类型,比方说 SelectionKey.OP_READ。也包含了特定的通道与特定的选择器之间的注册关系。 开发应用程序是,选择键是编程的关键。NIO 的编程,就是根据对应的选择键,进行 不同的业务逻辑处理。

(4)选择键的概念,和事件的概念比较相似。一个选择键类似监听器模式里边的一个 事件。由于 Selector 不是事件触发的模式,而是主动去查询的模式,所以不叫事件 Event,而是叫 SelectionKey 选择键。

Selector 的使用方法

Selector 的创建

通过调用 Selector.open()方法创建一个 Selector 对象,如下:

Selector selector = Selector.open();

注册 Channel 到 Selector 

要实现 Selector 管理 Channel,需要将 channel 注册到相应的 Selector 上

// 1、获取 Selector 选择器
Selector selector = Selector.open();

// 2、获取通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

// 3.设置为非阻塞
serverSocketChannel.configureBlocking(false);

// 4、绑定连接
serverSocketChannel.bind(new InetSocketAddress(9999));

// 5、将通道注册到选择器上,并制定监听事件为:“接收”事件
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

上面通过调用通道的 register()方法会将它注册到一个选择器上。

首先需要注意的是:

(1)与 Selector 一起使用时,Channel 必须处于非阻塞模式下,否则将抛出异常IllegalBlockingModeException。这意味着,FileChannel 不能与 Selector 一起使用,因 为 FileChannel 不能切换到非阻塞模式,而套接字相关的所有的通道都可以。

(2)一个通道,并没有一定要支持所有的四种操作。比如服务器通道ServerSocketChannel 支持 Accept 接受操作,而 SocketChannel 客户端通道则不支持。 可以通过通道上的 validOps()方法,来获取特定通道下所有支持的操作集合。

轮询查询就绪操作

(1)通过 Selector 的 select()方法,可以查询出已经就绪的通道操作,这些就绪的状态集合,包存在一个元素是 SelectionKey 对象的 Set 集合中。

(2)下面是 Selector 几个重载的查询 select()方法:

  • - select():阻塞到至少有一个通道在你注册的事件上就绪了。
  • - select(long timeout):和 select()一样,但最长阻塞事件为 timeout 毫秒。
  • - selectNow():非阻塞,只要有通道就绪就立刻返回。

select()方法返回的 int 值,表示有多少通道已经就绪,更准确的说,是自前一次 select方法以来到这一次 select 方法之间的时间段上,有多少通道变成就绪状态。

例如:首次调用 select()方法,如果有一个通道变成就绪状态,返回了 1,若再次调用 select()方法,如果另一个通道就绪了,它会再次返回 1。如果对第一个就绪的 channel 没有做任何操作,现在就有两个就绪的通道,但在每次 select()方法调用之间, 只有一个通道就绪了。

一旦调用 select()方法,并且返回值不为 0 时,在 Selector 中有一个 selectedKeys()方 法,用来访问已选择键集合,迭代集合的每一个选择键元素,根据就绪操作的类型, 完成对应的操作:

Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();

if(key.isAcceptable()) {

// a connection was accepted by a ServerSocketChannel. 
} else if (key.isConnectable()) {

// a connection was established with a remote server. 
} else if (key.isReadable()) {

// a channel is ready for reading

} else if (key.isWritable()) {

// a channel is ready for writing

}
keyIterator.remove();
}

停止选择的方法

选择器执行选择的过程,系统底层会依次询问每个通道是否已经就绪,这个过程可能 会造成调用线程进入阻塞状态,那么我们有以下三种方式可以唤醒在 select()方法中 阻塞的线程。

wakeup()方法 :通过调用 Selector 对象的 wakeup()方法让处在阻塞状态的 select()方法立刻返回该方法使得选择器上的第一个还没有返回的选择操作立即返回。如果当前没有进行中 的选择操作,那么下一次对 select()方法的一次调用将立即返回。

close()方法 :通过 close()方法关闭 Selector,

该方法使得任何一个在选择操作中阻塞的线程都被唤醒(类似 wakeup()),同时 使得注册到该 Selector 的所有 Channel 被注销,所有的键将被取消,但是 Channel 本身并不会关闭。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/600576.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全志Tina Linux下如何编译glibc库

本文整理自问答&#xff1a;https://bbs.aw-ol.com/topic/3615/ make工具 注意由于AW服务器make版本为3.8.1&#xff0c;在编译glibc高版本时候不兼容&#xff0c;所以需要更新make工具。假如服务器make版本较高&#xff0c;可以不用更新make工具。 网址 http://ftp.gnu.org/…

chatgpt赋能python:Python函数介绍

Python函数介绍 函数是Python编程中最重要的概念之一。它是一段可重用代码的集合&#xff0c;通过一个名字来暴露出来&#xff0c;可以在Python程序的多个地方调用。函数可以接收任意数量的参数&#xff0c;也可以返回值。Python中函数定义使用关键字 def。 Python函数定义 …

ChatGPT教你学Python爬虫

“ chatgpt作为一个编程助手&#xff0c;虽然不能帮我们解决一个复杂的业务需求&#xff0c;但在处理一些具体工具类需求上&#xff0c;能够快速生成我们需要的代码&#xff0c;尤其对一些编程初学者&#xff0c;不仅能借助chatgpt快速完成自己的小工具&#xff0c;还能在与cha…

硬件 TCP/IP 协议栈

目录 全硬件的TCP/IP 协议栈简介以太网接入单片机方案以太网接口芯片CH395Q 简介以太网接口芯片CH395Q 命令简介以太网接口芯片CH395Q 寄存器配置与使用移植CH395Q 源码 TCP_Client 实验TCPClient 配置流程TCPClient 实验硬件设计程序设计下载验证 WebServer 实验WebServer 简介…

chatgpt赋能python:Python编程:如何写文件到指定目录

Python编程&#xff1a;如何写文件到指定目录 Python是一种高级编程语言&#xff0c;它具有简单易学、可读性高的特点&#xff0c;并且能够应用于各种领域&#xff0c;尤其是数据科学和机器学习领域。在Python中&#xff0c;我们可以通过编写代码实现将文件写入到指定目录中。…

JavaEE(系列18) -- 文件操作I/O

前言: 我们平时所说的文件都是指硬盘上的文件,而我们之前在JavaSE阶段代码绝大部分都是围绕内存展开的,定义个变量&#xff0c;其实就是内存上申请空间。 内存和硬盘的区别: 速度&#xff1a;内存比硬盘快很多。空间&#xff1a;内存空间比硬盘小。成本&#xff1a;内存比硬盘贵…

利用CiteSpace快速锁定领域内最新研究热点并制作精美的可视化专题图

【基于Citespace和vosviewer文献计量学相关论文 】 ​ 01 文献计量学方法与应用 1. 文献计量学方法基本介绍 2. 与其他综述方法区别联系 3. 各学科领域应用趋势近况 4. 主流分析软件优缺点对比 5. 经典高分10SCI思路复盘 6. 软件安装与Java环境配置 02 主题确定、数据检…

Power BI 如何高效管理度量值和字段

内容说明 背景需求&#xff1a; 当分析场景涉及大量数据和分析度量值时&#xff0c;为了更好的区分原始数据和用于分析的度量值&#xff0c;需要合理安排和管理数据字段。 本文总结了三种方法&#xff1a; 移动度量值位置创建字段文件夹&#xff1a;子文件夹和多个文件夹创建…

chatgpt赋能python:Python函数改名:为什么需要改名以及如何改名

Python函数改名&#xff1a;为什么需要改名以及如何改名 在Python编程中&#xff0c;函数是非常常见和重要的代码语句&#xff0c;用于完成特定的任务或操作。然而&#xff0c;在实际开发中&#xff0c;我们可能需要对已有函数进行改名&#xff0c;这个过程可能并不简单&#…

国产BI工具大比拼,帆软Fine BI和观远BI到底该怎么选型?

之前写了一篇关于BI如何选型的文章《「BI选型秘诀」BI工具不知道怎么选择&#xff1f;这张选型评分表你一定要收藏&#xff01;》。 文章发布后&#xff0c;不少朋友私信我&#xff0c;想要我出个国产BI工具的测评&#xff0c;今天就开始第一期&#xff1a;帆软 VS 观远 仍旧…

el-form嵌套el-table编辑,校验信息显示在气泡框中

文章目录 概要整体架构流程技术名词解释技术细节 概要 提示&#xff1a;这里可以添加技术概要 正常情况下&#xff0c;el-table可编辑表格&#xff0c;如果输入框内容不合理的情况下&#xff0c;错误提示会显示在el-input下方对应。 但是&#xff0c;我不得不将错误提示放到el…

chatgpt赋能python:Python几行几列是什么?

Python几行几列是什么&#xff1f; Python几行几列是一款基于Python语言开发的优秀的划线工具。这个工具能够帮助开发者轻松创建、管理和分享自己的Python代码。Python几行几列是一款高效、易用的Python编辑器&#xff0c;它既可以用于快速编写Python代码&#xff0c;也可以用…

PG数据库 column “has_submit_am“ is of type numeric but expression is of type bool

目录 场景&#xff1a; 现象&#xff1a; 复盘分析&#xff1a; 解决方法&#xff1a; 测试&#xff1a; 扩展&#xff1a; 场景&#xff1a; 今天遇到一个问题&#xff0c;现场数据库中做了boolean隐式转换smallint时在执行对应的插入时一直报错 column "has_submit…

【Python NLTK】零基础也能轻松掌握的学习路线与参考资料

Python 自然语言处理工具包&#xff08;Natural Language Toolkit&#xff0c;简称 NLTK&#xff09;是一款 Python 的库&#xff0c;主要用于处理自然语言的相关问题&#xff0c;如文本清洗、标记化、分词、语义分析、词性标注、文本分类等功能&#xff0c;是数据科学家和机器…

Facebook不同账户类型的投放格式【Facebook企业户】

在Facebook企业户中&#xff0c;不同的广告类型具有多样化的投放格式&#xff0c;旨在帮助企业更好地吸引目标受众、提升品牌曝光和实现营销目标。 一、广告类型的概述 Facebook提供了多种广告类型&#xff0c;包括图片广告、视频广告、幻灯片广告、动态产品广告等。每种广告类…

vue项目H5页面在苹果手机点击输入框输入内容时 页面自动放大

遇到的问题 用vue写H5页面时,iPhone手机点击input搜索框,输入内容时,页面会自动放大。 解决方法 在public文件夹下找到html文件&#xff0c;打开后加上user-scalableno 就行 <meta name"viewport" content"widthdevice-width,initial-scale1.0,user-scala…

【从零开始进行高精度手眼标定 eye in hand(小白向)1 原理推导】

从零开始进行高精度手眼标定 eye in hand&#xff08;小白向&#xff09;1 原理推导 前言原理推导公式推导为什么在数据采集中至少需要两个位姿信息 MATLAB编程计算A矩阵的计算和获取matlab计算代码B矩阵的计算和获取matlab计算矩阵B 前言 最近由于组内的相关工作需求&#xf…

pytorch中Dataset、Dataloader、Sampler、collate_fn相互关系和使用说明

参考&#xff1a; https://blog.csdn.net/Chinesischguy/article/details/103198921 参考&#xff1a; https://zhuanlan.zhihu.com/p/76893455 参考&#xff1a;https://blog.csdn.net/lilai619/article/details/118784730 参考&#xff1a;https://pytorch.org/docs/stabl…

06 【Vue数据监视 v-model双向绑定】

1.Vue数据监视 1.1 问题演示 先来个案例引入一下&#xff1a; <!-- 准备好一个容器--> <div id"root"><h2>人员列表</h2><button click"updateMei">更新马冬梅的信息</button><ul><li v-for"(p,inde…