【JAVA】【NIO】7、Java NIO Selector

selector是Java NIO的组件可以检查一个或多个NIO的channel,并且决定哪个channel是为读写准备好了。这种方式,单个线程可以管理多个channel,也就是多个网络连接。

为什么使用选择器

优点就是更少的线程去处理多个通道。实际上,你可以使用一个线程去处理所有的通道。操作系统中线程的切换是很费资源的,而且每个线程本身也占用了一些资源(内存)。所以使用的线程越少越好!

现在的操作系统和CPU在多任务上变得越来越好,所以多线程的开销也变得更小了。事实上,如果一个CPU有多个核心,不用多线程可能是一种浪费。不管怎么说,设计讨论应该是在另一篇文章说。在这里,知道用selector,单线程去处理多通道就足够了。

创建选择器

通过调用Selector.open()方法创建。

注册通道

channel.configureBlocking(false);配置通道为非阻塞模式

channel.register(selector,SelectionKey.OP_ACCEPT);通过该方法注册

使用selector,channel必须是非阻塞模式。意味着FileChannel不用使用selector,因为FileChannel不能转为非阻塞模式。SocketChannel可以正常使用。

注意register方法的第二个参数。这是一个兴趣集合,意思是通过selector监听这个channel时,对什么样的事件感兴趣。有如下几种:

1、Connect

2、Accept

3、Read

4、Write

一个通道触发了一个事件意思就是对该事件准备就绪了。所以,一个channel和服务器连接成功了就是连接就绪。ServerSocketChannel接受了连接就是接受就绪。一个通道有数据准备好被读了就是读就绪。一个通道准备写入数据就是写就绪。

这四中事件通过SelectionKey的四个常量来定义:

1、SelectionKey.OP_CONNECT

2、SelectionKey.OP_ACCEPT

3、SelectionKey.OP_READ

4、SelectionKey.OP_WRITE

如果你对多个事件有兴趣,可以如下来写:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

在文章靠后将会回到兴趣集来讲解。

SelectionKey’s

正如前面所述,当你通过selector给通道注册,register方法将返回一个SelectionKey对象,这个对象包含了一些你感兴趣的属性:

·The interest set

·The ready set

·The Channel

·The Selector

·An attached object (optional)

Interest Set

interest集合是你感兴趣的事件集合。你可以通过SelectionKey读写兴趣集合,如下:

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;

你可以通过&操作找出某个事件是否是兴趣集合的。

Ready Set

ready集合是channel为哪些操作已经就绪了。在一次选择后,你会首先访问ready集合,如下:

int readySet = selectionKey.readyOps();

同样你可以像上面提到的方法一样通过&来测试哪些操作已经就绪了。但是同样你可以通过如下方法来得到:

selectionKey.isAcceptable();

selectionKey.isConnectable();

selectionKey.isReadable();

selectionKey.isWritable();

Channel + Selector

通过SelectionKey访问channel+selector很简单,如下:

Channel channel = selectionKey.channel();

Selector selector = selectionKey.selector();

Attaching Objects

可以将一个对象或者更多信息附加到SelectionKey上以便识别一个具体的通道。例如,你可以附加和通道一起使用的Buffer或者一个包含很多聚集数据的对象,如下:

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();

你也可以再register的时候就附加对象

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

Selecting Channels via a Selector

一旦你通过selector注册了多个通道,你可以调用selecto方法。这些方法返回为兴趣集合(连接,接收,读,写)事件就绪的通道。换言之,如果你对对读就绪的通道感兴趣, 你就会通过select方法返回读就绪的通道。

以下是select方法:

·int select()

·int select(long timeout)

·int selectNow()

select()方法会阻塞直到至少一个通道是为你注册的事件就绪的。

select(long timeout)和select()一样,除了它阻塞会有一个超时时间。

selectNow()没有阻塞,无论什么通道,就绪就立刻返回。

select()方法返回的int表示有多少通道就绪了,即自从最后一次调用select()方法以来,有多少通道就绪了。如果你调用select方法返回1,说明有一个通道就绪了,你再次调用返回1,说明另一个通道就绪了。如果你对第一个就绪的通道什么都不做,你现在就有两个就绪通道,但是仅仅只有一个通道就绪在每次select方法调用过程中。

selectedKeys()

一旦你调用了一个select()方法并且返回值,表明一个或多个通道就绪了,你可以通过selected key集合访问就绪通道,通过调用selectedKeys方法实现,如下:

Set selectedKeys = selector.selectedKeys();

当你注册了一个通道事件时会返回一个SelectionKey对象。这个对象表示注册到该Selector上的通道。你可以通过selectedKeySet访问这些keys。如下:

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();
}

这个循环遍历selected key集合。并检测各个键对应的通道就绪事件。

注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。下次该通道变成就绪时,Selector会再次将其放入已选择键集中。

SelectionKey.channel()方法返回的channel需要转型成你需要的,例如ServerSocketChannel,SocketChannel等。

wakeUp()

一个线程调用select方法阻塞了,即使没有就绪通道,也可以让select方法返回。让其它线程通过刚刚调用select方法的Selector对象调用wakeup方法即可。阻塞在select方法上的线程会立即返回。

如果其它线程调用了wakeup,但是当前没有线程阻塞在select,那么下一个调用select方法的线程会立即唤醒。

close()

用完Selector要调用close方法。关闭Selector并且将所有注册在selector上的键集作废。通道自己不会关闭。

完整实例

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;

public class SelectorDemo {

    public static void main(String[] args) throws IOException {
        //打开服务器套接字通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //非阻塞模式
        ssc.configureBlocking(false);
        //获取与此通道关联的服务器套接字
        ServerSocket ss = ssc.socket();
        //服务绑定
        ss.bind(new InetSocketAddress(8990));
        //打开一个选择器
        Selector selector = Selector.open();
        //在该选择器上注册通道事件
        SelectionKey registerKey = ssc.register(selector, SelectionKey.OP_ACCEPT);
        while(true) {
            int readyChannels = selector.select();
            if(readyChannels==0) {
                System.out.println("No Channel Is Ready !");
                continue;
            }
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
            while(keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                if(key.isAcceptable()) {
                    System.out.println("接收操作!");
                }else if(key.isConnectable()) {
                    System.out.println("连接操作!");
                }else if(key.isReadable()) {
                    System.out.println("读操作!");
                }else if(key.isWritable()) {
                    System.out.println("写操作!");
                }
                keyIterator.remove();
            }
        }
    }

}

以上代码注意,在注册事件时只能是ACCEPT,其它事件在外面注册都会导致程序运行失败,因为其它所有事件都是在ACCEPT后才能够注册的,所以要注意这一点。

下一节:等待

时间: 2024-11-05 10:20:33

【JAVA】【NIO】7、Java NIO Selector的相关文章

Java Nio 十六、Java NIO Files

最后更新:2015-04-15 这个Java NIO的Files类(java.nio.file.Files)提供了数个方法中文件系统中操作文件.这个Java NIO的Files类教程将会覆盖这些方法的大部分通用方法的使用.这个类包含了许多的方法,所以也可以核对这个JavaDoc,如果你需要一个没有在这里描述的方法.这个Files类只是可能为了它仍然有一个方法. 这个java.nio.file.Files类同java.nio.file.Path实例一起工作,以至于你在同Files类工作之前需要理解

Java第二天——标识符命名规则、Java的知识、快捷键的使用、Scanner获取值的常用方法

1.标识符命名规则 字母.下划线.数字.美元符号($)由这四个部分组成. 标识符=首字母+其他 首字母:字母.下划线.美元符号($) 其他:字母.下划线.数字.美元符号($) 注意: 1.首字母不能为数字 2.标识符不能是关键字 3.java语言严格区分大小写,比如:$name $Name表示两个不同的标识符 2.java程序的注释: 单行注释(//).多行注释(/**/).文档(doc)注释 单行注释:一般用于对每一行代码进行说明 多行注释:一般用于对每一段代码.某个方法.某个源文件进行解释说

elasticsearch系列七:ES Java客户端-Elasticsearch Java client(ES Client 简介、Java REST Client、Java Client、Spring Data Elasticsearch)

一.ES Client 简介 1. ES是一个服务,采用C/S结构 2. 回顾 ES的架构 3. ES支持的客户端连接方式 3.1 REST API ,端口 9200 这种连接方式对应于架构图中的RESTful style API这一层,这种客户端的连接方式是RESTful风格的,使用http的方式进行连接 3.2 Transport 连接 端口 9300 这种连接方式对应于架构图中的Transport这一层,这种客户端连接方式是直接连接ES的节点,使用TCP的方式进行连接 4. ES提供了多种

Java NIO:Buffer、Channel 和 Selector

Buffer 一个 Buffer 本质上是内存中的一块,我们可以将数据写入这块内存,之后从这块内存获取数据. java.nio 定义了以下几个 Buffer 的实现,这个图读者应该也在不少地方见过了吧. 其实核心是最后的 ByteBuffer,前面的一大串类只是包装了一下它而已,我们使用最多的通常也是 ByteBuffer. 我们应该将 Buffer 理解为一个数组,IntBuffer.CharBuffer.DoubleBuffer 等分别对应 int[].char[].double[] 等.

Java Nio 十四、Java NIO vs. IO

最后更新时间:2014-06-23 当学习Java NIO和IO的API的时候,一个问题很快的就会出现中我们的脑中: 我什么时候应该使用IO,什么时候应该使用NIO? 在这篇文章中我将会尝试着写出中NIO和IO之间不同的地方,他们的使用场景,以及他们怎么影响你的代码设计. Java NIO和IO的主要不同 下面的表格总结了Java NIO和IO的主要不同.针对这个表格中的不同点我将会给予更加详细的说明. IO NIO 基于流的 基于缓冲区的 堵塞IO 非堵塞IO   Selectors(选择器)

java的几个版本以及jre,jdk等概念——【转载】JDK、Java SE、Java EE、Java ME我该选

我们平时使用的一些软件,有一部分需要Java环境的支持,但是SUN那么多的产品,让人眼花缭乱的版本号,前看后看都差不多的缩写,让我们选择起来的时候常常望而却步,只好跟着感觉走.所以下面我要介绍的就是那些让大家困惑的东西,首先让我们看看SUN的产品之多:下载地址:http://developers.sun.com/downloads/ 哈哈还没有展开它们的子选项呢,让人眼花缭乱,下面介绍大家使用的比较广泛的名词吧:(一)J2SEJava2平台标准版(Java2 Platform Standard

Java网络编程和NIO详解4:浅析NIO包中的Buffer、Channel 和 Selector

Java网络编程与NIO详解4:浅析NIO包中的Buffer.Channel 和 Selector 转自https://www.javadoop.com/post/nio-and-aio 本系列文章首发于我的个人博客:https://h2pl.github.io/ 欢迎阅览我的CSDN专栏:Java网络编程和NIO https://blog.csdn.net/column/details/21963.html 部分代码会放在我的的Github:https://github.com/h2pl/ J

Java内存分配(直接内存、堆内存、Unsafel类、内存映射文件)

1.Java直接内存与堆内存-MarchOn 2.Java内存映射文件-MarchOn 3.Java Unsafe的使用-MarchOn 简单总结: 1.内存映射文件 读文件时候一般要两次复制:从磁盘复制到内核空间再复制到用户空间,内存映射文件避免了第二次复制,且内存分配在内核空间,应用程序访问的就是操作系统的内核内存空间,因此极大提高了读取效率.写文件同理. 2.堆内存分配与直接内存分配: Java申请空间时通常是从JVM堆内存分配的,即 ByteBuffer.allocate(int cap

Java--- J2EE、Java SE、Java EE、Java ME 区别

java SE=Java Standard EditionJava EE=Java Enterprise EditionJava ME=Java Mobile Edition SE主要用于桌面程序,控制台开发(JFC)EE企业级开发(JSP,EJB)ME嵌入式开发(手机,小家电) 目前,Java 2平台有3个版本,它们是适用于小型设备和智能卡的Java 2平台Micro版(Java 2 Platform Micro Edition,J2ME).适用于桌面系统的Java 2平台标准版(Java 2

JAVA程序员的前景如何,JAVA好学吗怎么自学?

JAVA在主流编程语言中的重要地位绝对可以排进TOP3,JAVA在C++的基础上扬弃发展,吸收优点,摒弃多继承.指针等难点.因此JAVA不但功能强大,而且简单易用,无论是学习还是使用都比C++更好上手. java还拥有广泛的应用市场,它的生态系统几乎涵盖了目前市面上所有的软硬件,java几乎是万能的,你能想到的,java基本都能实现. web开发:京东.淘宝.美团这些大型网站,都是用JAVA做的. 移动端开发:目前手机上所有的APP后台代码及部分移动端页面java是都能够做到的. 客户端开发:主