Selector 如何关联 channel,以及需要注意的点
一、创建 selector
Selector selector = Selector.open();
1、一个 selector 可以管理多个 channel 。
二、channel 如何注册到 selector 中 (建立关联关系,使 selector 能够监测到 channel 发生的事件)
// 创建一个 ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
// 此方法默认为 true,设置为 false 后,会使 accept() 不再阻塞
ssc.configureBlocking(false); /*
register() 建立 Selector 和 Channel 之间的联系(也称之为注册)
register() 方法支持三个参数:
参数一:指定channel要注册到那个selector中
参数二:默认传 0,注册时不指定 socket 需要关注的事件。也可以直接绑定注册的 socket 要关注事件。
* 直接绑定关注事件写法示例:ssc.register(selector, SelectionKey.OP_ACCEPT, null)
参数三:指定该channel的附件(attachment),该附件生命周期与channel一致。
*例如将一个ByteBuffer添加为附件,该channel在读取消息是使用这个ByteBuffer,
以免和其他channel混用一个ByteBuffer从而导致读取到的消息混淆在一起。
*获取附件方法:key.attachment();
*关联新的附件:key.attach(Object obj);
SelectionKey 就是将来该 key 对应的 channel 发生事件后可以知道是什么事件,以及是哪个 channel 的事件
四种事件:
1、accept: 客户端发起连接请求时触发
2、connect:客户端建立连接后出发
3、read: 可读事件
3、write: 可写事件
*/
SelectionKey sscKey = ssc.register(selector, 0, null);
/*
指定 SelectionKey 需要关注的事件。
如果要关注多个事件则:sscKey.interestOps(SelectionKey.OP_ACCEPT + SelectionKey.OP_WRITE);
如果以前已经关注过事件,本次关注事件不想替换上次关注事件,则:sscKey.interestOps(sscKey.interestOps() + SelectionKey.OP_ACCEPT);
去除某个关注的事件,例如去除写事件:sscKey.interestOps(sscKey.interestOps() - SelectionKey.OP_WRITE);
*/
sscKey.interestOps(SelectionKey.OP_ACCEPT);
// 绑定地址
ssc.bind(new InetSocketAddress("127.0.0.1", 8080));
三、selector 如何监听 channel 发生的事件
/*
事件监听
没有事件发生,线程阻塞;有事件发生,则恢复运行
如果监听到的事件未处理,则不会阻塞
总结:事件发生后,要么处理,要么取消
*/
selector.select();
四、监听到事件后如何处理,以及需要注意的点
while (true) {
/*
事件监听
没有事件发生,线程阻塞;有事件发生,则恢复运行
如果监听到的事件未处理,则不会阻塞
总结:事件发生后,要么处理,要么取消
*/
selector.select();
/*
处理事件
*/
// selectedKeys() 获取到 selector 中所有监听到事件的 SelectionKey 集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectionKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
// 区分事件类型
if (key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel sc = channel.accept();// 建立连接
// key.cancel(); // 处于某种原因不想处理事件,则取消
sc.configureBlocking(false); // 开启非阻塞模式,避免后续因为未读取到数据而照成线程阻塞
// 将 SocketChannel 也注册到selector 中
SelectionKey scKey = sc.register(selector, 0, null);
// 设置该 channel 需要关注的事件
scKey.interestOps(SelectionKey.OP_READ);
} else if (key.isReadable()) {
/*
如果客户端强制断开,注册在 selector 中的 channel 会不断触发 read 事件,
此时调用 read() 方法去读数据会触发 IOException 异常,
因此需要通过 try catch 捕捉处理,避免服务器因为异常而被强制停止。
需要调用 cancel() 方法进行注销,从 selector 中注销触发异常 SelectionKey 所对应的 channel
*/
try {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buff2 = ByteBuffer.allocate(16);
int len = sc.read(buff2);// 如果客户端正常断开,read() 则会返回 -1,也需要从 selector 中注销对应 channel
if (len == -1) {
key.cancel();
} else {
buff2.flip();
System.out.println(buff2.toString());
}
} catch (IOException e) {
e.printStackTrace();
key.cancel(); // 从 selector 中注销触发异常 SelectionKey 所对应的 channel
}
}
// 一个 key 的事件处理完成后,应当将其从 selectedKeys 集合中移除
iter.remove();
}
}
五、为什么一个 SelectionKey 的事件处理完后需要手动移除
*可以这样理解:
selector 会有两个集合,一个存放了所有注册到该 selector 中的 channel,姑且称之为 channels ;另一个用于存放所有 selector 监听到 channel 的事件,姑且称之为 selectedKeys ,里面包含 0 - n 个 selectionKey 。
每当 selector 监听到 channels 集合中的 channel 有新的事件发生时,会将该发生事件的 selectionKey 放入 selectedKeys 中,而我们对 selectedKeys 集合中发生事件的 key 处理后,该集合并不会主动移除此 selectionKey 。
当下次 selector 通过 select() 方法监听到新的事件,我们通过遍历 selectedKeys 集合去处理此事件时,会将之前已经处理过事件的 selectionKey 遍历出来,代码再次处理此 key 对应 channel 的事件时,往往得不到想要的结果。
例如,通过 selectionKey 得到一个已经处理完事件的 channel,使用该 channel 调用 accept() 函数建立连接时,会返回一个类型为 SocketChannel 的空对象,此时如果使用此对象进行相应操作则会导致空指针。
总结,已经处理完事件的 channel 再次处理对应事件得到的结果:1、accept() 返回空对象;2、read() 方法读取到的数据长度为 0 (待验证)。其余两个事件未测试。
# 附练习代码,用于联想所学,不具备实际功能意义
package com.sourceplan.nettydemo.selector; import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set; /**
* @Author zhou
* @Date 2021/7/19
*/
public class SelectorDemo { public static void main(String[] args) throws IOException {
// 创建 selector,管理多个 channel
Selector selector = Selector.open(); // 创建一个 ByteBuffer
ByteBuffer buff = ByteBuffer.allocate(16);
// 创建一个 ServerSocketChannel
ServerSocketChannel ssc = ServerSocketChannel.open();
// 此方法默认为 true,设置为 false 后,会使 accept() 不再阻塞
ssc.configureBlocking(false); /*
register() 建立 Selector 和 Channel 之间的联系(也称之为注册)
SelectionKey 就是将来该 key 对应的 channel 发生事件后可以知道是什么时间,以及是哪个 channel 的时间
四种事件:
1、accept: 客户端发起连接请求时触发
2、connect:客户端建立连接后出发
3、read: 可读事件
3、write: 可写事件
*/
SelectionKey sscKey = ssc.register(selector, 0, null);
// 指定 SelectionKey 需要关注的事件
sscKey.interestOps(SelectionKey.OP_ACCEPT); // 绑定地址
ssc.bind(new InetSocketAddress("127.0.0.1", 8080));
// 创建一个 List 用于存储与客户端建立的 channel
List<SocketChannel> scList = new ArrayList<>(); while (true) {
/*
事件监听
没有事件发生,线程阻塞;有事件发生,则恢复运行
如果监听到的事件未处理,则不会阻塞
总结:事件发生后,要么处理,要么取消
*/
selector.select();
/*
处理事件
*/
// selectedKeys() 获取到 selector 中所有监听到事件的 SelectionKey 集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iter = selectionKeys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
// 区分事件类型
if (key.isAcceptable()) {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel sc = channel.accept();// 建立连接
// key.cancel(); // 处于某种原因不想处理事件,则取消
sc.configureBlocking(false); // 开启非阻塞模式,避免后续因为未读取到数据而照成线程阻塞
// 将 SocketChannel 也注册到selector 中
SelectionKey scKey = sc.register(selector, 0, null);
// 设置该 channel 需要关注的事件
scKey.interestOps(SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buff2 = ByteBuffer.allocate(16);
sc.read(buff2);
buff2.flip();
System.out.println(buff2.toString());
}
// 一个 key 的事件处理完成后,应当将其从 selectedKeys 集合中移除
iter.remove();
} }
} private static void testOne(ByteBuffer buff, ServerSocketChannel ssc, List<SocketChannel> scList) throws IOException {
/*
accept() 与客户端建立连接,默认阻塞,获取到客户端连接请求后才会往下走
只有 ssc(ServerSocketChannel) 对象调用 configureBlocking(),将模式切换为非阻塞时,accept() 方法才为非阻塞
当 accept() 方法为非阻塞时,如果客户端没有发起连接请求,得到的结果为 null
*/
SocketChannel sc = ssc.accept();
if (sc != null) {
sc.configureBlocking(false); // 切换为非阻塞模式,该 channel 下原本为阻塞的方法将不再阻塞
scList.add(sc);
}
for (SocketChannel channel : scList) {
/*
read() 默认阻塞,只有读取到内容才会继续执行后续代码
当调用该方法的 SocketChannel 对象开启非阻塞模式时,read() 才为非阻塞方法
当 read() 方法为非阻塞时,如果未读取到数据,则返回 0
*/
int read = channel.read(buff);
if (read > 0) { }
}
} }
Selector 如何关联 channel,以及需要注意的点的更多相关文章
- NIO的Buffer&Channel&Selector
java的NIO和AIO Buffer position.limit.capacity 初始化 Buffer 填充 Buffer 提取 Buffer 中的值 mark() & reset() ...
- Java网络编程和NIO详解4:浅析NIO包中的Buffer、Channel 和 Selector
Java网络编程与NIO详解4:浅析NIO包中的Buffer.Channel 和 Selector 转自https://www.javadoop.com/post/nio-and-aio 本系列文章首 ...
- Java NIO:Buffer、Channel 和 Selector
Buffer 一个 Buffer 本质上是内存中的一块,我们可以将数据写入这块内存,之后从这块内存获取数据. java.nio 定义了以下几个 Buffer 的实现,这个图读者应该也在不少地方见过了吧 ...
- Java网络编程与NIO详解4:浅析NIO包中的Buffer、Channel 和 Selector
微信公众号[黄小斜]作者是蚂蚁金服 JAVA 工程师,目前在蚂蚁财富负责后端开发工作,专注于 JAVA 后端技术栈,同时也懂点投资理财,坚持学习和写作,用大厂程序员的视角解读技术与互联网,我的世界里不 ...
- JAVA NIO Selector Channel
These four events are represented by the four SelectionKey constants: SelectionKey.OP_CONNECT Select ...
- NIO组件Selector调用实例
*对于nio的非阻塞I/O操作,使用Selector获取哪些I/O准备就绪,注册的SelectionKey集合记录关联的Channel这些信息.SelectionKey记录Channel对buffer ...
- Java NIO系列教程(六) 多路复用器Selector
多路复用器Selector是Java NIO编程的基础,熟练地掌握Selector对于掌握NIO编程至关重要.多路复用器提供选择已经就绪的任务的能力.简单来讲,Selector会不断地轮询注册在其上的 ...
- Java NIO之Selector(选择器)
历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) Java NIO 之 Channel(通道) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂re ...
- Java NIO系列教程(三) Channel之Socket通道
目录: <Java NIO系列教程(二) Channel> <Java NIO系列教程(三) Channel之Socket通道> 在<Java NIO系列教程(二) Ch ...
- 一个I/O线程可以并发处理N个客户端连接和读写操作 I/O复用模型 基于Buf操作NIO可以读取任意位置的数据 Channel中读取数据到Buffer中或将数据 Buffer 中写入到 Channel 事件驱动消息通知观察者模式
Tomcat那些事儿 https://mp.weixin.qq.com/s?__biz=MzI3MTEwODc5Ng==&mid=2650860016&idx=2&sn=549 ...
随机推荐
- 以EEPROM为例的硬件IIC的使用
目录 参考调试MPU6050与EEPROM的经验,整合了目标内存/寄存器地址是否为16位的情况,合并了单字节与多字节间的操作,添加了返回值与读写超时功能:硬件IIC的7位从机地址查询方式读写参考代码 ...
- java HashMap 原理
jdk1.7 和 1.8 大致相同但还是有区别,主要是数据结构的区别,1.7 为数组+链表:1.8 为数组+链表+红黑树 关键知识点 加载因子:装填因子,目的是何时对 map 进行扩容,默认是 0.7 ...
- 京准GPS北斗卫星时钟同步系统投运国电内蒙古晶阳能源有限公司
京准GPS北斗卫星时钟同步系统投运国电内蒙古晶阳能源有限公司 2020年1月初期,我京准科技生产研发的GPS北斗卫星时钟同步系统投运国电内蒙古晶阳能源有限公司,为该单位的能源管理系统及其他各业务子系 ...
- reduced form(简化式)和structural form(结构式)
在复习软件构造的时候,我发现了这样一道练习题 例题要求我们对照给出的RI和AF画出相应的映射图.在这里产生了一个疑问,什么是reduced form?是分子小于分母的意思吗? 但是根据给出的答案,并不 ...
- leetcode91解码
解码,dp,注意特殊情况 def numDecodings( s: str) -> int: if len(s) < 1: return 1 if s[0] =='0': return 0 ...
- jdbc(工具类和配置文件)
原始的jdbc要操作7步 导入jar包 加载驱动 获取连接 获取执行者对象 编写sql语句 处理结果 释放对象资源 当我们每次都要注册驱动,获取连接的时候,都感觉很烦,这时候怎么才能懒呢? 把driv ...
- AFNI 教程 步骤5:统计和建模
第一部分 时间序列 用AFNI打开fMRI数据, Graph按钮可以打开信号界面,中心的信号是该像素的信号随着时间的变化图,m 可以显示更少的体素,M可以显示更多的体素.V 可以浏览整个图像,+ 可以 ...
- C - Perform the Combo
C - Perform the Combo 思路:当读到这个题的时候,第一反应就是枚举,但是,无线超时,没办法,那就变,利用前缀和,减少时间. 代码: #include<iostream> ...
- java 之 UncaughtExceptionHandler异常处理机制
1.java 1.5版本出现的 UncaughtExceptionHandler 当线程由于未捕获异常突然终止时调用的处理程序的接口. 当一个线程由于未捕获异常即将终止时,Java虚拟机将使用thre ...
- 安卓app的签名打包
今天学习了什么是Android程序的签名打包. Android APP都需要我们用一个证书对应用进行数字签名,不然的话是无法安装到Android手机上的,平时我们调试运行时到手机上时, 是AS会自动用 ...