Java NIO 通道(三)选择器基础概念介绍

今天来学习NIO中另一个重要的知识,选择器。

NIO这本书讲解的呢,说实话有点乱,感觉就是在翻译,乱的让人看不懂很多东西,所以这篇文章结合了一些博客去理解。

NIO Select

在选择器章节中,有三个很关键的角色,也是核心类,SelectableChannel,SelectionKey和Selector。


1.SelectableChannel

SelectableChannel是可选择通道,它可以被注册到Select对象上,同时可以指定对那个选择器而言,哪种操作是感兴趣的。一个通道可以被注册到多个选择器上,但是对于每个选择器而言只能被注册一次。

Channel分为两大块:

1. FileChannel,主要针对文件的IO的Channel,但是它不支持非阻塞模式,因此它不能支持选择。

2. SelectableChannel,除此之外的Channel,比如Soeket IO就是支持非阻塞模式的。

1.1 设置非阻塞模式

我们可以通过设置它的阻塞模式以便于支持选择:

public abstract SelectableChannel configureBlocking(boolean block) throws IOException;

1.2 注册

上图中Channel和Selector是需要通过SelectableChannel关联的,关联的方法就是register方法:

public abstract SelectionKey register(Selector sel, int ops, Object att)throws ClosedChannelException;
public final SelectionKey register(Selector sel, int ops)throws ClosedChannelException {
    return register(sel, ops, null);
}

2.SelectionKey

在register方法中的第二个参数表示的是感兴趣事件,是一个"insterest"集合,定义在SelectionKey中。可以监听4种不同类型的事件:

1. Connect(连接) ---> OP_CONNECT(1 << 3)

2. Accept(接受)   ---> OP_ACCEPT(1 << 4)

3. Read(读)         ---> OP_READ(1 << 0)

4. Write(写)         ---> OP_WRITE(1 << 2)

Selectkey是关联对象,关联对象内包含了比较多的信息:

1. interest集合

2. read集合

3. Channel对象

4. Selector对象

5. 附加对象(可选)

1. interest集合

这个集合是在注册通道时选择的感兴趣的事件集合。

public abstract int interestOps();
public abstract SelectionKey interestOps(int ops);

2. ready集合

ready集合是通道已经准备就绪的操作的集合。

public abstract int readyOps();

可以通过一些方法来判断Channel中什么事件已经就绪:

public final boolean isAcceptable() {
    return (readyOps() & OP_ACCEPT) != 0;
}
public final boolean isConnectable() {
    return (readyOps() & OP_CONNECT) != 0;
}
public final boolean isReadable() {
    return (readyOps() & OP_READ) != 0;
}
public final boolean isWritable() {
    return (readyOps() & OP_WRITE) != 0;
}

3&&4. Channel和Selector

public abstract SelectableChannel channel();
public abstract Selector selector();

5. 附加对象

SelectableChannel提供你可以添加更多的对象信息到Select中,通过register方法可以传入,并且后续你可以得到附加对象。

3.Selector

3.1 Selector创建

Selector是真正发挥作用的对象。通常是由静态方法open实例化出来。内部其实调用的是SelectorProvider对象的openSelector方法,也可以直接调用它。使用结束后close方法释放资源。

public static Selector open() throws IOException {
        return SelectorProvider.provider().openSelector();
}

3.2 Selector选择

一旦Selector注册了通道后,可以调用select()方法来返回感兴趣事件或者已经准备就绪的通道。

int select()
int select(long timeout)
int selectNow()

1. select() 阻塞到至少有一个通道在你注册的事件上就绪了。

2. select(timeout) 增加了阻塞的时间。

3. selectNow() 不会阻塞(如果自从前一次选择操作后,没有通道变成可选择的,直接返回0)。

select()方法返回本次执行select时从未准备好到准备好状态的Channel数。如果返回不为0,那么可以执行下面的操作:

public abstract Set selectedKeys( );
这个方法就能获取到一个包含SelectionKey对象的集合,分别对应各个准备好的Channel。
Set selectedKeys = selector.selectedKeys();
Iterator 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();
}
遍历循环可以检测每个键对应的通道的就绪事件。 最后的移除操作必须服务端实现,Selector不会参与

3.3 Selector唤醒wakeUp

由于select()方法是阻塞的,因此当没有通道就绪时,怎么让该线程返回呢?可以调用wakeUp()方法。具体操作如下:

1. 其他线程在调用select()方法的对象上调用Selector.wakeup()。阻塞的线程会立马返回。

2. 另外如果调用wakeup()时没有线程阻塞,那么此后如果有线程调用select()方法,会立马返回。

4.完整示例

int port = 30;
ServerSocketChannel serverChannel = ServerSocketChannel.open( );
ServerSocket serverSocket = serverChannel.socket( );
Selector selector = Selector.open( );
serverSocket.bind (new InetSocketAddress (port));
serverChannel.configureBlocking (false);
serverChannel.register (selector, SelectionKey.OP_ACCEPT);
while (true) {
    int n = selector.select( );
    if (n == 0) {
        continue; // nothing to do
    }
    Iterator it = selector.selectedKeys().iterator( );
    while (it.hasNext( )) {
        SelectionKey key = (SelectionKey) it.next( );
        if (key.isAcceptable( )) {
            ServerSocketChannel server =
            (ServerSocketChannel) key.channel( );
            SocketChannel channel = server.accept( );
            if (channel == null) {
                ;//handle code, could happen
            }
            channel.configureBlocking (false);
            channel.register (selector, SelectionKey.OP_READ);SelectionKey.OP_READ);
 
        }
        if (key.isReadable( )) {
            readDataFromSocket (key);
        }
        it.remove( );
    }
}

猜你喜欢

转载自blog.csdn.net/qq_32924343/article/details/80652546