李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
13.Selector处理accept和read事件
Leefs
2022-06-02 PM
747℃
0条
[TOC] ### 前言 + **多路复用** 单线程可以配合 Selector 完成对多个 Channel 可读写事件的监控,这称之为多路复用 * 多路复用仅针对网络 IO、普通文件 IO 没法利用多路复用 * 如果不用 Selector 的非阻塞模式,线程大部分时间都在做无用功,而 Selector 能够保证 * 有可连接事件时才去连接 * 有可读事件才去读取 * 有可写事件才去写入 * 限于网络传输能力,Channel 未必时时可写,一旦 Channel 可写,会触发 Selector 的可写事件 ### 一、处理accept事件 + **服务器端代码** ```java import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; /** * @author lilinchao * @date 2022/6/2 * @description Accept事件 **/ @Slf4j public class AcceptServer { public static void main(String[] args) throws IOException { //1.创建Selector,可以管理多个channel Selector selector = Selector.open(); ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); //2.建立channel和selector之间的联系(注册) //SelectionKey:事件发生后通过这个可以获取到相应事件,以及对应事件发生的channel SelectionKey sscKey = ssc.register(selector, 0, null); //表名这个key只关注accept事件 sscKey.interestOps(SelectionKey.OP_ACCEPT); log.debug("register key:{}",sscKey); ssc.bind(new InetSocketAddress(8080)); while (true){ //3. selector.select()方法,没有事件就阻塞,有事件发送就恢复运行继续向下处理 selector.select(); //4.处理事件,selectionKeys内部包含了所有发生的事件 Iterator
iterator = selector.selectedKeys().iterator(); while (iterator.hasNext()){ //注意,如果事件不调用accept进行处理,那么不会阻塞,因为事件没被处理,就不能阻塞 //也就是说事件要么处理要么取消,不能不管 SelectionKey key = iterator.next(); log.debug("key:{}",key); //拿到触发事件的channel ServerSocketChannel channel = (ServerSocketChannel)key.channel(); SocketChannel sc = channel.accept(); log.debug("{}",sc); } } } } ``` + **客户端代码** ```java import java.io.IOException; import java.net.Socket; /** * @author lilinchao * @date 2022/6/2 * @description 1.0 **/ public class AcceptClient { public static void main(String[] args) { try (Socket socket = new Socket("localhost", 8080)) { System.out.println(socket); socket.getOutputStream().write("world".getBytes()); System.in.read(); } catch (IOException e) { e.printStackTrace(); } } } ``` + **运行** (1)启动服务端程序 (2)通过Debug模式启动客户端程序 (3)通过Debug模式再启动一个客户端程序 + 客户端启动项,选择【Edit Configurations】 ![13.Selector处理accept和read事件01.jpg](https://lilinchao.com/usr/uploads/2022/06/3147726147.jpg) + 选择【Allow parallel run】,再点击【OK】 ![13.Selector处理accept和read事件02.jpg](https://lilinchao.com/usr/uploads/2022/06/89113966.jpg) + 再通过Debug模式启动一个客户端 + **服务端程序输出结果** ``` 17:05:25.304 [main] DEBUG com.lilinchao.nio.accept.AcceptServer - register key:sun.nio.ch.SelectionKeyImpl@3b764bce 17:05:38.141 [main] DEBUG com.lilinchao.nio.accept.AcceptServer - key:sun.nio.ch.SelectionKeyImpl@3b764bce 17:05:38.142 [main] DEBUG com.lilinchao.nio.accept.AcceptServer - java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:51904] 17:05:55.911 [main] DEBUG com.lilinchao.nio.accept.AcceptServer - key:sun.nio.ch.SelectionKeyImpl@3b764bce 17:05:55.912 [main] DEBUG com.lilinchao.nio.accept.AcceptServer - java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:51919] ``` 从打印结果可以看出有两个客户端向服务端发送了连接请求。 #### 问题:事件发生后能否不处理? > 事件发生后,要么处理,要么取消(cancel),不能什么都不做,否则下次该事件仍会触发,这是因为 nio 底层使用的是水平触发 ### 二、处理read事件 + **服务端代码** ```java import lombok.extern.slf4j.Slf4j; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import static com.lilinchao.nio.util.ByteBufferUtil.debugAll; /** * @author lilinchao * @date 2022/6/2 * @description Read事件 服务端 **/ @Slf4j public class ReadServer { public static void main(String[] args) throws IOException { //1.创建selector,管理多个channel Selector selector = Selector.open(); ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.configureBlocking(false); //2. 建立channel和selector之间的联系(注册) SelectionKey sscKey = ssc.register(selector, 0, null); sscKey.interestOps(SelectionKey.OP_ACCEPT); log.debug("register key:{}",sscKey); ssc.bind(new InetSocketAddress(8080)); while (true){ //3. selector.select()方法,没有事件就阻塞,有了事件发送了就恢复运行继续向下处理 selector.select(); //4. 处理事件,selectionKeys拿到所有发生的可读可写的事件 Iterator
iterator = selector.selectedKeys().iterator(); //多个key的时候,accept和read方法都会触发事件,所以要区分事件类型 while (iterator.hasNext()){ SelectionKey key = iterator.next(); //处理key的时候要从selectKeys中删除,否则会报错 iterator.remove(); log.debug("key:{}",key); //5.区分事件类型 if(key.isAcceptable()){ //拿到触发事件的channel ServerSocketChannel channel = (ServerSocketChannel)key.channel(); SocketChannel sc = channel.accept(); //设置为非阻塞 sc.configureBlocking(false); //scKey管sc的channel SelectionKey scKey = sc.register(selector, 0, null); //scKey关注读事件,也就是说客户端的通道关注可读事件 scKey.interestOps(SelectionKey.OP_READ); log.debug("{}",sc); }else if(key.isReadable()){ //客户端关闭之后也会引发read事件,这时需要从key中remove掉,否则拿不到channel,报错 try { SocketChannel channel = (SocketChannel)key.channel(); ByteBuffer buffer1 = ByteBuffer.allocate(16); //客户端正常断开,read返回值是-1 int read = channel.read(buffer1); if(read == -1){ //正常断开 key.channel(); } buffer1.flip(); debugAll(buffer1); } catch (IOException e) { e.printStackTrace(); key.cancel();//客户端断开,需要将key取消(从selector的key集合中真正删除) } } } } } } ``` + **客户端代码** ```java import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.channels.SocketChannel; /** * @author lilinchao * @date 2022/6/2 * @description Read事件 客户端 **/ public class ReadClient { public static void main(String[] args) throws IOException { SocketChannel sc = SocketChannel.open(); sc.connect(new InetSocketAddress("localhost", 8080)); SocketAddress localAddress = sc.getLocalAddress(); System.out.println("waiting..."); } } ``` + **运行** (1)启动服务端程序 (2)Debug启动客户端程序 + 选择【Evalute Expression】 ![13.Selector处理accept和read事件03.jpg](https://lilinchao.com/usr/uploads/2022/06/3551388388.jpg) + 输入如下内容 ```java sc.write(Charset.defaultCharset().encode("hello!")); ``` ![13.Selector处理accept和read事件04.jpg](https://lilinchao.com/usr/uploads/2022/06/4223533605.jpg) + 点击【Evaluate】提交 + **服务端输出结果** ```java 17:20:50.713 [main] DEBUG com.lilinchao.nio.read.ReadServer - register key:sun.nio.ch.SelectionKeyImpl@3b764bce 17:21:11.373 [main] DEBUG com.lilinchao.nio.read.ReadServer - key:sun.nio.ch.SelectionKeyImpl@3b764bce 17:21:11.374 [main] DEBUG com.lilinchao.nio.read.ReadServer - java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:52466] 17:23:48.603 [main] DEBUG com.lilinchao.nio.read.ReadServer - key:sun.nio.ch.SelectionKeyImpl@368102c8 17:23:48.653 [main] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework +--------+-------------------- all ------------------------+----------------+ position: [0], limit: [6] +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 68 65 6c 6c 6f 21 00 00 00 00 00 00 00 00 00 00 |hello!..........| +--------+-------------------------------------------------+----------------+ ``` 服务端监听到客户端的连接,并读取打印客户端发送过来的数据。 #### 问题:为何要 iter.remove() > 因为 select 在事件发生后,就会将相关的 key 放入 selectedKeys 集合,但不会在处理完后从 selectedKeys 集合中移除,需要我们自己编码删除。例如 > > * 第一次触发了 ssckey 上的 accept 事件,没有移除 ssckey > * 第二次触发了 sckey 上的 read 事件,但这时 selectedKeys 中还有上次的 ssckey ,在处理时因为没有真正的 serverSocket 连上了,就会导致空指针异常 #### 问题:cancel 的作用 > cancel 会取消注册在 selector 上的 channel,并从 keys 集合中删除 key 后续不会再监听事件
标签:
Netty
,
NIO
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2107.html
上一篇
12.NIO之选择器(Selector)
下一篇
14.NIO消息边界问题处理
取消回复
评论啦~
提交评论
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
标签云
稀疏数组
MyBatisX
随笔
前端
正则表达式
Flink
VUE
字符串
工具
Tomcat
Flume
JVM
Filter
Kafka
HDFS
Spark
Http
Scala
Git
微服务
MyBatis-Plus
Hive
ajax
哈希表
算法
散列
Map
Sentinel
SpringBoot
Golang
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞