Java NIO 中的 Selector 详解(下)

简介: Java NIO 中的 Selector 详解

4、选择键(SelectionKey)


(1)Channel 注册之后,并且一旦通道处于某种就绪状态,就可以被选择器查询到。这个工作使用选择器 Selector 的 select() 方法完成。select 方法的作用,对感兴趣的通道操作,进行就绪状态的查询。


(2)Selector 可以不断的查询 Channel 中发生的操作的就绪状态。并且选择甘心去的操作就绪状态。一旦通道有操作的就绪状态达成,并且是 Selecor 感兴趣的操作,就会被 Selector 选中,放入选择键集合中。


(3)一个选择键,首先包含了注册在 Selector 的通道操作的类型,比方说: SelectionKey.OP_READ . 也包含了特定的通道与特定的选择器之间的注册关系。

开发应用程序是,选择键是编程的关键,NIO 编程,就是更具对应的选择键,进行不同的业务逻辑处理。


(4)选择键的概念,和事件的概念比较相似。一个选择键类似监听器模式里面的一个事件。由于 Selector 不是事件触发的模式,而是主动去查询的模式,所以不叫事件 Event, 而是叫 SelectionKey 选择键。


Selector 的使用方法


1、Selector 的创建


通过 Selector.open() 方法创建一个 Selector 对象。如下;


// 获取 Selector 选择器
Selector selector = Selector.open();


2、注册 Channel 到 Selector


要实现 Selector 管理 Channel , 需要将 channel 注册到相应的 Selector 上


// 1. 获取 Selector 选择器
Selector selector = Selector.open();
// 2. 获取通道
ServerSocketChannel socketChannel = ServerSocketChannel.open();
// 3. 设置为非阻塞
socketChannel.configureBlocking(false);
// 4. 绑定连接
socketChannel.bind(new InetSocketAddress(9999));
// 5. 将通道注册到选择器
socketChannel.register(selector, SelectionKey.OP_ACCEPT);


上面通过调用通道的 register() 方法会将它注册到一个选择器上。


需要注意的是:


(1)与 Selector 一起使用, channel 必须处于非阻塞模式下,否则将抛出异常 IllegalBlockingModeException 。 这意味着,FileChannel 不能与 Selector 一起使用,因为 FileChannel 不能切换到非阻塞模式,而套接字相关的所有通道都可以。


(2)一个通道,并没有一定要持有所有的四种操作。比如服务器通道 ServerSocketChannel 支持 Accept 接收操作,而 SocketChannel 客户端通道则不支持。可以通过通道上的 vildOps() 方法,来获取特定通道下所支持的操作集合。


3、轮训查询就绪操作


(1) 通过 Selector 的 select() 方法, 可以查询出已经就绪的通道操作,有些就绪的状态集合,包含在一个元素是 Selectionkey 对象的 Set 集合中


(2) 下面是 Selector 几个重载的查询 select() 方法:


  • select() 阻塞到至少有一个通道在你注册的事件上就绪。
  • select(long timeout) 和 select() 一样,但最长阻塞事件为 timeout 毫秒。
  • selectNow() 非阻塞,只要有通道就立即返回。


select() 方法返回的 int 之,表示有多少通道已经就绪,准确的说目前一次 select

方法来到这一次 select 方法之间的时间段上,有多少个通道编程了就绪状态。


例如:首次调用 select() 方法,如果有一个通道编程了就绪状态,返回了 1 , 若子啊次调用 select() 方法,如果另外一个通道就绪了,它会再次返回 1。 如果第一个就绪的 chnanel 么有做任何操作,现在就有两个就绪通道,但是每次 select() 方法调用之间,只有一个通道就绪了。


一旦调用 select() 方法,并且返回值部位 0 时,在 Selector 中有一个 seletedKeys() 方法,用来范围已选择键集合,迭代集合的每个以元素,根据就绪操作的类型,完成对应的操作


// 查询已经就绪的通道操作
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
    SelectionKey key = iterator.next();
    // 判断 key 就绪状态操作
    if (key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        // a connection was established with a remote server.
    } else if (key.isReadable()) {
        // a channel is ready for reading
    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
}
iterator.remove();


4、停止选择的方法


选择器执行选择的过程汇总,系统底层会依次询问每个通道是否已经就绪,这个过程可能会造成调用线程进入阻塞状态,那么我们有一下三种方式可以唤醒在 select()方法中阻塞的线程。


wakeup() 方法:通过调用 Selector 对象的 wakeup() 方法让处于阻塞状态的 select() 方法立刻返回


该方法使得选择器上的第一个哈没有返回的选择操作立即返回。如果当前没有进行中的选择操作,那么下一次会对 select() 方法的一次调用立即返回。


close() 方法: 通过 close() 方法关闭 selector


该方法使得任何一个在选择操作中阻塞的线程都被唤醒(类似 wakeup()) , 同时使的注册到该 Selector 的所有 Channel 被注销,所有的键都被取消,但是 Channel 本身不会关闭。


示例代码


1、服务端代码


@Test
public void server() throws IOException {
    //1. 获取服务端通道
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    //2. 切换非阻塞模式
    serverSocketChannel.configureBlocking(false);
    //3. 创建 buffer
    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
    ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
    writeBuffer.put("收到了。。。。".getBytes(StandardCharsets.UTF_8));
    //4. 绑定端口号
    serverSocketChannel.bind(new InetSocketAddress(20000));
    //5. 获取 selector 选择器
    Selector selector = Selector.open();
    //6. 通道注册到选择器,进行监听
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    //7. 选择器进行轮训,进行后续操作
    while (selector.select() > 0) {
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
        // 循环
        while (selectionKeyIterator.hasNext()) {
            // 获取就绪状态
            SelectionKey k = selectionKeyIterator.next();
            // 操作判断
            if (k.isAcceptable()) {
                // 获取连接
                SocketChannel accept = serverSocketChannel.accept();
                // 切换非阻塞模式
                accept.configureBlocking(false);
                // 注册
                accept.register(selector, SelectionKey.OP_READ);
            } else if (k.isReadable()) {
                SocketChannel socketChannel = (SocketChannel) k.channel();
                readBuffer.clear();
                socketChannel.read(readBuffer);
                readBuffer.flip();
                System.out.println("received:" + new String(readBuffer.array(), StandardCharsets.UTF_8));
                k.interestOps(SelectionKey.OP_WRITE);
            } else if (k.isWritable()) {
                writeBuffer.rewind();
                SocketChannel socketChannel = (SocketChannel) k.channel();
                socketChannel.write(writeBuffer);
                k.interestOps(SelectionKey.OP_READ);
            }
        }
    }
}


2、客户端代码


@Test
public void client() throws IOException {
    //1. 获取通道,绑定主机和端口号
    SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(20000));
    //2. 切换到非阻塞模式
    socketChannel.configureBlocking(false);
    //3. 创建 buffer
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    //4. 写入 buffer 数据
    buffer.put(new Date().toString().getBytes(StandardCharsets.UTF_8));
    //5. 模式切换
    buffer.flip();
    //6. 写入通道
    socketChannel.write(buffer);
    //7. 关闭
    buffer.clear();
    socketChannel.close();
}


3、NIO 编程步骤总结


1、创建一个 ServerSocketChannel 通道


2、设置为非阻塞模式


3、创建一个 Selector 选择器


4、Channel 注册到选择器中,监听连接事件


5、调用 Selector 中的 select 方法(循环调用),监听通道是否是就绪状态


6、调用 SelectKeys() 方法就能获取 就绪 channel 集合


7、遍历就绪的 channel 集合,判断就绪事件类型,实现具体的业务操作。


8、根据业务流程,判断是否需要再次注册事件监听事件,重复执行。


相关文章
|
2月前
|
存储 Java 数据处理
|
2月前
|
Java API
java中IO与NIO有什么不同
java中IO与NIO有什么不同
|
18天前
|
缓存 Java API
Java NIO和IO之间的区别
NIO(New IO),这个库是在JDK1.4中才引入的。NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另一套就是网络编程NIO。
16 1
|
23天前
|
监控 Java 开发者
深入理解 Java 网络编程和 NIO
【4月更文挑战第19天】Java网络编程基于Socket,但NIO(非阻塞I/O)提升了效率和性能。NIO特点是非阻塞模式、选择器机制和缓冲区,适合高并发场景。使用NIO涉及通道、选择器和事件处理,优点是高并发、资源利用率和可扩展性,但复杂度、错误处理和性能调优是挑战。开发者应根据需求选择是否使用NIO,并深入理解其原理。
|
25天前
|
存储 监控 Java
浅谈Java NIO
浅谈Java NIO
7 0
|
26天前
|
消息中间件 存储 Java
【Java NIO】那NIO为什么速度快?
是这样的,在NIO零拷贝出现之前,一个I/O操作会将同一份数据进行多次拷贝。可以看下图,一次I/O操作对数据进行了四次复制,同时来伴随两次内核态和用户态的上下文切换,众所周知上下文切换是很耗费性能的操作。
29 1
【Java NIO】那NIO为什么速度快?
|
28天前
|
存储 监控 Java
Java输入输出:什么是NIO(New I/O)?
Java NIO是一种高效I/O库,特征包括非阻塞性操作、通道(如文件、网络连接)、缓冲区和选择器。选择器监控通道状态变化,通知应用程序数据可读写,避免轮询,提升性能。示例代码展示了一个使用NIO的服务器,监听连接、读取数据并处理客户端通信。
14 1
|
3月前
|
移动开发 编解码 网络协议
用Java的BIO和NIO、Netty来实现HTTP服务器(三) 用Netty实现
用Java的BIO和NIO、Netty来实现HTTP服务器(三) 用Netty实现
|
3月前
|
网络协议 Java Linux
用Java来实现BIO和NIO模型的HTTP服务器(二) NIO的实现
用Java来实现BIO和NIO模型的HTTP服务器(二) NIO的实现
|
3月前
|
编解码 网络协议 Java
用Java的BIO和NIO、Netty实现HTTP服务器(一) BIO与绪论
用Java的BIO和NIO、Netty实现HTTP服务器(一) BIO与绪论