Java NIO(七)Selector

简介: 选择器是Java NIO组件,它可以检查一个或多个NIO通道,并确定哪些通道准备好 阅读或写作。 这样一个单一的线程可以管理多个通道,从而可以管理多个网络连接。

选择器是Java NIO组件,它可以检查一个或多个NIO通道,并确定哪些通道准备好 阅读或写作。 这样一个单一的线程可以管理多个通道,从而可以管理多个网络连接。

为什么要用Selector

使用单个线程来处理多个通道的优点是您需要较少的线程来处理通道。 实际上,你可以只用一个线程来处理你所有的频道。 线程之间的切换对于操作系统而言是昂贵的,并且每个线程也占用操作系统中的一些资源(存储器)。 因此,你使用的线程越少越好。

但请记住,现代操作系统和CPU在多任务处理方面越来越好,所以多线程的开销随着时间的推移而变小。 事实上,如果一个CPU有多个核心,那么你可能会因为没有多任务而浪费CPU资源。 无论如何,这个设计讨论属于不同的文本。 在这里说一下就足够了,你可以使用一个Selector来处理单个线程的多个通道。

下面是一个使用Selector处理3个Channel的线程的例子:


img_31c914f1dea54651d7af61ff9e3f279f.png
image.png

创建一个Selector

您可以通过调用Selector.open()方法创建一个Selector,如下所示:

Selector selector = Selector.open();

用Selector注册channel

为了将频道与选择器一起使用,您必须使用选择器注册频道。 这是使用SelectableChannel.register()方法完成的,如下所示:

channel.configureBlocking(false);//非阻塞

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

通道必须处于非阻塞模式才能与选择器一起使用。 这意味着你不能使用FileChannel和Selector,因为FileChannel不能切换到非阻塞模式。 套接字通道将正常工作。

注意第二个参数,这是一个按照类型设置的,意味着你设置的监听Channel的事件类型,经过选择器,有四个不通的事件:

  1. Connect
  2. Accept
  3. Read
  4. Write
    “发生事件”的频道也被认为是“准备就绪”的事件。 因此,成功连接到另一台服务器的通道是“连接Connect就绪”。 接受传入连接的服务器套接字通道是“接受Accept”就绪。 准备好读取数据Read的通道已经准备就绪。 一个准备好写入数据的通道Write已经准备好了。
    这四个事件由四个SelectionKey常量表示:
  5. SelectionKey.OP_CONNECT
  6. SelectionKey.OP_ACCEPT
  7. SelectionKey.OP_READ
  8. SelectionKey.OP_WRITE
    如果你需要设置多个事件,或者将常量放在一起,就像这样:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;    

SelectionKey

正如你在上一节看到的,当你用一个选择器注册一个Channel时,register()方法返回一个SelectionKey对象。 这个SelectionKey对象包含一些有趣的属性:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)
    下面我来介绍下这几个属性
Interest Set

The interest set是您在“选择”中感兴趣的事件集合,如“用选择器注册通道”部分所述。 您可以通过SelectionKey读取和写入如下所示的interest set:

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;    

正如你所看到的,您可以用给定的SelectionKey常数和兴趣组发现如果一个特定的事件是为了集合。

Ready Set

Ready Set是通道准备好的一组操作。 您将主要在选择后访问Ready Set。 选择在后面的章节中解释。 你可以这样访问readyset:

int readySet = selectionKey.readyOps();

您可以按照与兴趣集相同的方式测试频道准备好的事件/操作。 但是,您也可以使用这四种方法,而这些方法都是重新构造一个布尔值:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Channel + Selector

从SelectionKey访问通道+选择器是很简洁的。 这是如何完成的:

Channel  channel  = selectionKey.channel();

Selector selector = selectionKey.selector();    
Attaching Objects

您可以将对象附加到SelectionKey,这是识别给定通道channel或将更多信息附加到通道channel的便捷方式。 例如,您可以将您正在使用的缓冲区与通道或包含更多聚合数据的对象连接起来。 这里是你如何附加对象:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

在register()方法中,您也可以在向Selector注册Channel时附加一个对象。 这是如何看起来如此:

SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);
通过选择器来选择通道

一旦你用一个Selector注册了一个或多个通道,你可以调用其中一个select()方法。 这些方法返回您感兴趣的事件(连接,接受,读取或写入)的“准备好”的通道。 换句话说,如果您对准备阅读的频道感兴趣,您将收到准备从select()方法读取的通道。
这里是select()方法:

  • int select()
  • int select(long timeout)
  • int selectNow()
    int select(),直到至少有一个通道准备好注册的事件。
    int select(long timeout):和select()一样的,除了它的最大超时毫秒(参数)。
    int selectNow():它返回立即用什么渠道都准备好了。
    select()方法返回的int告诉准备好多少个通道。 也就是说,自上次调用select()以来已经准备了多少个通道。 如果你调用select(),并返回1,因为一个通道已经准备好了,而且你再次调用select(),并且另外一个通道已经准备就绪,它将再次返回1。 如果您已经准备好的第一个频道没有做任何事情,那么您现在有2个就绪频道,但在每个select()调用之间只有一个频道已准备就绪。
selectedKeys()

一旦你调用了一个select()方法,并且它的返回值已经表明一个或者多个通道已经准备就绪,你可以通过调用选择器selectedKeys()方法来通过“selected key set”访问ready channels。 这是如何看起来如此:

Set<SelectionKey> selectedKeys = selector.selectedKeys();    

当您使用Selector注册频道时,Channel.register()方法将返回SelectionKey对象。 这个键表示通道注册到该选择器。 这些键可以通过selectedKeySet()方法访问。 从SelectionKey。
您可以迭代此选定的selected key集以访问就绪通道。 这是如何看起来如此:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

while(keyIterator.hasNext()) {
    
    SelectionKey key = keyIterator.next();

    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
    }

    keyIterator.remove();
}

此循环迭代所选键集中的键。 对于每个SelectionKey ,它测试SelectionKey 以确定SelectionKey 引用的通道已准备好。

注意每次迭代结束时的keyIterator.remove()调用。 选择器不会从选定的SelectionKey 集本身中删除SelectionKey实例。 当您完成频道处理后,您必须执行此操作。 通道下一次变为“就绪”时,选择器将再次将其添加到所选的selected key 。

SelectionKey.channel()方法返回的频道应该被转换为你需要使用的频道,例如ServerSocketChannel或SocketChannel等。

wakeUp()

调用被阻塞的select()方法的线程可以离开select()方法,即使没有通道尚未准备好。 这是通过让不同的线程调用Selector上的Selector.wakeup()方法来完成的,第一个线程调用了select()。 在select()中等待的线程将立即返回。

如果一个不同的线程调用wakeup()并且select()内部当前没有线程被阻塞,那么调用select()的下一个线程将立即“唤醒”。

close()

当你完成Selector时,你调用close()方法。 这将关闭选择器,并使在此选择器中注册的所有SelectionKey实例失效。 渠道本身并没有关闭。

完整的Selector的例子

这是一个完整的例子,它打开一个选择器,注册一个通道(通道实例被省略),并持续监控选择器是否准备好四个事件(接受,连接,读取,写入)。

Selector selector = Selector.open();

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);


while(true) {

  int readyChannels = selector.select();

  if(readyChannels == 0) continue;


  Set<SelectionKey> selectedKeys = selector.selectedKeys();

  Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

  while(keyIterator.hasNext()) {

    SelectionKey key = keyIterator.next();

    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
    }

    keyIterator.remove();
  }
}
相关文章
|
4月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
17天前
|
Java
让星星⭐月亮告诉你,Java NIO之Buffer详解 属性capacity/position/limit/mark 方法put(X)/get()/flip()/compact()/clear()
这段代码演示了Java NIO中`ByteBuffer`的基本操作,包括分配、写入、翻转、读取、压缩和清空缓冲区。通过示例展示了`position`、`limit`和`mark`属性的变化过程,帮助理解缓冲区的工作原理。
21 2
|
2月前
|
存储 网络协议 Java
Java NIO 开发
本文介绍了Java NIO(New IO)及其主要组件,包括Channel、Buffer和Selector,并对比了NIO与传统IO的优势。文章详细讲解了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel及Pipe.SinkChannel和Pipe.SourceChannel等Channel实现类,并提供了示例代码。通过这些示例,读者可以了解如何使用不同类型的通道进行数据读写操作。
Java NIO 开发
|
3月前
|
Java
"揭秘Java IO三大模式:BIO、NIO、AIO背后的秘密!为何AIO成为高并发时代的宠儿,你的选择对了吗?"
【8月更文挑战第19天】在Java的IO编程中,BIO、NIO与AIO代表了三种不同的IO处理机制。BIO采用同步阻塞模型,每个连接需单独线程处理,适用于连接少且稳定的场景。NIO引入了非阻塞性质,利用Channel、Buffer与Selector实现多路复用,提升了效率与吞吐量。AIO则是真正的异步IO,在JDK 7中引入,通过回调或Future机制在IO操作完成后通知应用,适合高并发场景。选择合适的模型对构建高效网络应用至关重要。
67 2
|
3月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
98 0
|
4月前
|
安全 Java Linux
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
147 1
|
3月前
|
存储 网络协议 Java
【Netty 神奇之旅】Java NIO 基础全解析:从零开始玩转高效网络编程!
【8月更文挑战第24天】本文介绍了Java NIO,一种非阻塞I/O模型,极大提升了Java应用程序在网络通信中的性能。核心组件包括Buffer、Channel、Selector和SocketChannel。通过示例代码展示了如何使用Java NIO进行服务器与客户端通信。此外,还介绍了基于Java NIO的高性能网络框架Netty,以及如何用Netty构建TCP服务器和客户端。熟悉这些技术和概念对于开发高并发网络应用至关重要。
63 0
|
4月前
|
安全 Java
【Java】已解决java.nio.channels.OverlappingFileLockException异常
【Java】已解决java.nio.channels.OverlappingFileLockException异常
103 1
|
4月前
|
Java
【Java】已解决java.nio.channels.ClosedChannelException异常
【Java】已解决java.nio.channels.ClosedChannelException异常
326 1
|
4月前
|
Java
【Java】已解决java.nio.channels.FileLockInterruptionException异常
【Java】已解决java.nio.channels.FileLockInterruptionException异常
35 1