nio 02 多路复用

  1. NIO 多路复用器
  2. selector 核心组件
    1. SelectableChannel
    2. Selector
    3. SelectorKey
    4. demo 演示通过选择器实现UDP服务器
    5. Selector 操作状态说明

前面我们模拟了一个阻塞模型,我们再回顾下:

在客户端建立连接开始,服务器就分配了线程,此时由于数据传输需要耗费时间,因此,线程在被新建后有一段时间处于等待的状态。能否等消息到达之后在分配线程进行处理?

这就需要Selector出场了。只要将管道设置为非阻塞模式,然后注册至 Selector,当数据准备完毕后就会得到通知,此时才会创建线程。

NIO 多路复用器

selector 是 nio 的核心组件,用于检查一个或多个 channel 的状态是否处于可读、可写

如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。

Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器

那么,selector 是如何工作的?

selector 核心组件

SelectableChannel

核心功能有两个:

  • configureBlocking 设置阻塞模式,默认为true,向选择器注册前必须置为false

  • 调用register方法注册到指定管道,并且指定要监听的事件

关于监听事件

可选的事件有:CONNECT(建立连接)、ACCEPT(接受连接)、READ(可读)、WRITE(可写) 。

但并非所有管道都支持这四事件,可通过validOps()来查看当前管道支持哪些事件。

一个管道可注册监听多个事件并且在注册后可改变监听的事件

Selector

以键集的形式维护已注册的管道,通过select()方法更新键的状态

下面是几个重载的 select 方法

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

SelectorKey

获取关联的管道,变更管道的注册事件,取消注册等功能

// 监听读取事件
key=socketChannel.register(selector, SelectionKey.OP_READ);
// 同时监听读写事件
key.interestOps(SelectionKey.OP_READ|OP_WRITE);

但并非所有管道都支持这四个事件如:

  • ServerSocketChannel 仅支持OP_ACCEPT事件,
  • SocketChannel 支持 OP_CONNECT、OP_READ、OP_WRITE 三个事件

查看管道支持哪些事件可通过 validOps() 反回的值 然后进行‘&’运算 判断如

//表示该管道支持 OP_CONNECT 事件监听
socketChannel.validOps()&SelectionKey.OP_CONNECT != 0

此外Key还有如下主要功能:

  • channel() 获取管道
  • 判断状态
    isAcceptable() 管道是否处于Accept状态
    isConnectable 管道是否处于连接就绪状态
    isReadable 管道是否处于读取就绪状态
    isWritable 管道是否处于写就续状态
  • isValid() 判断该键是否有效,管道关闭、选择器关闭、键调用 cancel() 方法都会导致该键无效。
  • cancel() 取消管道注册(不会直接关闭管道)

demo 演示通过选择器实现UDP服务器

package com.shar.netty.selector;

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;

public class SelectorTest {

    @Test
    public void test3() throws IOException {
        Selector selector = Selector.open();
        DatagramChannel channel = DatagramChannel.open();
        channel.bind(new InetSocketAddress(8080));
        // 设置非阻塞模式
        channel.configureBlocking(false);
        // 注册读取就续事件
        channel.register(selector, SelectionKey.OP_READ);
        while (true) {
            // 刷新键集
            int count = selector.select();
            if (count > 0) {
                // 获取就续集并遍历
                Iterator<SelectionKey> iterator =   selector.selectedKeys().iterator();
                // 遍历就续集
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    // 处理该就续键
                    handle(key);
                    // 从就续集中移除
                    iterator.remove();
                }
            }
        }
    }

    public void handle(SelectionKey key) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(8192);
        DatagramChannel channel = (DatagramChannel) key.channel();
        // 读取消息并写入缓冲区
        channel.receive(buffer);
    }
}

Selector 操作状态说明

Selector 内部维护三种键集,底层都是Set实现所以不会重复

  • keys 增加键集合,在调用registor()时触发维护
  • selectedKeys 更新键集合,在调用select()方法时,添加准备就绪的键
  • canceledKeys 取消键集合,,在调用remove()时触发维护

转载请注明来源。 欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。 可以在下面评论区评论,也可以邮件至 sharlot2050@foxmail.com。

文章标题:nio 02 多路复用

字数:994

本文作者:夏来风

发布时间:2020-07-12, 10:00:42

原始链接:http://www.demo1024.com/blog/nio-selector/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。