李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
13.Selector处理accept和read事件
Leefs
2022-06-02 PM
1161℃
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
NLP
4
标签云
CentOS
Kafka
递归
gorm
Java阻塞队列
并发线程
Nacos
MyBatis
线程池
Spark SQL
Netty
二叉树
Kibana
栈
人工智能
ajax
GET和POST
Yarn
SQL练习题
RSA加解密
Tomcat
JavaSE
持有对象
JavaScript
Sentinel
设计模式
数据结构
SpringCloud
Spark Core
Hadoop
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭