NIO - selector简单介绍

一 前言

        selector作为NIO当中三大组件之一,是处理NIO非阻塞模式下的核心组件,它允许一个单个线程管理多个通道。

        NIO下的阻塞模式

        因为对于阻塞模式下的NIO模式,存在很大的问题,即使在单线程下,对应的服务端也会一直进行等待客户端的连接,甚至在建立连接之后读写模式下也会阻塞,这就导致只能让当前访问结束之后才能进行下一个客户端的访问,无法并行访问。这里我们主要介绍

        NIO下的非阻塞模式

        对于NIO下的非阻塞模式,我们只需要对于channel通道关闭阻塞模式即可。

        //1。注册连接 创建连接通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//1.1 设置为非阻塞模式serverSocketChannel.configureBlocking(false);

                Server层代码:

public class Server {private static final Logger log = LoggerFactory.getLogger(Server.class);//创建集合,存储对应的客户端信息public static ArrayList<SocketChannel> socketChannels = new ArrayList<>();public static void main(String[] args) throws IOException {//0.设置byteBuffer缓冲区存储数据ByteBuffer buffer = ByteBuffer.allocate(1024);//1。注册连接 创建连接通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//1.1 设置为非阻塞模式serverSocketChannel.configureBlocking(false);//2.设置监听端口serverSocketChannel.bind(new InetSocketAddress(8080));while (true) {log.debug("connecting");//3.创建与客户端之间的连接 每有一个客户端连接,都会从这里进行监听//3.1 非阻塞模式下,说客户端与服务端连接的建立在这里不会堵塞,会直接通行,但是这里如果没有对应的客户端访问//那么返还值就为NULL,根据这个我们可以加一些判断对于这些空值进行处理SocketChannel accept = serverSocketChannel.accept();if (accept != null) {//添加数据socketChannels.add(accept);}for (SocketChannel socketChannel : socketChannels) {//获取数据log.debug("准备读取数据了!");//在非阻塞模式下,这里的读取也不会再停止,对应的会继续运行,如果没有读取到数据将会返还为空int read = socketChannel.read(buffer.flip());//读取数据buffer.flip();if (read!=0){log.debug(String.valueOf(buffer));log.debug("数据读取完毕");}//清空数据变为读取 清空数据buffer.clear();}}}
}

                Client层代码: 


//客户端
public class Client {public static void main(String[] args) throws IOException {//1.创建连接通道SocketChannel socketChannel = SocketChannel.open();//2.设置连接服务器的地址socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));System.out.println("waiting for connection");socketChannel.write(Charset.defaultCharset().encode("Hello World!"));}
}

                Tips: 在非阻塞模式下有两点需要注意, 在服务端与客户端之间创建连接的时候,如果当前没有服务端连接,返还的值变为NULL,如下操作

            //那么返还值就为NULL,根据这个我们可以加一些判断对于这些空值进行处理SocketChannel accept = serverSocketChannel.accept();

                另一方面,在进行读取操作的时候也是一样,如果对应的客户端没有发送消息,读取的数据就会为0,因此我们可以在此基础上添加一些判断条件,如下              

  int read = socketChannel.read(buffer.flip());//读取数据buffer.flip();if (read!=0){log.debug(String.valueOf(buffer));log.debug("数据读取完毕");}

                缺点:但是NIO非阻塞模式解决了阻塞模式下各种操作执行之间的阻塞关系,不会因为当前没有客户端连接而阻塞,换为一直都在执行当中。但是同时的也带来了一定的问题:一直循环不断的连接(accept)与读(read),如果我们一直都没有客户端连接,那么就会造成CPU资源的浪费,即使没有数据读写,也会让CPU一直处于资源消耗中~

二 Slector模式

        在我之前的博客当中有对于NIO一些基础知识的介绍,也有关Selector这方面的介绍,多家对比,大家可以去看看呦 ^ - ^

Netty - NIO基础学习-CSDN博客

        这里我就直接写一个比较基础的Selector代码,其中先不包含读写,仅仅包含如何使用Selector进行与客户端之间建立连接,以及如何监听客户端,让客户端与服务端之间建立连接

        Server层:

public class Server {private static final Logger log = LoggerFactory.getLogger(Server.class);//创建集合,存储对应的客户端信息public static void main(String[] args) throws IOException {//1.创建SelectorSelector selector = Selector.open();//2.创建服务端通道ServerSocketChannel ssc = ServerSocketChannel.open();ssc.configureBlocking(false);ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8080));//3.创建客户端与Selector之间的连接,将两者之间建立连接SelectionKey sscKey = ssc.register(selector, 0, null);//4.建立连接之后就需要绑定对应的channel的事件类型,事件类型包括四种:accept connect read write 是哪一种事件需要我们自己进行绑定//这里我们这个SelectionKey作为管理员只需要关注对应的客户端是否建立连接即可sscKey.interestOps(SelectionKey.OP_ACCEPT);while (true) {//5.使用select进行检查,如果没有事件发生就在这里阻塞,有事件发生才会继续进行 这里类似一个监听器,如果有连接这种事件发生才会执行之后的操作selector.select();//6.使用迭代器处理发生的事件 selectKeys当中会存储所有的KEY,这里如果我们想要对其进行更多的操作,例如删除。那么就必须使用到迭代器Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {//获取KEYSelectionKey key = iterator.next();log.debug("Selected key: {}", key);//6.使用KEY获取对应的SSC之后创建连接ServerSocketChannel channel = (ServerSocketChannel) key.channel();channel.accept();log.debug("Accepted connection");}}}
}

        Cilent层:

//客户端
public class Client {public static void main(String[] args) throws IOException {//1.创建连接通道SocketChannel socketChannel = SocketChannel.open();//2.设置连接服务器的地址socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));System.out.println("waiting for connection");socketChannel.write(Charset.defaultCharset().encode("Hello World!"));}
}

        在使用Selector的时候需要注意几点:

        1.建立与channel之间的关联

                首先便是建立起Selector跟对应的Channel之间的关联

                我们需要使用到ServerSocketChannel下的注册,用以建立关联

//3.创建客户端与Selector之间的连接,将两者之间建立连接SelectionKey sscKey = ssc.register(selector, 0, null);

        2.SelectionKey绑定对应的channel事件

                 这里先简单说一下channel的几个事件类型,主要有:

                        accept: 建立客户端与服务端之间的连接

                        connect: 客户端与服务端连接建立之后自动触发的

                        read: 读操作

                        write: 写操作

                当前的selector只需要作为一个管理员,管理对应其自己的事件即可,所以我们需要设置与对应KEY的关联channel事件类型,如下图:

                设定完成之后,如果触发了对应的事件,选择器就会监听到

        3.触发事件select()

                这里我们需要用到selector的核心方法 - select()方法。

                select方法会处于阻塞状态,除非 :

                        1> 已注册通道好的已经开始发送I/O请求

                        2>线程中断

                        3>当前的选择器Slector已被关闭

                select有一个返回值,代表的是当前选择器当中已经准备好I/O请求的通道个数

                也就是说,客户端向服务端发送请求的时候,非阻塞状态才会被激活。

                select成功激活之后,会将当前检测到的事件的SelectKey放进迭代器当中

                但是迭代器当中的数据是不会自动删除的,这一点很重要

                建立连接之后执行的代码逻辑如下:

        while (true) {//5.使用select进行检查,如果没有事件发生就在这里阻塞,有事件发生才会继续进行 这里类似一个监听器,如果有连接这种事件发生才会执行之后的操作selector.select();//6.使用迭代器处理发生的事件 selectKeys当中会存储所有的KEY,这里如果我们想要对其进行更多的操作,例如删除。那么就必须使用到迭代器Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {//获取KEYSelectionKey key = iterator.next();log.debug("Selected key: {}", key);//6.使用KEY获取对应的SSC之后创建连接ServerSocketChannel channel = (ServerSocketChannel) key.channel();channel.accept();log.debug("Accepted connection");}}

三 selector处理读写

        我们需要谨记一个概念,一个Selector当中可以存储多个KEY,那么实际上读写操作也就是再创建一个KEY放入Selector当中,并设置对应的事件类型即可!

        上文提到,只要有对应的事件触发,那么select就会将其放置到迭代器的循环当中,也就是说所有事件类型的KEY,都会被存放在其中,但是不同的事件类型实际上执行的代码是不一样的,所以我们需要在迭代循环的时候根据KEY的事件类型不同进行区分

        综上,我们改良之后的Server代码如下:

public class Server {private static final Logger log = LoggerFactory.getLogger(Server.class);//创建集合,存储对应的客户端信息public static void main(String[] args) throws IOException {//1.创建SelectorSelector selector = Selector.open();//2.创建服务端通道ServerSocketChannel ssc = ServerSocketChannel.open();ssc.configureBlocking(false);ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8080));//3.创建客户端与Selector之间的连接,将两者之间建立连接SelectionKey sscKey = ssc.register(selector, 0, null);//4.建立连接之后就需要绑定对应的channel的事件类型,事件类型包括四种:accept connect read write 是哪一种事件需要我们自己进行绑定//这里我们这个SelectionKey作为管理员,只需要关注对应的客户端是否建立连接即可//我们设置当前的KEY用来专门管理客户端的连接 accept()sscKey.interestOps(SelectionKey.OP_ACCEPT);while (true) {//5.使用select进行检查,如果没有事件发生就在这里阻塞,有事件发生才会继续进行 这里类似一个监听器,如果有连接这种事件发生才会执行之后的操作selector.select();//6.使用迭代器处理发生的事件 selectKeys当中会存储所有的KEY,这里如果我们想要对其进行更多的操作,例如删除。那么就必须使用到迭代器Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {//获取KEYSelectionKey key = iterator.next();//判断对应的KEY的类型if (key.isAcceptable()) {log.debug("Accept Selected key: {}", key);//6.使用KEY获取对应的SSC之后创建连接ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();//创建连接,返还客户端连接通道SocketChannel sc = serverSocketChannel.accept();sc.configureBlocking(false);SelectionKey ssKey = sc.register(selector, SelectionKey.OP_READ);//绑定事件ssKey.interestOps(SelectionKey.OP_READ);log.debug("Accept connection");} else if (key.isReadable()) {//如果对应的KEY是读取类型的ByteBuffer buffer = ByteBuffer.allocate(1024);SocketChannel channel = (SocketChannel) key.channel();channel.read(buffer);buffer.flip();System.out.println(buffer);buffer.compact();}}}}
}

        其实上面的代码没有变化,知识迭代的过程代码发生变化:

        1>对读(Read)操作开创新的KEY

                在连接之后,又注册了当前通道的KEY,设置其事件类型为READ,并且将其交给selector管理

if (key.isAcceptable()) {log.debug("Accept Selected key: {}", key);//6.使用KEY获取对应的SSC之后创建连接ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();//创建连接,返还客户端连接通道SocketChannel sc = serverSocketChannel.accept();sc.configureBlocking(false);SelectionKey ssKey = sc.register(selector, SelectionKey.OP_READ);//绑定事件ssKey.interestOps(SelectionKey.OP_READ);log.debug("Accept connection");}

        2>新增有关读取数据的操作

else if (key.isReadable()) {//如果对应的KEY是读取类型的ByteBuffer buffer = ByteBuffer.allocate(1024);SocketChannel channel = (SocketChannel) key.channel();channel.read(buffer);buffer.flip();System.out.println(buffer);buffer.compact();}

                但是以上代码还是存在弊端,运行之后,发现报错

               分析一下案发现场:

               我们上面提到,执行select之后,变为非阻塞状态,==》 之后会将对应的事件的KEY交给下边的迭代器集合。这里我们的客户端发送了连接申请,并且写入了数据。

                1.那么我们的服务器检测到事件之后,将连接申请相关事件的KEY提交给selectKeys的集合当中(也就是迭代器集合)

                2.类型匹配,匹配到了key.isAcceptable(),进入并且创建连接,又新增一个KEY,绑定读操作事件,当前if结束

                3.循环回到accept(),检测到写操作,之后将写操作的KEY提交给selectKeys当中

                4.类型匹配,我们发现,之前已经完成过的事件,也就是连接事件依旧存在于迭代循环当中!但是我们这个事件已经处理结束!因此,accept()之后的数据为NULL

                真相大白,其实就是因为我们没有删除对应在selectKeys集合当中已执行的KEY所导致的。      

                这也就是为什么必须使用iterator.remove()的原因了。

                在一开始迭代就删除当前的元素即可。

while (iterator.hasNext()) {//在一开始执行就直接移除这个KEYiterator.remove();//获取KEYSelectionKey key = iterator.next();//判断对应的KEY的类型if (key.isAcceptable()) {log.debug("Accept Selected key: {}", key);//6.使用KEY获取对应的SSC之后创建连接ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();//创建连接,返还客户端连接通道SocketChannel sc = serverSocketChannel.accept();sc.configureBlocking(false);SelectionKey ssKey = sc.register(selector, SelectionKey.OP_READ);//绑定事件ssKey.interestOps(SelectionKey.OP_READ);log.debug("Accept connection");} else if (key.isReadable()) {//如果对应的KEY是读取类型的ByteBuffer buffer = ByteBuffer.allocate(1024);SocketChannel channel = (SocketChannel) key.channel();channel.read(buffer);buffer.flip();System.out.println(buffer);buffer.compact();}}

今天写这个写了不少时间,明日继续更新,大家是不是都要考六级了呢 = -- = 艾

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

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

相关文章

C语言:分支结构

C语言&#xff1a;分支结构 分支结构 问题引出 我们在程序设计往往会遇到如下的问题&#xff0c;比如下面的函数的计算 也就是我们是必须要通过一个条件的结果来选择下一步的操作&#xff0c;算法上属于一个分支结构&#xff0c;C语言中实现分支结构主要使用if语句 条件判断…

利用anzocapital昂首资本技术优化订单执行

在金融市场的深海中&#xff0c;anzocapital昂首资本作为内行&#xff0c;深知订单执行的技术缺陷如何悄然侵蚀交易者的利润。那么&#xff0c;一个懂交易的人会如何避免这些缺陷&#xff0c;确保自己的投资策略不被市场波动所左右呢? 在订单执行过程中&#xff0c;技术缺陷可…

RTR Chaptor11 下

全局光照 定向遮蔽预计算定向遮蔽定向遮蔽的动态计算使用定向屏蔽进行着色 满反射全局光照表面预照明定向表面预照明预计算传输存储方法动态漫反射全局光照光照传播体积基于体素的方法屏幕空间方法其他方法 镜面全局光照局部环境贴图环境贴图的动态更新基于体素的方法平面反射屏…

超越DFINE最新目标检测SOTA模型DEIM

代码地址&#xff1a;https://github.com/ShihuaHuang95/DEIM 论文地址&#xff1a;DEIM: DETR with Improved Matching for Fast Convergence 论文中文版&#xff1a;DEIM: 改进匹配的 DETR 以实现快速收敛 以下是文章的主要贡献和发现&#xff1a; DEIM框架&#xff1a;提…

位运算的总结--奇思妙解

目录 前言 先回顾常用的位运算 1&#xff1a;给一个数 n &#xff0c;确定它的二进制表示中的第x位是0 还是 1 2&#xff1a;将一个数 n 的二进制表示的第x 位修改成 1 3&#xff1a;将一个数 n 的二进制表示的第 x位修改成 0 4&#xff1a;与位图联系 5&#xff1a;提取一…

语音识别flask接口开发

要开发一个flask语音识别接口&#xff0c;首先要解决语音文件在网络中的传输问题&#xff0c;然后选识别算法进行识别 文章目录 1、以二进制文件流方式上次语音2、网页端长连接流式上传语音文件3、语音识别接口 1、以二进制文件流方式上次语音 python服务端代码&#xff0c;以…

Kafka怎么发送JAVA对象并在消费者端解析出JAVA对象--示例

1、在pom.xml中加入依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-stream-kafka</artifactId><version>3.1.6</version></dependency> 2、配置application.yml 加入Kafk…

JS中的原型链与继承

原型链的类比 JS中原型链&#xff0c;本质上就是对象之间的关系&#xff0c;通过protoype和[[Prototype]]属性建立起来的连接。这种链条是动态的&#xff0c;可以随时变更。 这个就跟C/C中通过指针建立的关系很相似&#xff0c;比如&#xff0c;通过指针建立一个链表&#xf…

CSS学习记录04

CSS边框 CSS border 属性指定元素边框的样式、宽度和颜色。border-style 属性指定要显示的边框类型。dotted - 定义点线边框dashed - 定义虚线边框solid - 定义实线边框double - 定义双边框groove - 定义3D坡口边框&#xff0c;效果取决于border-color值ridge - 定义3D脊线边框…

一文了解模式识别顶会ICPR 2024的研究热点与最新趋势

简介 对模式识别研究领域前沿方向的跟踪是提高科研能力和制定科研战略的关键。本文通过图文并茂的方式介绍了ICPR 2024的研究热点与最新趋势&#xff0c;帮助读者了解和跟踪模式识别的前沿研究方向。本推文的作者是黄星宇&#xff0c;审校为邱雪和许东舟。 一、会议介绍 ICPR…

福昕PDF低代码平台

福昕PDF低代码平台简介 福昕PDF 低代码平台是一款创新的工具&#xff0c;旨在简化PDF处理和管理的流程。通过这个平台&#xff0c;用户可以通过简单的拖拽界面上的按钮&#xff0c;轻松完成对Cloud API的调用工作流&#xff0c;而无需编写复杂的代码。这使得即使没有编程经验的…

oracle 11g中如何快速设置表分区的自动增加

在很多业务系统中&#xff0c;一些大表一般通过分区表的形式来实现数据的分离管理&#xff0c;进而加快数据查询的速度。分区表运维管理的时候&#xff0c;由于人为操作容易忘记添加分区&#xff0c;导致业务数据写入报错。所以我们一般通过配置脚本或者利用oracle内置功能实现…

Antd X : 迅速搭建 AI 页面的解决方案

前言 随着 AI 热度的水涨船高&#xff0c;越来越多的 AI 应用如井喷式爆发&#xff0c;那么如何迅速搭建一个 AI 应用的美观高质量 Web 前端页面呢&#xff0c; Antd 团队给出了一个解决方案。 X Ant DesIgn XAI 体验新秩序Ant Design 团队匠心呈现 RICH 设计范式&#xff0…

SD Express 卡漏洞导致笔记本电脑和游戏机遭受内存攻击

Positive Technologies 最近发布的一份报告揭示了一个名为 DaMAgeCard 的新漏洞&#xff0c;攻击者可以利用该漏洞利用 SD Express 内存卡直接访问系统内存。 该漏洞利用了 SD Express 中引入的直接内存访问 (DMA) 功能来加速数据传输速度&#xff0c;但也为对支持该标准的设备…

波特图方法

在电路设计中&#xff0c;波特图为最常用的稳定性余量判断方法&#xff0c;波特图的根源是如何来的&#xff0c;却鲜有人知。 本章节串联了奈奎斯特和波特图的渊源&#xff0c;给出了其对应关系和波特图相应的稳定性余量。 理论贯通&#xff0c;不在于精确绘…

React 组件中 State 的定义、使用及正确更新方式

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;React篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来React篇专栏内容React 组件中 State 的定义、使用及正确更新方式 前言 在 React 应用开发中&#xff0c;state …

C++(十二)

前言&#xff1a; 本文将进一步讲解C中&#xff0c;条件判断语句以及它是如何运行的以及内部逻辑。 一&#xff0c;if-else,if-else语句。 在if语句中&#xff0c;只能判断两个条件的变量&#xff0c;若想实现判断两个以上条件的变体&#xff0c;就需要使用if-else,if-else语…

查询产品所涉及的表有(product、product_admin_mapping)

文章目录 1、ProductController2、AdminCommonService3、ProductApiService4、ProductCommonService5、ProductSqlService1. 完整SQL分析可选部分&#xff08;条件筛选&#xff09;&#xff1a; 2. 涉及的表3. 总结4. 功能概述 查询指定管理员下所有产品所涉及的表&#xff1f;…

游戏引擎学习第36天

仓库 :https://gitee.com/mrxiao_com/2d_game 回顾之前的内容 在这个程序中&#xff0c;目标是通过手动编写代码来从头开始制作一个完整的游戏。整个过程不使用任何库或现成的游戏引擎&#xff0c;这样做的目的是为了能够全面了解游戏执行的每一个细节。开发过程中&#xff0…

【SpringMVC】用户登录器项目,加法计算器项目的实现

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 一&#xff1a;用户登录项目实现 1&#xff1a;需求 2&#xff1a;准备工作 &#xff08;1&#xf…