Java NIO (四)NIO Selector类

1 选择器与注册

        选择器是什么?选择器和通道关系是什么?

        简单的说,选择器的使用是完成IO的多路复用,其主要工作是通道的注册、监听、事件查询。一个通道代表一条连接通路,通过选择器可以同时监听多个通道的IO(输入输出)状况。选择器和通道的关系是监控和被监控的关系。

        选择器提供了独特的API方法,能够选出(select)所监控的通道已经发生了哪些IO事件,包括读写就绪的IO操作事件。

        在NIO编程中,一般是一个单线程处理一个选择器,一个选择器可以监控很多通道。所以,通过选择器,一个单线程可以处理成千上万甚至更多的通道。在极端情况狂(数万个连接),只用一个线程就可以处理所有的通道,这样会大量减少线程之间上下文切换的开销。

        通道和选择器之间的关联通过register(注册)的方式完成。调用通道的register(Selector sel,int ops)方法,可以将通道实例注册到一个选择器中。register方法有两个参数:第一个参数指定通道注册到的选择器实例;第二个参数指定选择器要监控的IO事件类型。

        可供选择监控的通道IO事件类型包括以下四种:        

        (1) 可读:SelectionKey.OP_READ。

        (2) 可写:SelectionKey.OP_WRITE。

        (3) 连接:SelectionKey.OP_CONNECT。

        (4) 接收:SelectionKey.OP_ACCEPT。

        以上事件类型常量定义在SelectionKey类中。如果选择器要监听通道的多种事件,可以用"按位或"运算符来实现。例如,同时监听可读和可写IO事件:

int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE

        什么是IO事件?

        这里的IO事件不是对通道的IO操作,而是通道处于某个IO操作的就绪状态,表示通道具备执行某个IO操作的条件。例如,某个SocketChannel传输通道如果完成了和对端的三次握手,就会发生“链接就绪”事件;某个ServerSocketChannel服务器连接监听通道,在监听到一个新连接到来时,则会发生“接受就绪”事件等。

【说明】socket连接事件的核心原理和TCP连接的建立过程有关。

2 SelectableChannel

        并不是所有的通道都是可以被选择器监控或选择的。例如,FileChannel就不能被选择器复用。判读一个通道能否被选择器监控或选择有一个前提:判断它是否继承了抽象类SelectableChannel(可选择通道),如果是,就可以被选择,否则不能被选择。

        SelectableChannel类,提供了实现通道可选择性所需要的公共方法。Java NIO中所有网路连接socket通道都继承了SelectableChannel类,都是可选择的。

3 SelectionKey

        通道和选择器的监控关系注册成功后,就可以选择就绪事件,具体的选择工作可调用Selector的select()方法来完成。通过select()方法,选择器可以不断地选择通道中所发生操作的就绪状态,返回注册过的那些感兴趣的IO事件。换句话说,一旦在通道中发生了某些IO事件,并且是在选择器中注册过的IO事件,就会被选择器选中,并放入SelectionKey(选择键)的集合中。

        SelectionKey是什么呢?简单的说,SelectionKey就是那些被选择器选中的IO事件。前面讲到,一个IO事件发生(就绪装填达成)后,如果之前在选择器中注册过,就会被选择器选中,并放入SelectionKey中;如果之前没有注册过,那么即使发生了IO事件,也不会被选择器选中。SelectionKey和IO的关系可以简单地理解为SelectionKey就是被选中了的IO事件。

        在实际编程时,SelectionKey的功能是很强大的。通过SelectionKey,不仅可以获得通道的IO事件类型(比如SelectionKey.OP_READ),还可以获得选择器实例。

4 选择器使用流程

        选择器的使用主要有以下三步:

        (1)获取选择器实例。选择器实例是通过调用静态工厂方法open()来获取的。具体如下:

Selector selector = Selector.open();

        Selector的类方法open()的内部是向选择器SPI发出请求,通过默认的SelectorProvider(选择器提供者)对象获取一个新的选择器实例。Java中的SPI(服务提供者接口)是一种可以扩展的服务提供和发现机制。Java通过SPI的方式提供选择器的默认实现版本。也就是说,其他的服务提供者可以通过SPI的方式提供定制化版本的选择器的动态替换或者扩展。

        (2)将通道注册到选择器实例。要实现选择器管理通道,需要将通道注册到相应的选择器上,简单的示例代码如下:

        //1.获取Selector选择器Selector selector = Selector.open();//2.获取通道ServerSocketChannel serverChannel = ServerSocketChannel.open();ServerSocket serverSocket = serverChannel.socket();//3.设置为非阻塞serverChannel.configureBlocking(false);//4.绑定连接InetSocketAddress address = new InetSocketAddress(8080);serverSocket.bind(address);//5.将通道注册到选择器上,并注册的IO事件为:"接收新连接"serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        上面通过调用通道的register()方法将 ServerSocketChannel 注册到一个选择器上。当然,在注册之前,需要准备好通道。

        这里需要注意:注册到选择器的通道必须处于非阻塞模式下,否则将抛出异常。这意味着,FileChannel不能与选择器一起使用,因为FileChannel只有阻塞模式,不能切换到非阻塞模式;而socket相关的所有通道都可以。其次,一个通道并一定支持所有的四种IO事件。例如,服务器监听通道ServerSocketChannel 仅支持Accept(接收到新连接)IO事件,而传输通道SocketChannel则不同,它不支持Accept类型的IO时间。

        如何判断通道支持哪些事件呢?可以在注册之前通过通道的validOps()方法来获取该通道支持的所有IO事件集合。

        (3)选出感兴趣的IO就绪时间(选择键集合)。通过Selector的select()方法,选出已经注册的、已经就绪的IO事件,并且保存到SelectionKey集合中。SelectionKey集合保存在选择器实例内部,其元素为SelectionKey类型实例。调用选择器的selectionKeys()方法,可以取得选择键集合。

        接下来,迭代集合的每一个选择键,根据具体IO事件类型执行对应的业务操作。大致的处理流程如下:

public void test() throws IOException{//1.获取Selector选择器Selector selector = Selector.open();//2.获取通道ServerSocketChannel serverChannel = ServerSocketChannel.open();ServerSocket serverSocket = serverChannel.socket();//3.设置为非阻塞serverChannel.configureBlocking(false);//4.绑定连接InetSocketAddress address = new InetSocketAddress(8080);serverSocket.bind(address);//5.将通道注册到选择器上,并注册的IO事件为:"接收新连接"serverChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("serverChannel 在监听");//6.轮询感兴趣的IO就绪事件(选择键集合)while(selector.select() > 0){if(null == selector.selectedKeys()) continue;//7.获取键集合Iterator<SelectionKey> it = selector.selectedKeys().iterator();while(it.hasNext()){//8.获取单个的选择键,并处理SelectionKey key = it.next();if(null == key) continue;//IO事件ServerSocketChannel 服务器监听通道有新连接if(key.isAcceptable()){//业务处理}//IO事件传输通道连接成功else if(key.isConnectable()){}//IO事件传输通道可读else if(key.isReadable()){}//IO事件传输通道可写else if(key.isWritable()){}//处理完成后,移除选择键it.remove();}}}

        处理完成后,需要将选择键从SelectionKey集合中移除,以防止下一次循环时被重复处理。SelectionKey集合不能添加元素,则将抛出异常。

        用于选择就绪的IO事件select()方法有多个重载的实现版本,具体如下:

        1、select():阻塞调用,直到至少有一个通道发生了注册的IO事件。

        2、select(long timeout):和select()一样,但最长阻塞时间为timeout指定毫秒数。

        3、selectNow():非阻塞,不管有没有IO事件都会立刻返回。

        select()方法的返回值是整数类型(int),表示发生了IO事件的数量,即从上一次select到这一次select之间有多少通道发生了IO事件,更加准备地说是发生了选择器感兴趣(注册过)的IO事件数。

5 使用NIO实现Discard服务器的实战案例

        Discard服务器的功能很简单:仅读取客户端通道的输入数据,读取完成后直接关闭客户端通道,并且直接抛弃掉(Discard)读取到的数据。

public class NioDiscardServer {public static void startServer() throws IOException{//1.创建一个 Selector 选择器Selector selector = Selector.open();//2. 获取通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//3.设置为非阻塞serverSocketChannel.configureBlocking(false);//4.绑定监听端口serverSocketChannel.bind(new InetSocketAddress(8080));System.out.println("服务器启动成功");//5. 将通道注册到选择器上,并注册的IO事件为接收新连接serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//6. 轮询感兴趣的IO就绪时间(选择键集合)while(selector.select() > 0){//7. 获取选择键集合Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();while(selectedKeys.hasNext()){//8. 获取单个的选择键,并处理SelectionKey selectionKey = selectedKeys.next();//9.判断key是具体的什么事件if(selectionKey.isAcceptable()){System.out.println("发生了 新连接到来事件 "+ selectionKey.channel());//10. 若选择键的IO事件是"连接就绪"事件,就获取客户端连接SocketChannel socketChannel = serverSocketChannel.accept();//11. 切换非阻塞模式socketChannel.configureBlocking(false);//12. 将该通道注册到selector选择器上SelectionKey channelSK = socketChannel.register(selector,SelectionKey.OP_READ | SelectionKey.OP_CONNECT | SelectionKey.OP_CONNECT);}if(selectionKey.isWritable()){System.out.println("发生了写就绪事件 " + selectionKey.channel());}if(selectionKey.isConnectable()){System.out.println("发生了客户端 连接成功事件 " + selectionKey.channel());}if(selectionKey.isReadable()){System.out.println("发生了读 就绪事件 " + selectionKey.channel());//13. 若选择键的IO事件是“可读”事件,读取数据SocketChannel socketChannel = (SocketChannel) selectionKey.channel();//14. 读取数据ByteBuffer byteBuffer = ByteBuffer.allocate(1024);int length = 0;while((length = socketChannel.read(byteBuffer)) > 0){byteBuffer.flip();Logger.info(new String(byteBuffer.array(), 0, length));byteBuffer.clear();}socketChannel.close();}//15.移除选择键selectedKeys.remove();}}//16.关闭连接serverSocketChannel.close();}public static void main(String[] args) throws IOException {startServer();}
}

5.1 代码分析

        实现DiscardServer共分为16步,其中第7~15是循环执行的,不断查询,将感兴趣的IO事件选择到选择键集合中,然后通过.selector.selectedKeys()获取该选择键集合,并且进行迭代处理。在事件处理过程中,对于新建立的socketChannel客户端传输通道,也要注册到同一个选择器上,这样就能使用同一个选择线程不断地对所有的注册通道进行选择键的查询。

        在DiscardServer程序中,涉及两次选择器注册:一次是注册serverChannel(服务器通道);另一次是注册接收的socketChannel客户端传输通道。serverChannel所注册的是新连接的IO事件SelectionKey.OP_ACCEPT,socketChannel所注册的是可读IO事件SelectionKey.OP_READ。

        注册完成后,如果有事件发生,则DiscardServer在对选择键进行处理时先判断类型,然后进行相应的处理:

        (1)如果是SelectionKey.OP_ACCEPT新连接事件类型,代表serverChannel接收到新的客户端连接,发生了新连接时间,则通过服务器通道的accept方法获取新的socketChannel传输通道,并且将新通道注册到选择器。

        (2)如果是SelectionKey.OP_READ可读事件类型,代表某个客户端通道有数据可读,则读取选择键中socketChannel传输通道的数据,进行业务处理,这里是直接丢弃数据。

        

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

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

相关文章

长拖尾数据的采样方法

以下内容来自于ChatGPT长拖尾数据的采样方式&#xff1a; 对于具有长拖尾&#xff08;长尾&#xff09;分布的数据&#xff0c;通常使用传统的随机抽样方法可能不太适用&#xff0c;因为这样的分布意味着有一些极端值&#xff08;outliers&#xff09;会对整体分布产生较大影响…

【Linux】python版本控制和环境管理

文章目录 1.查看目前python的版本2.添加软件源并更新3.选择你想要下载的版本4.警示&#xff1a;没必要设置默认版本误区千万千万不要覆盖python3软链接解决办法 5.pip软件包管理最省心稍微麻烦换源 网上有很多教程都是教导小白去官方下载之后编译安装。但是&#xff0c;小白连c…

4.go 基础类型及类型转换

目录 概述basic types总结例子 Zero values总结例子 类型转换总结例子 结束 概述 go 版本&#xff1a; go1.20.13 basic types 总结 基本类型如下&#xff1a; boolstringint int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptrbyte // alias for uint8…

[pytorch入门] 2. tensorboard

tensorboard简介 TensorBoard 是一组用于数据可视化的工具。它包含在流行的开源机器学习库 Tensorflow 中.但是也可以独立安装&#xff0c;服务Pytorch等其他的框架 可以常常用来观察训练过程中每一阶段如何输出的 安装pip install tensorboard启动tensorboard --logdir<d…

蓝桥杯理历年真题 —— 数学

1. 买不到的数目 这道题目&#xff0c;考得就是一个日常数学的积累&#xff0c;如果你学过这个公式的话&#xff0c;就是一道非常简单的输出问题&#xff1b;可是如果没学过&#xff0c;就非常吃亏&#xff0c;在考场上只能暴力求解&#xff0c;或是寻找规律。这就要求我们什么…

Python图像处理【19】基于霍夫变换的目标检测

基于霍夫变换的目标检测 0. 前言1. 使用圆形霍夫变换统计图像中圆形对象2. 使用渐进概率霍夫变换检测直线2.1 渐进霍夫变换原理2.2 直线检测 3. 使用广义霍夫变换检测任意形状的对象3.1 广义霍夫变换原理3.2 检测自定义形状 小结系列链接 0. 前言 霍夫变换 (Hough Transform,…

H5112C PWM调光 无频闪 高性价比 支持12V 24V 36V 48V 60V 72V 内置MOS

PWM调光芯片是一种常用于LED调光控制的芯片&#xff0c;其工作原理如下&#xff1a; 脉冲宽度调制&#xff08;PWM&#xff09;&#xff1a;PWM是一种调制技术&#xff0c;通过改变信号的脉冲宽度来控制输出信号的平均功率。在PWM调光中&#xff0c;芯片会以一定的频率产生一系…

SpringCloud Alibaba 深入源码 - Nacos 和 Eureka 的区别(健康检测、服务的拉取和订阅)

目录 一、Nacos 和 Eureka 的区别 1.1、以 Nacos 注册流程来解析区别 一、Nacos 和 Eureka 的区别 1.1、以 Nacos 注册流程来解析区别 a&#xff09;首先&#xff0c;我们的服务启动时。都会把自己的信息提交给注册中心&#xff0c;然后注册中心就会把信息保存下来. 注册的…

Midjourney常见参数列表(极速版)

前言 参数是添加到提示词末尾的选项&#xff0c;可以改变图片的生成方式。参数可以改变图片的长宽比&#xff08;Aspect Ratios&#xff09;&#xff0c;切换不同的Midjourney模型版本&#xff08;Model Versions&#xff09;&#xff0c;改变使用的放大器&#xff08;Upscaler…

Leetcode 3016. Minimum Number of Pushes to Type Word II

Leetcode 3016. Minimum Number of Pushes to Type Word II 1. 解题思路2. 代码实现 题目链接&#xff1a;3016. Minimum Number of Pushes to Type Word II 1. 解题思路 这道题的话思路其实还是蛮简单的&#xff0c;显然我们的目的是要令对给定的word在键盘上敲击的次数最小…

ELK日志分析

目录 一、ELK概述 &#xff08;一&#xff09;ELK的定义 &#xff08;二&#xff09;ELK工具 1.ElasticSearch 2.Kiabana 3.Logstash &#xff08;1&#xff09;定义 &#xff08;2&#xff09;插件 ① input ② filter ③ output &#xff08;三&#xff09;可以添…

Spring、Spring-MVC、Mybatis、Mybatis-generator整合核心配置文件记录

Spring、Spring-MVC、Mybatis、Mybatis-generator整合核心配置文件记录 文章目录 Spring、Spring-MVC、Mybatis、Mybatis-generator整合核心配置文件记录1. spring.xml2. spring-mvc.xml3. mybatis-config.xml4. mybaits-generator.xml5. ehcach.xml6. web.xml Spring、Spring-…

快速排序(三)——hoare法

目录 ​一.前言 二.快速排序 hoare排法​ 三.结语 一.前言 本文给大家带来的是快速排序&#xff0c;快速排序是一种很强大的排序方法&#xff0c;相信大家在学习完后一定会有所收获。 码字不易&#xff0c;希望大家多多支持我呀&#xff01;&#xff08;三连&#xff0b;关…

Spring Boot3整合Druid(监控功能)

目录 1.前置条件 2.导依赖 错误依赖&#xff1a; 正确依赖&#xff1a; 3.配置 1.前置条件 已经初始化好一个spring boot项目且版本为3X&#xff0c;项目可正常启动。 作者版本为3.2.2最新版 2.导依赖 错误依赖&#xff1a; 这个依赖对于spring boot 3的支持不够&#…

微服务架构弹性伸缩策略方案

微服务架构的弹性伸缩策略是确保系统能够在不同工作负载下高效运行的关键。通过巧妙的策略&#xff0c;可以实现对每个微服务的独立伸缩&#xff0c;提高系统的灵活性和性能。本文将深入探讨微服务架构下的弹性伸缩方案。 1. 独立微服务的弹性伸缩 微服务架构的核心思想是将应…

用go语言删除重复文件

用go语言删除重复文件 文章目录 用go语言删除重复文件需求&#xff1a;将同级别目录&#xff08;只有一层的目录&#xff0c;没子目录&#xff09;下的重复文件删除打包成exe文件使用 需求&#xff1a;将同级别目录&#xff08;只有一层的目录&#xff0c;没子目录&#xff09;…

H5嵌入小程序适配方案

时间过去了两个多月&#xff0c;2024已经到来&#xff0c;又老了一岁。头发也掉了好多。在这两个月时间里都忙着写页面&#xff0c;感觉时间过去得很快。没有以前那么轻松了。也不是遇到了什么难点技术&#xff0c;而是接手了一个很烂得项目。能有多烂&#xff0c;一个页面发起…

Sim ROS2

ROS2_Galactic Ubuntu (Debian) — ROS 2 Documentation: Galactic documentation VMware界面大小调整两种方法超详细教程_vmware怎么调整虚拟机界面大小-CSDN博客 orca4 simulator https://github.com/clydemcqueen/orca4 Docker 【 全干货 】5 分钟带你看懂 Docker &#…

开源无代码应用程序生成器Saltcorn

什么是 Saltcorn &#xff1f; Saltcorn 是一个无需编写任何代码即可构建数据库 Web 应用程序的平台。它配备了一个吸睛的仪表板&#xff0c;丰富的生态系统、视图生成器以及支持主题的界面&#xff0c;使用直观的点击、拖放用户界面来构建整个应用程序。 软件的特点&#xff1…

100213. 按距离统计房屋对数目 II

100213. 按距离统计房屋对数目 II - 力扣&#xff08;LeetCode&#xff09; class Solution { public:vector<int> dif;void add(int l, int r, int d) {if (l > r) return;dif[l] d;dif[r 1] - d;return;}vector<long long> countOfPairs(int n, int x, in…