Java NIO 中 Selector 解析

目录
  • 一、Selector 简介
    • 1、Selector 和 Channel 关系
    • 2、可选择通道(SelectableChannel)
    • 3、Channel 注册到 Selector
    • 4、选择键(SelectionKey)
  • 二、Selector 的使用方法
    • 1、Selector 的创建
    • 2、注册 Channel 到 Selector
    • 3、轮训查询就绪操作
    • 4、停止选择的方法
  • 三、示例代码
    • 1、服务端代码
    • 2、客户端代码
    • 3、NIO 编程步骤总结

一、Selector 简介

1、Selector 和 Channel 关系

Selector 一般称为选择器,可以翻译为 多路复用。它是 Java NIO 核心组件中的一个,用于检查一个或者多个 NIO Channel (通道) 的状态是否处于可读、可写。如此可以实现单线程管理多个 Channels , 也就是可以管理多个网络链接。

使用 Selector 的好处在于:使用更少的线程就可以来处理通道了,相比使用多个线程,避免了线程上下文切换带来的开销。

2、可选择通道(SelectableChannel)

(1)不是所有的 Channel 都是可以被 Selector 复用的。比方说, FileChannel 就不能被选择器复用。判断一个 Channel 能被 Selector 复用,有一个前提:判断他是否继承了一个抽象类 SelectableChannel。如果继承了 SelectableChannel , 则可以被复用,否则不能。

(2)SelectableChannel 提供了实现通道选择性所需要的公共方法。它是所有支持就绪检查通道类的父类,所有 socket 通道,都继承 SelectableChannel 类都是可选择的,包括从管道(Pipe) 对象的中获取得到的通道。而 FileChannel 类,没有继承 SelectableChannel , 因此是不是可选通道。

(3)一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。通道和选择器之间的关系,使用注册的方式完成。SelectableChannel 可以被注册到 Selector 对象上,在注册时候,需要指定通道的那些操作,是 Selector 感兴趣的。

3、Channel 注册到 Selector

(1)使用Channel.register(Selector sel, int pos) 方法,将一个通道注册到一个选择器时。第一个参数,指定通道要注册的选择器。第二个参数指定选择器需要查询的通道操作。

(2)可供选择器查询的通道操作,从类型类分,包括一下四种:

  • 可读:SelectionKey.OP_READ
  • 可写:SelectionKey.OP_WRITE
  • 连接:SelectionKey.OP_CONNECT
  • 接收:SelectionKey.OP_ACCEPT

如果 Selector 对通道的多操作类型感兴趣,可以用“位或”操作符来实现:

比如int key = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

(3)选择器查询的不是通道的操作,而是通道的某个操作的一种就绪状态。什么操作的就绪状态?一旦通道具备完成某个操作的条件,表示该通道的某个操作已经就绪,就可以被 Selector 查询到,程序可以对通道进行对应的操作。比方说,某个 SocketChannel 通道可以连接到一个服务器,则处于“连接就绪”状态(OP_CONNECT)。 再比方说,一个ServerSocketChannel 服务器通道准备好接收新进入的连接,则处于“接收就绪”(OP_ACCEPT)状态。还比方说,一个数据可读的通道,可以说是“读就绪”(OP_READ)。一个等待写数据的通道可以说是“写就绪”(OP_WRITE)。

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() 方法:

  1. select() 阻塞到至少有一个通道在你注册的事件上就绪。
  2. select(long timeout) select() 一样,但最长阻塞事件为 timeout 毫秒。
  3. selectNow() 非阻塞,只要有通道就立即返回。
  4. 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、根据业务流程,判断是否需要再次注册事件监听事件,重复执行。

到此这篇关于Java NIO 中 Selector 解析的文章就介绍到这了,更多相关Java NIO 中的Selector内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入了解java NIO之Selector(选择器)

    这一节我们将探索选择器(selectors).选择器提供选择执行已经就绪的任务的能力,这使得多元 I/O 成为可能.就像在第一章中描述的那样,就绪选择和多元执行使得单线程能够有效率地同时管理多个 I/O 通道(channels).C/C++代码的工具箱中,许多年前就已经有 select()和 poll()这两个POSIX(可移植性操作系统接口)系统调用可供使用了.许过操作系统也提供相似的功能,但对Java 程序员来说,就绪选择功能直到 JDK 1.4 才成为可行的方案. 下面我们来使用选择器:

  • Java NIO Selector用法详解【含多人聊天室实例】

    本文实例讲述了Java NIO Selector用法.分享给大家供大家参考,具体如下: 一.Java NIO 的核心组件 Java NIO的核心组件包括:Channel(通道),Buffer(缓冲区),Selector(选择器),其中Channel和Buffer比较好理解 简单来说 NIO是面向通道和缓冲区的,意思就是:数据总是从通道中读到buffer缓冲区内,或者从buffer写入到通道中. 关于Channel 和 Buffer的详细讲解请看:Java NIO 教程 二.Java NIO Se

  • Java NIO 中 Selector 解析

    目录 一.Selector 简介 1.Selector 和 Channel 关系 2.可选择通道(SelectableChannel) 3.Channel 注册到 Selector 4.选择键(SelectionKey) 二.Selector 的使用方法 1.Selector 的创建 2.注册 Channel 到 Selector 3.轮训查询就绪操作 4.停止选择的方法 三.示例代码 1.服务端代码 2.客户端代码 3.NIO 编程步骤总结 一.Selector 简介 1.Selector 和

  • Java NIO 中Buffer 缓冲区解析

    目录 一.Buffer 简介 二.Buffer 的基本方法 1.使用 Buffer 读写数据 2.使用 Buffer 的例子 三.Buffer 的 capactity.posittion 和limit 四.Buffer 的类型 五.Buffer 分配和写数据 1. Buffer 分配 2.向 buffer 中写数据 3.flip() 方法 六.从 Buffer 中读取数据 七.Buffer 几个方法 1.rewind() 方法 2.clear() 与 compact() 方法 3.mark()

  • 详解java nio中的select和channel

    什么是NIO? 线程在处理数据时,如果线程还处于将数据从channel读到buffer的这段时间内,线程可以去做别的事情,等数据都读到buffer了,线程再回来处理读到的数据 channel是什么? 类比流的概念.与流的区别在于 1.channel是可读可写的,但是一个流要么写要么读 2.chanel可以异步的读和写 3.数据总是从channel中读到buffer,或者从buffer中写到channel 流的读取或写一般是一次性的操作,数据在读取过程中不会有缓存,这也就意味着没有办法自己随便移动

  • 在Java代码中解析html,获取其中的值方法

    有时我们获取到了页面需要在Java代码中进行解析,获取html中的数据,Jsoup是一个很方便的工具. 一.什么是Jsoup? 官网网站:http://jsoup.org/ 可在官网下载对应的jar 通俗的将Jsoup就是一个解析网页的东西 二.示例 1.页面,通过查询获取到了一些数据: 2.源码,这是一个table,class="list",通过这些来唯一标识它 3.代码,将html以String的形式传进来,使用Jsoup进行解析: import org.jsoup.Jsoup;

  • Java NIO异步文件通道原理及用法解析

    在Java 7,AsynchronousFileChannel 被添加到了Java NIO中.使用AsynchronousFileChannel可以实现异步地读取和写入文件数据. 创建一个AsynchronousFileChannel 我们可以使用AsynchronousFileChannel提供的静态方法 open() 创建它.示例代码如下: Path path = Paths.get("data/test.xml"); AsynchronousFileChannel fileCha

  • java NIO 详解

    Java NIO提供了与标准IO不同的IO工作方式: Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中. Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情.当数据被写入到缓冲区时,线程可以继续处理它.从缓冲区写入通道也类似. S

  • Java NIO原理图文分析及代码实现

    前言: 最近在分析hadoop的RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.可以参考:http://baike.baidu.com/view/32726.htm )机制时,发现hadoop的RPC机制的实现主要用到了两个技术:动态代理(动态代理可以参考博客:http://weixiaolu.iteye.com/blog/1477774 )和java NIO.为了能够正确地分析

  • Java NIO:浅析IO模型_动力节点Java学院整理

    也许很多朋友在学习NIO的时候都会感觉有点吃力,对里面的很多概念都感觉不是那么明朗.在进入Java NIO编程之前,我们今天先来讨论一些比较基础的知识:I/O模型.下面本文先从同步和异步的概念 说起,然后接着阐述了阻塞和非阻塞的区别,接着介绍了阻塞IO和非阻塞IO的区别,然后介绍了同步IO和异步IO的区别,接下来介绍了5种IO模型,最后介绍了两种和高性能IO设计相关的设计模式(Reactor和Proactor). 以下是本文的目录大纲: 一.什么是同步?什么是异步? 二.什么是阻塞?什么是非阻塞

  • Java NIO深入分析

    以下我们系统通过原理,过程等方便给大家深入的简介了Java NIO的函数机制以及用法等,学习下吧. 前言 本篇主要讲解Java中的IO机制 分为两块: 第一块讲解多线程下的IO机制 第二块讲解如何在IO机制下优化CPU资源的浪费(New IO) Echo服务器 单线程下的socket机制就不用我介绍了,不懂得可以去查阅下资料 那么多线程下,如果进行套接字的使用呢? 我们使用最简单的echo服务器来帮助大家理解 首先,来看下多线程下服务端和客户端的工作流程图: 可以看到,多个客户端同时向服务端发送

随机推荐