NIO(三) Selector使用(NIO综合)

Selector(选择器)能够管理一到多个Channel(通道),监听通道是否为事件做好准备。

一,使用Selector的好处


只需少量线程来处理多个通道, 从而管理多个网络连接。

二,Selector示例


服务端
// 创建channel,服务端监听连接使用ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8000));// 与Selector一起使用时,Channel必须处于非阻塞模式下。因为FileChannel不能切换到非阻塞模式,所以不能与Selector一起使用。
ssc.configureBlocking(false);// 注册channel到selector
Selector selector = Selector.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);// 创建buffer
ByteBuffer readBuff = ByteBuffer.allocate(1024);
ByteBuffer writeBUff = ByteBuffer.allocate(2048);while (true) {// 阻塞直到有事件就绪int readyNum = selector.select();if (readyNum == 0) {continue;}Set<SelectionKey> keys = selector.selectedKeys();Iterator it = keys.iterator();while (it.hasNext()) {SelectionKey key = (SelectionKey) it.next();if (key.isAcceptable()) {SocketChannel socketChannel = ssc.accept();socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ);System.out.println("创建连接");} else if (key.isReadable()) {SocketChannel socketChannel = (SocketChannel) key.channel();int readLen = 0;readBuff.clear();StringBuffer sb = new StringBuffer();while ((readLen = socketChannel.read(readBuff)) > 0) {// 注意这里是错误写法!因为最后一次从channel读,buffer里可能有老数据,占不满,参见Buffer原理
//              sb.append(new String(readBuff.array()));
//              readBuff.clear();readBuff.flip();byte[] temp = new byte[readLen];readBuff.get(temp, 0, readLen);sb.append(new String(temp));readBuff.clear();}// 如果客户端关闭就关闭客户端channelif (-1 == readLen) {socketChannel.close();}// 注意这个是覆盖key.interestOps(SelectionKey.OP_WRITE);System.out.println("接受客户端消息:" + sb.toString());} else if (key.isWritable()) {writeBUff.clear();String s = "hello " + new String(readBuff.array()).trim();writeBUff.put(s.getBytes());writeBUff.flip();SocketChannel socketChannel = (SocketChannel) key.channel();// 非阻塞模式write返回了可能还没写完while(writeBUff.hasRemaining()) {socketChannel.write(writeBUff);}key.interestOps(SelectionKey.OP_READ);}// 删除key,注意这一步必须,不然会重复处理it.remove();}
}
  • register()方法的第二个参数表示监听Channel时对什么事件感兴趣,可以监听四种不同类型的事件:Connect、Accept、Read、Write
  • 如果对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如:int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE
  • Selector不会自己从已选择键集中移除SelectionKey,必须在处理完通道时自己remove移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。
客户端
// 客户端使用SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8000));ByteBuffer writeBuff = ByteBuffer.allocate(32);
ByteBuffer readBuff = ByteBuffer.allocate(32);writeBuff.put("alex".getBytes());
writeBuff.flip();while (true) {writeBuff.rewind();socketChannel.write(writeBuff);readBuff.clear();// 这里会阻塞等服务端消息socketChannel.read(readBuff);readBuff.flip();System.out.println("接受服务端消息:" + new String(readBuff.array()));Thread.sleep(1000);
}

三,SelectionKey介绍


当向Selector注册Channel时,register()方法会返回一个SelectionKey对象, 用于 绑定Channel和Selector。
这个对象包含了一些属性:
  • interest集合
  • ready集合
  • Channel
  • Selector
  • 附加的对象(可选)

1,SelectionKey.interestOps():获取Selector对Channel感兴趣的操作集合。

最初该集合是Channel被注册到Selector时传进来的值,该集合不会被Selector改变, 但是可通过interestOps()改变: key.interestOps(SelectionKey. OP_WRITE )。
我们可以通过以下方法来判断Selector是否对Channel的某种事件感兴趣:
int interestSet = selectionKey.interestOps();
boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;

2,SelectionKey.readyOps():获取相关通道已就绪的操作。

它是 感兴趣 集合的子集,表示interests集合中从上次调用select()以后已经就绪的那些操作。
int readSet = selectionKey.readOps();
selectionKey.isAcceptable();//等价于selectionKey.readyOps()&SelectionKey.OP_ACCEPT
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
注意, 通过readyOps()方法返回的就绪状态指示只是一个提示,底层的通道在任何时候都会不断改变,而其他线程也可能在通道上执行操作并影响到它的就绪状态。另外,我们不能直接修改ready集合。

3,SelectionKey.channel() 和 SelectionKey.selector()

从SelectionKey访问Channel和Selector很简单。如下:
Channel channel  = selectionKey.channel();
Selector selector = selectionKey.selector();

4,SelectionKey.cancel():取消特定的注册关系。

该方法调用之后,该SelectionKey对象将会被”拷贝”至已取消键的集合中,该键此时已经失效,但是该注册关系并不会立刻终结。 在下一次select()时,已取消键的集合中的元素会被清除,相应的注册关系也真正终结。

5,SelectionKey.attach():附加对象

可以将一个对象附着到SelectionKey上,这样就能方便的识别某个给定的Channel。例如,可以附加与Channel一起使用的Buffer,或者一个Runnable处理就绪的事件。
// 绑定
selectionKey.attach(theObject);// 获取
Object attachedObj = selectionKey.attachment();// 取消
// 如果附加的对象不再使用,一定要人为清除,因为垃圾回收器不会回收该对象,若不清除的话会成内存泄漏。
selectionKey.attach(null).
还可以在用register()方法向Selector注册Channel的时候附加对象。如:
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);或者:SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); 
// 附加了一个Acceptor对象,这是用来处理连接请求的Runnable
selectionKey.attach(new Acceptor(selector, serverSocketChannel));

四,Selector的一些方法


一旦向Selector注册了一或多个Channel,就可以调用几个重载的select方法。这些方法返回感兴趣的事件(如连接、接受、读写)已经就绪的那些Channel。
使用:selector.select()。
// 阻塞到至少有一个通道在注册的事件上就绪了
int select() // 最长会阻塞timeout毫秒(参数)
int select(long timeout) // 执行非阻塞的选择。如果自从前一次选择后,没有通道变成可选择的,则此方法直接返回零
int selectNow() 
  • select()方法返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。
  • 如果对第一个就绪的channel没有做任何操作(也没把key从 selector .selectedKeys()中删除),现在又有一个channel就绪, 执行 selector .selectedKeys()就会返回2个key,但是 selector .select()只会返回1 ,即上次select()完和这次之间只有一个就绪! 即select()返回的数量是增量的。

1,selector.keys():已注册的SelectionKey集合

返回所有与selector关联的channel所生成的SelectionKey集合。
并不是所有注册过的键都仍然有效,这个集合也可能是空的。
这个集合不能直接修改。

2,selector.selectedKeys():已就绪的SelectionKey集合

每个SelectionKey都关联一个 已经准备好至少一种interest操作的Channel。每个SelectionKey都有一个内嵌的ready集合,指示了所关联的Channel已就绪的事件。
SelectionKey可以直接从这个集合中移除,但不能添加。

3,已删除的SelectionKey集合

这个集合包含了执行过selectionKey.cancel()的key,但它们还没有被注销。这个集合是selector对象的私有成员,因而无法直接访问。

4,wakeUp()

某个线程调用select()方法后阻塞了,只要让 其它线程调用阻塞selector对象的wakeup()方法,阻塞在select()方法上的线程就会立马结束阻塞。
如果有其它线程调用了wakeup()方法,但当前没有线程阻塞在select()方法上,下个调用select()方法的线程会立即“醒来”。

5,close()

用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。

五,Selector.select的过程


1,检查已取消键集合(执行过selectionKey.cancel会添加进来),如果该集合不为空,则清空该集合里的键,同时该集合中每个键也将从已注册键集合和已选择键集合中移除。(一个SelectionKey被取消时,并不会立刻从集合中移除,而是将该键“拷贝”至已取消键集合中,这种就是“延迟取消”策略。)
2,检查已注册键集合(准确说是该集合中每个键的interests集合)。操作系统底层会执行多路复用系统调用(比如select、epoll)去检测就绪的Channel,并关联上SelectionKey。一旦发现某个Channel就绪了,则会首先判断该SelectionKey是否已经存在在已选择键集合当中,如果已经存在,则更新该SelectionKey的ready集合,如果不存在,则首先清空该SelectionKey的ready集合,然后重设ready集合,最后将该键存至已选择集合中。
当更新ready集合时,在上次select()中已经就绪的事件不会被删除,也就是 ready集合中的元素是累积的,比如在第一次的selector对某个Channel的read和write操作感兴趣,在第一次执行select()时,该通道的read操作就绪,此时该通道对应的键中的ready集合存有read元素,在第二次执行select()时,该通道的write操作也就绪了,此时该通道对应的ready集合中将同时有read和write元素。

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

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

相关文章

环境安装-GIT

下载 git官网下载 https://git-scm.com/ 安装 点击下载的安装包&#xff0c;并点击下一步 选择安装路径&#xff0c;照例改选自定义路径 选择默认的即可 选择GIT编辑器&#xff0c;默认选择vim即可 设置初始化新项目(本地仓库)的主分支名&#xff0c;按默认即可&#xff0c;点…

keysight 34901A (安捷伦)多路复用器

34970A 数据采集/开关单元的 Keysight 34901A&#xff08;安捷伦&#xff09;模块是通用扫描中最通用的多路复用器。它将密集的多功能开关与 60 通道/秒的扫描速率相结合&#xff0c;可满足广泛的数据采集应用。两线和四线通道可以混合在同一模块上。两个额外的保险丝输入&…

Hadoop 面试题(八)

1. 在 Hadoop 集群的配置文件中有如下两个配置&#xff0c;请问假如集群中有一个节点宕机&#xff0c;主节点 namenode 需要多长时间才能感知到&#xff08;&#xff09; &#xff1f; dfs.heartbeat.interval 3heartbeat.recheck.interval 2000A&#xff1a;26秒 B&#xff1…

音频傅里叶变换(基于开源kissffs)

主要参考资料&#xff1a; 深入浅出的讲解傅里叶变换&#xff08;真正的通俗易懂&#xff09;: https://zhuanlan.zhihu.com/p/19763358 推荐开源项目&#xff1a;KISS FFT&#xff1a; https://blog.csdn.net/gitblog_00031/article/details/138840117 数字硅麦数据的处理&…

基于Java蛋糕甜品商城系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f;感兴趣的可以先收藏起来&#xff0c;还…

LLama 3的各种微调:拿我司七月的paper-review数据集微调LLama 3

前言 llama 3出来后&#xff0c;为了通过paper-review的数据集微调3&#xff0c;有以下各种方式 不用任何框架 工具 技术&#xff0c;直接微调原生的llama 3&#xff0c;毕竟也有8k长度了 效果不期望有多高&#xff0c;纯作为baseline通过PI&#xff0c;把llama 3的8K长度扩展…

EDU学校漏洞sql注入挖掘记录

某搜索框 biaoti参数单引号报错 双引号正常 经过我的不断测试&#xff0c;’||exp(710)||’报错&#xff0c;exp函数就是执行e的多少次方&#xff0c;709不会报错&#xff0c;710会导致这个数太大报错 709正常,这里说明一下&#xff0c;因为这个数是小数所以返回200&#xff0c…

awk脚本监控

awk脚本监控 使用脚本监控内存&#xff0c;cpu和硬盘的根目录&#xff0c;超过80%提示用户&#xff0c;写成函数库的行&#xff0c;每天早上 的8.50分&#xff0c;执行一次脚本 现在脚本中写需要的内容 cpuu () {aa$(top -b -n 1 |awk NR3 {printf "%.F",$2$4})if …

Spring容器启动流程——refresh()单个方法分析

文章目录 Spring启动过程this()方法refresh()prepareRefresh()obtainFreshBeanFactory()prepareBeanFactory()postProcessBeanFactory()invokeBeanFactoryPostProcessorsregisterBeanPostProcessorsinitMessageSource()initApplicationEventMulticaster()onRefresh()registerLi…

Pycharm利用Anaconda环境

创建环境 conda create --name d2l python3.11 -y 激活环境 conda activate d2l 配置环境 以torch环境为例 我们可以按如下⽅式安装PyTorch的CPU或GPU版本&#xff1a; pip install torch pip install torchvision 我们的下⼀步是安装d2l包&#xff0c;以⽅便调取本书中经…

WPF 数据分组显示

WPF 数据分组显示 效果展示&#xff1a; Student类&#xff1a; public class Student {public string Name { get; set; }public string Class { get; set; }public int Age { get; set; } }MainWindow.xaml.cs public partial class MainWindow : Window {private Observ…

【调试笔记-20240620-Windows- Tauri + Vue 中实现部分区域滚动】

调试笔记-系列文章目录 调试笔记-20240620-Windows- Tauri Vue 中实现部分区域滚动 文章目录 调试笔记-系列文章目录调试笔记-20240620-Windows- Tauri Vue 中实现部分区域滚动 前言一、调试环境操作系统&#xff1a;Windows 10 专业版调试环境调试目标 二、调试步骤搜索相似…

如何在Java中处理InterruptedException异常?

如何在Java中处理InterruptedException异常&#xff1f; 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 在Java编程中&#xff0c;多线程是一个常见的应用场景…

专业140+总分400+武汉理工大学855信号与系统考研经验电子信息与通信工程,真题,大纲,参考书

专业855信号与系统140&#xff0c;总分400&#xff0c;今年顺利上岸武汉理工大学&#xff0c;总结一下自己的复习经历&#xff0c;希望对报考武理工的同学有所帮助。专业课&#xff1a;855信号与系统 首先教材&#xff1a; 《信号与系统》高等教育出版社 作者&#xff1a;刘泉…

第一百二十三节 Java面向对象的设计 - Java接口继承

Java面向对象的设计 - Java接口继承 接口可以从另一个接口继承。与类不同&#xff0c;接口可以从多个接口继承。 interface Singer {void sing();void setRate(double rate);double getRate(); } interface Writer {void write();void setRate(double rate);double getRate();…

人间烟火气视频素材去哪里找?人间生活气息视频素材网站分享

在数字化时代迅猛发展的今天&#xff0c;短视频已经成为人们表达情感、记录生活的流行方式。无论是在抖音、快手还是B站&#xff0c;一种特别的元素——人间烟火气&#xff0c;为短视频增添了无尽魅力。许多创作者常常困惑&#xff0c;这种生活气息浓厚的视频素材应当如何寻找&…

使用 Swift 6 语言模式构建 Swift 包

文章目录 前言下载 Swift 6 工具链Swiftenv - macOSSwiftly - Linux在 SPM 中启用语言模式命令行包清单文件输出结论前言 我最近了解到,Swift 6 的一些重大变更(如完整的数据隔离和数据竞争安全检查)将成为 Swift 6 语言模式的一部分,该模式将在 Swift 6 编译器中作为可选…

Python 类对象

Python 类对象 经典迭代器 可迭代对象的定义&#xff1a; 使用内置的iter可以获取迭代器的对象。如果对象实现了能返回迭代器的__iter__方法&#xff0c;那么对象就是可迭代的。序列都可以迭代。实现了__getitem__方法&#xff0c;而且接受从0开始的索引&#xff0c;这种对象也…

EfficientNet-V1论文阅读笔记

目录 EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks摘要Introduction—简介Compound Model Scaling—混合模型缩放Problem Formulation—范式化问题&#xff08;理论基础&#xff09;Scaling Dimensions—维度缩放Compound Scaling—混合缩放 Eff…

【网络协议】精讲ARP协议工作原理!图解超赞超详细!!!

亲爱的用户&#xff0c;打开微信&#xff0c;搜索公众号&#xff1a;“风云说通信”&#xff0c;即可免费阅读该文章~~ 目录 前言 1. ARP协议介绍 1.1 ARP协议功能 1.2 ARP请求报文 1.3 ARP工作原理 2. ARP 缓存超时 2.1 RARP 3. ARP 攻击 3.1 ARP 攻击分类 前言 首先…