Java NIO Selector

    技术2022-07-10  133

    Java NIO Selector是一个组件,可以检查一个或多个Java NIO Channel实例,并确定那些通道可供使用,read或write。这样,单个线程可以管理多个Channel,从而可以管理多个网络连接。

     

    1.为什么要使用Selector?

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

    但是请记住,现代操作系统和CPU在多任务处理方面变得越来越好,因此多线程的开销随着时间的推移会变得越来越小。实际上,如果一个CPU有多个内核,则可能由于不执行多任务处理而浪费了CPU性能。

     

    这是单线程使用Selector处理3个Channel的图示:

     

    2.创建一个Selector

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

    Selector selector = Selector.open();

     

    3.在Selector中注册Channel

    为了将Channel与Selector一起使用,必须在Selector中注册Channel。使用SelectableChannel.register()方法,如下所示:

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

     

    Channel必须处于非阻塞模式才能与Selector一起使用。这意味着你不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式,Socket Channel才可以。

    注意register()方法的第二个参数,表示你希望通过Selector在Channel中监听那些事件。可以监听四种不同的事件:

    Connect

    Accept

    Read

    Write

    一个Channel触发事件也可以说准备某事件 。因此,已成功连接到服务器的Channel是“connect ready”。接受连接的服务器socket Channel是“accept ready ”。准备读取数据的Channel是“read ready”。准备好向其写入数据的Channel是“write ready”。

    这四个事件由四个SelectionKey常量表示:

    SelectionKey.OP_CONNECT

    SelectionKey.OP_ACCEPT

    SelectionKey.OP_READ

    SelectionKey.OP_WRITE

    如果想要监听多个事件,如下所示:

    int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

     

    4.SelectionKey

    当向Selector注册Channel时,register()方法将返回SelectionKey对象,这个SelectionKey对象包含一些属性:

    interest set

    ready set

    Channel

    Selector

    attached object

    下面分别进行描述

     

    Interest Set

    interest set是监听的事件集,你可以通过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;

     

    可以将interest set与给定的SelectionKey常数进行“与”运算,以找出某个事件是否在interestSet中。

     

    Ready Set

    ready set是Channel "ready"的一组操作,选择后,将主要访问就绪集。可以按以下方式访问就绪集:

    int readySet = selectionKey.readyOps();

     

    你可以用interest set相同的方式测试Channel准备就绪的事件。你也可以改用这四个方法,它们都返回一个布尔值:

    selectionKey.isAcceptable(); selectionKey.isConnectable(); selectionKey.isReadable(); selectionKey.isWritable();

     

    Channel、Selector

    从SelectionKey访问Channel和Selector很简单:

    Channel channel = selectionKey.channel(); Selector selector = selectionKey.selector();

     

    Attaching Objects

    你可以将一个对象附加到SelectionKey,这是识别给定Channel或将更多信息附加到该Channel的便捷方法。例如,将正在使用的Buffer附加到Channel,或包含更多聚合数据的对象。这是附加对象的方式:

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

     

    你还可以在register()方法中向Selector注册Channel时已经附加对象:

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

     

    5.通过Selector选择Channel

    使用Selector注册一个或多个Channel后,你可以调用select()方法。这些方法返回你监听的事件(connect, accept, read or write)的“ready”Channel。换句话说,如果你监听了Channel的读事件,select()方法将告知你准备读的Channel。

    select相关方法:

    int select() 

    int select(long timeout) 

    int selectNow()

    select()会阻塞,直到至少有一个Channel可用于你注册的事件。

    select(long timeout)与select()类似,它会阻塞直到超时(毫秒)。

    selectNow()完全不阻塞,无论Channel是否准备好,它都会立即返回。

     

    select()方法返回的int告诉你多少个Channel ready。也就是说,自从你上次调用select()后已准备好了多少个Channel。如果调用select()返回1,则一个Channel已准备好了;再调用一次select(),若有一个Channel已准备好,则它将再次返回1。

     

    selectedKeys()

    一旦调用了select()方法,并且其返回值指示一个或多个Channel已就绪,则可以通过调用Selector的selectedKeys()方法来访问就绪Channel:

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

     

    您可以迭代此集合以访问就绪Channel:

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

     

    此循环迭代会测试Channel已就绪的事件。

    注意,每次迭代结束时都会调用keyIterator.remove(),Selector不会从集合中删除SelectionKey实例,处理完Channel后,必须执行此操作。下次Channel变为“就绪”状态时,Selector将再次将其添加到集合中。

    由SelectionKey.channel()方法返回的Channel应强制转换为你需要使用的Channel,例如ServerSocketChannel或SocketChannel等。

     

    6.wakeUp()

    即使尚未准备好任何Channel,也可以使已调用被阻塞的select()方法的线程离开select()方法。这是通过让另一个线程在Selector上调用Selector.wakeup()方法来完成的,在select()中等待的线程将立即返回。

    如果其他线程调用了wakeup(),而select()内当前没有任何线程被阻塞,则下一个调用select()的线程将立即“唤醒”。

     

    7.close()

    Selector完成后,将调用其close()方法。这将关闭Selector,并使在此选择器中注册的所有SelectionKey实例无效,但Channel本身未关闭。

     

    8.完整Selector实例

    这是一个完整的示例,打开一个Selector,向其注册一个Channel(忽略通道实例化),并监听Selector四个事件(accept, connect, read, write)。

    Selector selector = Selector.open(); channel.configureBlocking(false); SelectionKey key = channel.register(selector, SelectionKey.OP_READ); while(true) { int readyChannels = selector.selectNow(); 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(); } }

     

    原文地址: https://www.zhblog.net/go/java/tutorial/java-nio-selector?t=614

     

     

     

     
    Processed: 0.017, SQL: 9